package tc.oc.pgm.goals;

import com.google.common.collect.ImmutableList;
import tc.oc.pgm.match.Competitor;
import tc.oc.pgm.match.Match;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * A measurement of progress towards completing goals in a match
 */
public class GoalProgress implements Comparable<GoalProgress> {
    public final int completed;                                 // Number of completed goals
    public final int touched;                                   // Number of completed goals
    public final ImmutableList<Double> progress;                // Progress of incremental goals, from highest to lowest
    public final ImmutableList<Integer> completionProximity;    // Distance squared to untouched goals, from lowest to highest
    public final ImmutableList<Integer> touchProximity;         // Distance squared to touched goals, from lowest to highest

    private static <T extends Collection<?>> T shorter(T a, T b) {
        return a.size() < b.size() ? a : b;
    }

    private static int compareProgresses(List<Double> a, List<Double> b) {
        int count = Math.max(a.size(), b.size());
        double aProgress = 0, bProgress = 0;

        for(int i = 0; i < count; i++) {
            aProgress = i < a.size() ? a.get(i) : 0;
            bProgress = i < b.size() ? b.get(i) : 0;
            if(aProgress != bProgress) break; // Find first differing progress
        }

        return Double.compare(bProgress, aProgress);
    }

    /**
     * Only compare as many proximity scores as both teams have available.
     * If one team has more proximity scores available than the other,
     * ignore the extras.
     */
    private static int compareProximities(List<Integer> a, List<Integer> b) {
        int count = Math.min(a.size(), b.size());
        int aProximity = Integer.MAX_VALUE, bProximity = Integer.MAX_VALUE;

        for(int i = 0; i < count; i++) {
            aProximity = a.get(i);
            bProximity = b.get(i);
            if(aProximity != bProximity) break;
        }

        return Integer.compare(aProximity, bProximity);
    }

    private GoalProgress(int completed, int touched, Collection<Double> progress, Collection<Integer> completionProximity, Collection<Integer> touchProximity) {
        this.completed = completed;
        this.touched = touched;
        this.progress = ImmutableList.copyOf(progress);
        this.completionProximity = ImmutableList.copyOf(completionProximity);
        this.touchProximity = ImmutableList.copyOf(touchProximity);
    }

    GoalProgress(Competitor competitor) {
        Match match = competitor.getMatch();

        int completed = 0;
        int touched = 0;
        List<Double> progress = new ArrayList<>();
        List<Integer> completionProximity = new ArrayList<>();
        List<Integer> touchProximity = new ArrayList<>();
        final GoalMatchModule gmm = match.needMatchModule(GoalMatchModule.class);

        for(Goal<?> goal : gmm.getGoals(competitor)) {
            if(goal.isRequired()) {
                if(goal.isCompleted(competitor)) {
                    completed++;
                } else {
                    if(goal instanceof ProximityGoal) {
                        ProximityGoal proximity = (ProximityGoal) goal;
                        TouchableGoal touchable = goal instanceof TouchableGoal ? (TouchableGoal) goal : null;

                        if(touchable != null && touchable.hasTouched(competitor)) {
                            touched++;
                            if(proximity.isProximityRelevant(competitor)) {
                                completionProximity.add(proximity.getProximity(competitor));
                            }
                        } else {
                            if(proximity.isProximityRelevant(competitor)) {
                                touchProximity.add(proximity.getProximity(competitor));
                            }
                        }

                        if(goal instanceof IncrementalGoal) {
                            IncrementalGoal incrementalGoal = (IncrementalGoal) goal;
                            progress.add(incrementalGoal.getCompletion());
                        } else if(touchable != null && touchable.hasTouched(competitor)) {
                            // A touched, non-incremental goal is worth 50% completion
                            progress.add(0.5);
                        }
                    }
                }
            }
        }

        Collections.sort(progress, Collections.reverseOrder());
        Collections.sort(completionProximity);
        Collections.sort(touchProximity);

        this.completed = completed;
        this.touched = touched;
        this.progress = ImmutableList.copyOf(progress);
        this.completionProximity = ImmutableList.copyOf(completionProximity);
        this.touchProximity = ImmutableList.copyOf(touchProximity);
    }

    @Override
    public int compareTo(@Nonnull GoalProgress that) {
        // This team has more completed goals, so they take the lead
        if(this.completed > that.completed) return -1;
        if(this.completed < that.completed) return 1;

        // Equal number of completed goals, compare touches
        if(this.touched > that.touched) return -1;
        if(this.touched < that.touched) return 1;

        // Equal number of touches, compare the progress of incremental goals from highest to lowest
        int compareProgress = compareProgresses(this.progress, that.progress);
        if(compareProgress != 0) return compareProgress;

        // All progresses are equal, compare proximity to a completion
        int compareCompletionProximity = compareProximities(this.completionProximity, that.completionProximity);
        if(compareCompletionProximity != 0) return compareCompletionProximity;

        // This team got equally close to another completion, compare proximity to touch
        int compareTouchProximity = compareProximities(this.touchProximity, that.touchProximity);
        if(compareTouchProximity != 0) return compareTouchProximity;

        // Both teams are equal in every measurable respect
        return 0;
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof GoalProgress && this.compareTo((GoalProgress) obj) == 0;
    }

    /**
     * Given two tied sets of accomplishments, return the common subset of
     * accomplishments that can be used to compare the two sets.
     */
    public static GoalProgress commonSubset(GoalProgress a, GoalProgress b) {
        return new GoalProgress(a.touched,
                                a.completed,
                                a.progress,
                                shorter(a.completionProximity, b.completionProximity),
                                shorter(a.touchProximity, b.touchProximity));

    }
}
