bgabriel998/SoftwareDevProject

View on GitHub
app/src/main/java/ch/epfl/sdp/peakar/user/challenge/goal/RemotePointsChallenge.java

Summary

Maintainability
A
0 mins
Test Coverage
A
99%
package ch.epfl.sdp.peakar.user.challenge.goal;

import android.annotation.SuppressLint;
import android.net.Uri;
import android.os.Build;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import ch.epfl.sdp.peakar.database.Database;
import ch.epfl.sdp.peakar.database.DatabaseReference;
import ch.epfl.sdp.peakar.user.challenge.Challenge;
import ch.epfl.sdp.peakar.user.challenge.ChallengeStatus;
import ch.epfl.sdp.peakar.user.score.ScoringConstants;
import ch.epfl.sdp.peakar.user.services.AuthService;
import ch.epfl.sdp.peakar.user.services.OtherAccount;

/**
 * Concrete implementation of <code>PointsChallenge</code> using Database interaction.
 */
public class RemotePointsChallenge extends PointsChallenge {

    /**
     * Constructor for a new concrete points challenge. This constructor needs to be called to retrieve an existing challenge, not to create a new one.
     * @param id unique identifier of the challenge.
     * @param founderID Id of the founder
     * @param challengeName challenge name.
     * @param users users who joined the challenge.
     * @param creationDateTime challenge creation date
     * @param startDateTime challenge start date
     * @param finishDateTime challenge finish date
     * @param durationInDays challenge duration (given in days)
     * @param founderUri URI of the founder profile picture
     * @param status challenge status
     * @param userIDPointsMap current user ranking
     * @param userIDUsernameMap map between user ID and user names
     */
    public RemotePointsChallenge(String id, String founderID, Uri founderUri , String challengeName, List<String> users, int status,
                                 LocalDateTime creationDateTime, int durationInDays,
                                 LocalDateTime startDateTime, LocalDateTime finishDateTime,
                                 @Nullable HashMap<String,Integer> userIDPointsMap,
                                 @Nullable HashMap<String,String> userIDUsernameMap) {
        super(id, founderID, founderUri, challengeName, users, status, creationDateTime, durationInDays, startDateTime, finishDateTime, userIDPointsMap,userIDUsernameMap);
    }
    

    /**
     * Generate a new PointsChallenge.
     * @param founderID ID of the founder of the challenge.
     * @param challengeName name of the challenge
     * @param durationInDays duration of the challenge in number of days
     */
    @SuppressLint("NewApi")
    public static Challenge generateNewChallenge(String founderID, String challengeName, int durationInDays) {
        // Create a new challenge remotely
        DatabaseReference challengeRef = Database.getInstance().getReference().child(Database.CHILD_CHALLENGES).push();
        // Get ID of new challenge
        String id = challengeRef.getKey();
        assert id != null;
        // Add users
        List<String> users = new ArrayList<>(Collections.singleton(founderID));
        challengeRef.child(Database.CHILD_USERS).child(founderID).setValue(FOUNDER);

        //Set name
        challengeRef.child(Database.CHILD_CHALLENGE_NAME).setValue(challengeName);

        //Set name
        challengeRef.child(Database.CHILD_CHALLENGE_FOUNDER).setValue(founderID);

        // Add start/finish times
        LocalDateTime creationTime = LocalDateTime.now();
        challengeRef.child(Database.CHILD_CHALLENGE_CREATION).setValue(creationTime.toString());
        challengeRef.child(Database.CHILD_CHALLENGE_DURATION).setValue(durationInDays);

        //Add challenge status
        challengeRef.child(Database.CHILD_CHALLENGE_STATUS).setValue(ChallengeStatus.PENDING.getValue());

        // Join the new challenge remotely + set value to current amount of points that the user has
        Database.getInstance().getReference().child(Database.CHILD_USERS).child(founderID).child(Database.CHILD_CHALLENGES).child(id).setValue(0);
        RemotePointsChallenge newChallenge = new RemotePointsChallenge(id,founderID, AuthService.getInstance().getPhotoUrl(), challengeName, users, ChallengeStatus.PENDING.getValue(),
                            creationTime, durationInDays,null,null, null, null);
        // Add locally if the founder is the authenticated user
        if(founderID.equals(AuthService.getInstance().getID())) AuthService.getInstance().getAuthAccount().getChallenges().add(newChallenge);


        return newChallenge;
    }


    @SuppressLint("NewApi")
    @Override
    public void join() {
        // If the authenticated user has already joined this challenge.
        if(getUsers().contains(AuthService.getInstance().getID())) return;
        // Join remotely
        Database.getInstance().getReference().child(Database.CHILD_CHALLENGES).child(getID()).child(Database.CHILD_USERS).child(AuthService.getInstance().getID()).setValue(JOINED);
        // Add locally
        AuthService.getInstance().getAuthAccount().getChallenges().add(this);

        /*Set start score */
        Database.getInstance().getReference().child(Database.CHILD_USERS).child(AuthService.getInstance().getID()).child(Database.CHILD_CHALLENGES)
                .child(getID()).setValue(AuthService.getInstance().getAuthAccount().getScore());

        //Check if challenge has started
        if(getStatus() == ChallengeStatus.PENDING.getValue()){
            //Start challenge status and start/stop (locally)
            setStatus(ChallengeStatus.ONGOING);
            setStartDateTime(LocalDateTime.now());
            setFinishDateTime(LocalDateTime.now().plusDays(getDurationInDays()));
            //Start challenge status and start/stop (on DB)
            Database.getInstance().getReference().child(Database.CHILD_CHALLENGES).child(getID()).child(Database.CHILD_CHALLENGE_START).setValue(getStartDateTime().toString());
            Database.getInstance().getReference().child(Database.CHILD_CHALLENGES).child(getID()).child(Database.CHILD_CHALLENGE_FINISH).setValue(getFinishDateTime().toString());
            Database.getInstance().getReference().child(Database.CHILD_CHALLENGES).child(getID()).child(Database.CHILD_CHALLENGE_STATUS).setValue(ChallengeStatus.ONGOING.getValue());


            //Initialize initial amount of points for the two challengers (owner + joiner)
            List<String> users = getUsers();
            for(String user : users){
                if(!user.equals(AuthService.getInstance().getID()))
                    Database.getInstance().getReference().child(Database.CHILD_USERS)
                        .child(user).child(Database.CHILD_CHALLENGES).child(getID()).setValue(OtherAccount.getInstance(user).getScore());

            }
        }
        super.join();
    }

    /**
     * Challenge Timer expired
     * Give points to users
     * Remove challenge from database and from each users that have participated
     * @return number of points gained. If the user has not the most points among the users during
     * the challenge period, he gets 0 pts
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    public int endChallenge(){
        int pointsGained = 0;

        if(getStatus() == ChallengeStatus.ONGOING.getValue()){
            //Set the status of the challenge to ENDED if it is still ONGOING
            setStatus(ChallengeStatus.ENDED);
            Database.getInstance().getReference().child(Database.CHILD_CHALLENGES).child(getID()).child(Database.CHILD_CHALLENGE_STATUS)
                    .setValue(ChallengeStatus.ENDED.getValue());
            //Search the winner among enrolled users
            List<String> enrolledUsers = getUsers();
            //Map <UID,Score>
            HashMap<String, Integer> scoreMap = getPointsGainedPerUser(enrolledUsers);
            //Find winner UID
            String winner = findWinner(scoreMap);
            //Reward winner with points
            rewardWinner(winner);
            //Remove winner -> enrolledUsers is now list of losers
            enrolledUsers.remove(winner);
            //Set winner / loser tags for each enrolled users in the DB
            modifyUserStatusInDB(winner, enrolledUsers);
        }

        //Check if authUser is winner or loser
        String authUser = Database.getInstance().getReference().child(Database.CHILD_CHALLENGES)
                .child(getID())
                .child(Database.CHILD_USERS).child(AuthService.getInstance()
                .getID()).get().getValue(String.class);
        if(authUser.equals(WINNER)){
            pointsGained = ScoringConstants.REWARD_FINISH_CHALLENGE;
        }

        //Check if the user was the last in the challenge -> if yes remove the
        //challenge from the DB
        List<String> users = getUsers();
        //Remove self from the list
        users.remove(AuthService.getInstance().getID());
        if(users.size() >= 1) {
            //Remove user from the /challenge/id/users list
            Database.getInstance().getReference().child(Database.CHILD_CHALLENGES)
                    .child(getID()).child(Database.CHILD_USERS)
                    .child(AuthService.getInstance().getID()).removeValueAsync();
        }
        else {
            //Remove completely the challenge if no user is left in the challenge
            Database.getInstance().getReference().child(Database.CHILD_CHALLENGES).child(getID()).removeValueAsync();
        }

        //Remove the challenge from the user section
        Database.getInstance().getReference().child(Database.CHILD_USERS)
                .child(AuthService.getInstance().getID()).child(Database.CHILD_CHALLENGES)
                .child(getID()).removeValue();

        return pointsGained;
    }

    /**
     * Check among the hashMap of enrolled user which one has the most points
     * @param enrolledUserScoreMap map between UID and score points gained during the challenge
     * @return UID of the user with the most points gained
     */
    public String findWinner(HashMap<String, Integer> enrolledUserScoreMap){
        Map.Entry<String, Integer> maxEntry = null;
        for(Map.Entry<String,Integer> entry : enrolledUserScoreMap.entrySet()){
            if(maxEntry == null || entry.getValue().compareTo(maxEntry.getValue())> 0)
                maxEntry = entry;
        }
        assert maxEntry != null;
        return maxEntry.getKey();
    }

    /**
     * Update winner score in the DB
     * @param winnerUID winner user ID
     */
    private void rewardWinner(String winnerUID){
        int currentScore  = Database.getInstance().getReference()
                .child(Database.CHILD_USERS).child(winnerUID)
                .child(Database.CHILD_SCORE)
                .get().getValue(Integer.class);
        currentScore+= ScoringConstants.REWARD_FINISH_CHALLENGE;
        Database.getInstance().getReference()
                .child(Database.CHILD_USERS).child(winnerUID)
                .child(Database.CHILD_SCORE)
                .setValueAsync(currentScore);
    }

    /**
     * Retrieve points gained by each enrolled user during the challenge
     * @param enrolledUsers list of enrolled users
     * @return HashMap<String,Integer> with key = UID and value = points gained
     */
    public HashMap<String,Integer> getPointsGainedPerUser(List<String> enrolledUsers){
        HashMap<String, Integer> retScoreMap = new HashMap<>();
        for(String user : enrolledUsers){
            //Get user score at beginning of challenge
            int initialScore =  Optional.ofNullable(Database.getInstance().getReference()
                    .child(Database.CHILD_USERS).child(user)
                    .child(Database.CHILD_CHALLENGES).child(getID())
                    .get().getValue(Integer.class)).orElse(0);

            //Get current user score
            int currentScore = Optional.ofNullable(Database.getInstance().getReference()
                    .child(Database.CHILD_USERS).child(user)
                    .child(Database.CHILD_SCORE)
                    .get().getValue(Integer.class)).orElse(0);

            String username = Optional.ofNullable(Database.getInstance().getReference()
                    .child(Database.CHILD_USERS).child(user)
                    .child(Database.CHILD_USERNAME)
                    .get().getValue(String.class)).orElse("");

            //Compute the number of points gained during the challenge
            int pointsGainedInChallenge = currentScore - initialScore;
            retScoreMap.put(user,pointsGainedInChallenge);
        }
        return retScoreMap;
    }

    /**
     * Write to challenge for each user if he is winner or loser
     * @param winnerUID ID of user that won the challenge
     * @param losersUIDs IDs of users that loses the challenge
     */
    private void modifyUserStatusInDB(String winnerUID, List<String> losersUIDs){
        Database.getInstance().getReference().child(Database.CHILD_CHALLENGES).child(getID())
                .child(Database.CHILD_USERS).child(winnerUID).setValue(WINNER);
        for(String userID : losersUIDs){
            Database.getInstance().getReference().child(Database.CHILD_CHALLENGES).child(getID())
                    .child(Database.CHILD_USERS).child(userID).setValue(LOSER);
        }
    }
}