rkuenzi-epfl/Wnder

View on GitHub
app/src/main/java/com/github/wnder/tour/FirebaseTourDatabase.java

Summary

Maintainability
A
3 hrs
Test Coverage
A
100%
package com.github.wnder.tour;

import android.content.Context;
import android.location.Location;

import com.github.wnder.picture.FirebasePicturesDatabase;
import com.github.wnder.picture.InternalCachePictureDatabase;
import com.google.android.gms.tasks.Task;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.SetOptions;

import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;

import javax.inject.Inject;

public class FirebaseTourDatabase implements TourDatabase{

    private final CollectionReference tourCollection;

    private final InternalCachePictureDatabase ICPD;

    @Inject
    public FirebaseTourDatabase(Context context) {
        tourCollection = FirebaseFirestore.getInstance().collection("tours");

        ICPD = new InternalCachePictureDatabase(context);
    }

    @Override
    public CompletableFuture<List<String>> getTourPics(String tourUniqueId) {
        CompletableFuture<List<String>> tour = new CompletableFuture<>();

        tourCollection.document(tourUniqueId).get().
            addOnSuccessListener((documentSnapshot) -> {
                List<String> picIds = (List<String>) documentSnapshot.get("tourPics");
                tour.complete(picIds);
            })
            .addOnFailureListener(tour::completeExceptionally);

        return tour;
    }

    @Override
    public CompletableFuture<String> getTourName(String tourUniqueId) {
        CompletableFuture<String> tourNameFuture = new CompletableFuture<>();

        tourCollection.document(tourUniqueId).get().
            addOnSuccessListener((documentSnapshot) -> {
                String tourName = documentSnapshot.getString("tourName");
                tourNameFuture.complete(tourName);
            })
            .addOnFailureListener(tourNameFuture::completeExceptionally);

        return tourNameFuture;
    }

    @Override
    public CompletableFuture<Double> getTourDistance(String tourUniqueId, Location place){
        CompletableFuture<Double> distanceFuture = new CompletableFuture<>();

        tourCollection.document(tourUniqueId).get().
                addOnSuccessListener((documentSnapshot) -> {
                    double latitude = documentSnapshot.getDouble("tourFirstLat");
                    double longitude = documentSnapshot.getDouble("tourFirstLong");

                    Location tourLocation = new Location("");
                    tourLocation.setLatitude(latitude);
                    tourLocation.setLongitude(longitude);

                    double distance = tourLocation.distanceTo(place);

                    distanceFuture.complete(distance);
                })
                .addOnFailureListener(distanceFuture::completeExceptionally);

        return distanceFuture;
    }

    @Override
    public CompletableFuture<Double> getTourLength(String tourUniqueId){
        CompletableFuture<Double> tourLengthFuture = new CompletableFuture<>();

        tourCollection.document(tourUniqueId).get().
                addOnSuccessListener((documentSnapshot) -> {
                    double tourLength = documentSnapshot.getDouble("tourLength");
                    tourLengthFuture.complete(tourLength);
                })
                .addOnFailureListener(tourLengthFuture::completeExceptionally);

        return tourLengthFuture;
    }

    @Override
    public CompletableFuture<Void> uploadTour(String tourUniqueId, String tourName, List<String> picsUniqueIds) {

        CompletableFuture<Void> nameUpload = uploadTourName(tourUniqueId, tourName);
        CompletableFuture<Void> picsUpload = uploadTourPics(tourUniqueId, picsUniqueIds);
        CompletableFuture<Void> firstLocUpload = uploadTourFirstLocation(tourUniqueId, picsUniqueIds.get(0));
        CompletableFuture<Void> lengthUpload = uploadTourLength(tourUniqueId, picsUniqueIds);

        return CompletableFuture.allOf(nameUpload, picsUpload, firstLocUpload, lengthUpload);
    }

    /**
     * Uploads a tour name to the db
     * @param tourUniqueId tour unique id
     * @param tourName tour name
     * @return a future completing when transaction is done
     */
    private CompletableFuture<Void> uploadTourName(String tourUniqueId, String tourName){
        CompletableFuture<Void> uploadTourNameFuture = new CompletableFuture<>();

        Map<String, String> tourNameMap = new HashMap<>();
        tourNameMap.put("tourName", tourName);

        tourCollection.document(tourUniqueId).set(tourNameMap, SetOptions.merge())
                .addOnSuccessListener(result -> uploadTourNameFuture.complete(null))
                .addOnFailureListener(uploadTourNameFuture::completeExceptionally);

        return uploadTourNameFuture;
    }

    /**
     * Uploads a tour pic list in the db
     * @param tourUniqueId tour unique id
     * @param tourPics tour pics
     * @return a future completing when transaction is done
     */
    private CompletableFuture<Void> uploadTourPics(String tourUniqueId, List<String> tourPics){
        CompletableFuture<Void> uploadTourPicsFuture = new CompletableFuture<>();

        Map<String, List<String>> tourPicsMap = new HashMap<>();
        tourPicsMap.put("tourPics", tourPics);

        tourCollection.document(tourUniqueId).set(tourPicsMap, SetOptions.merge())
                .addOnSuccessListener(result -> uploadTourPicsFuture.complete(null))
                .addOnFailureListener(uploadTourPicsFuture::completeExceptionally);

        return uploadTourPicsFuture;
    }

    /**
     * Uploads a tour first position in the db
     * @param tourUniqueId tour unique id
     * @param firstPicId id of the first pic of the tour
     * @return a future completing when transaction is done
     */
    private CompletableFuture<Void> uploadTourFirstLocation(String tourUniqueId, String firstPicId){
        CompletableFuture<Void> uploadLocFuture = new CompletableFuture<>();

        ICPD.getLocation(firstPicId).thenAccept(location -> {
            Map<String, Double> tourFirstLoc = new HashMap<>();
            tourFirstLoc.put("tourFirstLat", location.getLatitude());
            tourFirstLoc.put("tourFirstLong", location.getLongitude());

            tourCollection.document(tourUniqueId).set(tourFirstLoc, SetOptions.merge())
                    .addOnSuccessListener(result -> uploadLocFuture.complete(null))
                    .addOnFailureListener(uploadLocFuture::completeExceptionally);
        });

        return uploadLocFuture;
    }

    /**
     * Uploads a tour length
     * @param tourUniqueId tour unique id
     * @param picIds ids of the pics in the tour (in order)
     * @return a future completing when the transaction is done
     */
    private CompletableFuture<Void> uploadTourLength(String tourUniqueId, List<String> picIds){
        CompletableFuture<Void> uploadLengthFuture = new CompletableFuture<>();
        Set<Double> allLengths = new HashSet<>();

        //create an array to store all the length futures
        CompletableFuture[] futureLengths = new CompletableFuture[picIds.size() - 1];

        //for each 2 pics, get their inbetween distance
        for(int i = 0; i < picIds.size() - 1; i++){
            final int id = i;

            futureLengths[id] = getDistBetweenTwoPics(picIds.get(id), picIds.get(id+1)).thenApply(distance -> {
                return distance;
            });
        }

        //Once a length is completed, add it to all the lengths already completed
        for(CompletableFuture<Double> futureLength: futureLengths){
            futureLength.thenAccept(score -> allLengths.add(score));
        }

        //once all scores have been completed, complete the future, compute total length, and upload
        CompletableFuture<Void> allLengthsReceived = CompletableFuture.allOf(futureLengths);
        allLengthsReceived.thenAccept(empty -> {
            double totalLength = 0;
            for(double length: allLengths){
                totalLength += length;
            }

            Map<String, Double> tourLength = new HashMap<>();
            tourLength.put("tourLength", totalLength);

            tourCollection.document(tourUniqueId).set(tourLength, SetOptions.merge())
                    .addOnSuccessListener(result -> uploadLengthFuture.complete(null))
                    .addOnFailureListener(uploadLengthFuture::completeExceptionally);
        });

        return uploadLengthFuture;
    }

    /**
     * Gets the distance between two pics
     * @param firstPicId first pic
     * @param secondPicId second pic
     * @return distance between first and second pic
     */
    private CompletableFuture<Double> getDistBetweenTwoPics(String firstPicId, String secondPicId){
        CompletableFuture<Double> distanceFuture = new CompletableFuture<>();

        ICPD.getLocation(firstPicId).thenAccept(location1 -> {
            ICPD.getLocation(secondPicId).thenAccept(location2 -> {
               double distance = location1.distanceTo(location2);

               distanceFuture.complete(distance);
            });
        });

        return distanceFuture;
    }

    @Override
    public String generateTourUniqueId(String tourName){
        return tourName + Calendar.getInstance().getTimeInMillis();
    }
}