package com.telephoners.krakyournet.ctf.repositories;

import com.telephoners.krakyournet.ctf.beans.Flag;
import com.telephoners.krakyournet.ctf.beans.Solution;
import com.telephoners.krakyournet.ctf.beans.Team;
import com.telephoners.krakyournet.ctf.beans.tasks.Task;
import com.telephoners.krakyournet.ctf.exceptions.SolutionAlreadySubmittedException;
import org.mongodb.morphia.Datastore;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@Singleton
public class SolutionsRepository extends Repository<Solution>
{
    private final TasksRepository tasksRepository;

    @Inject
    public SolutionsRepository(TasksRepository tasksRepository,
                               Datastore datastore)
    {
        super(datastore);
        this.tasksRepository = tasksRepository;
    }

    public Map<Integer, List<Flag>> getTeamSolutions(Team team)
    {
        //todo: do not identify team by name? conflict in team ids?
        return datastore.createQuery(Solution.class)
                .filter("team.name", team.getName()).asList()
                .stream()
                .collect(Collectors.groupingBy(new Function<Solution, Task>()
                {
                    @Override
                    public Task apply(Solution solution1)
                    {
                        return solution1.getTask();
                    }
                }))
                .entrySet()
                .stream()
                .collect(Collectors.toMap(
                        taskSolutions -> taskSolutions.getKey().getLevel(),
                        taskSolutions -> taskSolutions.getValue().stream()
                                .map(Solution::getFlag).collect(Collectors.toList())
                ));
    }

    private boolean isAlreadySubmittedSolution(Solution solution)
    {
        List<Solution> solutions = datastore.find(Solution.class)
                .filter("flag.value", solution.getFlag().getValue()) //todo: do not filter by field
                .filter("team.name", solution.getTeam().getName()) //todo: whole object instead
                .asList();
        return !solutions
                .isEmpty();
    }

    public List<Integer> getCompletedTasks(Team team)
    {
        Map<Integer, List<Flag>> teamSolutions = getTeamSolutions(team);
        return tasksRepository.getAll().stream()
                .filter(task -> {
                    List<Flag> teamTaskSolutions = teamSolutions.get(task.getLevel());
                    return teamTaskSolutions != null && teamTaskSolutions.size() == task.getFlags().size();
                })
                .map(Task::getLevel)
                .collect(Collectors.toList());
    }

    public void submitSolution(Solution solution)
    {
        if (isAlreadySubmittedSolution(solution)) {
            throw new SolutionAlreadySubmittedException();
        }
        add(solution);
    }

    private int calculatePointsSum(List<Flag> flags) {
       return flags.stream()
                .mapToInt(Flag::getPoints)
                .sum();
    }

    public Map<Integer, Integer> getPointsSum(Team team) {
        return getTeamSolutions(team).entrySet().stream()
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        entry -> calculatePointsSum(entry.getValue())
                ));

    }
}