package nl.vpro.poel.controller;

import nl.vpro.poel.UserUtil;
import nl.vpro.poel.domain.*;
import nl.vpro.poel.dto.PredictionForm;
import nl.vpro.poel.dto.ScoredPrediction;
import nl.vpro.poel.exception.MultiplierException;
import nl.vpro.poel.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.security.Principal;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

@Controller
@RequestMapping(value = "/predictions")
class PredictionController {

    private final MatchService matchService;

    private final BonusService bonusService;
    private final BonusChoiceService bonusChoiceService;

    private final PredictionService predictionService;

    private final ScoreService scoreService;

    @Autowired
    public PredictionController(MatchService matchService, BonusService bonusService, BonusChoiceService bonusChoiceService, PredictionService predictionService, ScoreService scoreService) {
        this.matchService = matchService;
        this.bonusService = bonusService;
        this.bonusChoiceService = bonusChoiceService;
        this.predictionService = predictionService;
        this.scoreService = scoreService;
    }

    @RequestMapping(method = RequestMethod.GET)
    String showForm(Principal principal, Model model) {

        CurrentUser currentUser = UserUtil.getCurrentUser(principal)
                .orElseThrow(() -> new RuntimeException("No user?!"));

        User user = currentUser.getUser();

        List<BonusChoice> countryChoices = bonusChoiceService.findByCategory(BonusCategory.COUNTRY);
        List<BonusChoice> playerChoices = bonusChoiceService.findByCategory(BonusCategory.PLAYER);

        model.addAttribute("countries", countryChoices);
        model.addAttribute("players", playerChoices);

        Instant now = Instant.now();

        List<ScoredPrediction> finished = toScoredPredictions(matchService.findAllCompleted(), bonusService.findAllCompleted(), user);
        List<ScoredPrediction> unfinished = toScoredPredictions(matchService.findAllUnfinished(now), bonusService.findAllUnfinished(now), user);
        List<ScoredPrediction> future = toScoredPredictions(matchService.findMatchesToPredict(now), bonusService.findBonusesToPredict(now), user);

        model.addAttribute("finished", finished);
        model.addAttribute("unfinished", unfinished);
        model.addAttribute("future", future);

        return "predictions";
    }

    @RequestMapping(method = RequestMethod.POST)
    String savePredictions(Principal principal, @ModelAttribute("predictions") PredictionForm predictions, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
        Instant submittedAt = Instant.now();
        User user = UserUtil.getCurrentUser(principal).orElseThrow(() -> new RuntimeException("No user?!")).getUser();
        try {
            int updates = predictionService.save(user, predictions, submittedAt);
            redirectAttributes.addFlashAttribute("flash", updates + " voorspelling" + (updates != 1 ? "en" : "") + " opgeslagen");
        } catch (MultiplierException e) {
            redirectAttributes.addFlashAttribute("flash", e.getMessage());
        }

        return "redirect:/predictions";
    }

    // TODO: Move this logic out to a service?
    private List<ScoredPrediction> toScoredPredictions(List<Match> matches, List<Bonus> bonuses, User user) {

        List<ScoredPrediction> combined = new ArrayList<>();

        combined.addAll(matches.stream()
                .map(match -> predictionService.getPredictionForMatch(user, match).orElseGet(() -> new Prediction(user, match)))
                .map(prediction -> new ScoredPrediction(prediction, scoreService.getScore(prediction)))
                .collect(Collectors.toList())
        );

        combined.addAll(bonuses.stream()
                        .map(bonus -> predictionService.getPredictionForBonus(user, bonus).orElseGet(() -> new Prediction(user, bonus)))
                        .map(prediction -> new ScoredPrediction(prediction, scoreService.getScore(prediction)))
                        .collect(Collectors.toList())
        );

        return orderScoredPredictionsByStart(combined);
    }

    // TODO: jajaja
    private List<ScoredPrediction> orderScoredPredictionsByStart( List<ScoredPrediction> scoredPredictions ) {

        List<ScoredPrediction> ordered = new ArrayList<>();

        NavigableMap<Date, List<ScoredPrediction>> predictionsByStart = new TreeMap<>();
        for (ScoredPrediction scoredPrediction : scoredPredictions) {
            Date startDate = scoredPrediction.getPrediction().getStart();
            if ( startDate != null ) {
                List<ScoredPrediction> predictions = predictionsByStart.getOrDefault(startDate, new ArrayList<>());
                predictions.add(scoredPrediction);
                predictionsByStart.put( startDate, predictions );
            }
        }

        for (Map.Entry<Date, List<ScoredPrediction>> entry : predictionsByStart.entrySet()) {
            List<ScoredPrediction> scored = entry.getValue();
            for (ScoredPrediction sp : scored) {
                ordered.add( sp);
            }
        }

        return ordered;
    }
}

@@ -827,16 +827,48 @@
 lectors;
+%0Aimport java.util.stream.Stream;
 %0A%0A@Contr
@@ -3841,33 +3841,32 @@
 ser user) %7B%0A
-%0A
         
 List%3CScoredP
@@ -3857,86 +3857,46 @@
     
-List%3CScoredPrediction%3E combined = new ArrayList%3C%3E();%0A%0A        combined.addAll(
+return Stream.concat(%0A                
 matc
@@ -3900,32 +3900,40 @@
 atches.stream()%0A
+        
                 
@@ -4002,55 +4002,25 @@
 tch)
-.orElseGet(() -%3E new Prediction(user, match)))%0A
+%0A                
     
@@ -4032,38 +4032,36 @@
        .
-map(prediction
+orElseGet(()
  -%3E new 
 ScoredPr
@@ -4044,38 +4044,32 @@
 seGet(() -%3E new 
-Scored
 Prediction(predi
@@ -4067,55 +4067,23 @@
 ion(
-prediction, scoreService.getScore(prediction
+user, match
 )))
+,
 %0A   
@@ -4099,74 +4099,8 @@
     
-.collect(Collectors.toList())%0A        );%0A%0A        combined.addAll(
 bonu
@@ -4202,16 +4202,49 @@
 , bonus)
+%0A                                
 .orElseG
@@ -4282,32 +4282,42 @@
 onus)))%0A        
+        )%0A
                 
@@ -4424,818 +4424,103 @@
     
-        .collect(Collectors.toList())%0A        );%0A%0A        return orderScoredPredictionsByStart(combined);%0A    %7D%0A%0A    // TODO: jajaja%0A    private List%3CScoredPrediction%3E orderScoredPredictionsByStart( List%3CScoredPrediction%3E scoredPredictions ) %7B%0A%0A        List%3CScoredPrediction%3E ordered = new ArrayList%3C%3E();%0A%0A        NavigableMap%3CDate, List%3CScoredPrediction%3E%3E predictionsByStart = new TreeMap%3C%3E();%0A        for (ScoredPrediction scoredPrediction : scoredPredictions) %7B%0A            Date startDate = scoredPrediction.getPrediction().getStart();%0A            if ( startDate != null ) %7B%0A                List%3CScoredPrediction%3E predictions = predictionsByStart.getOrDefault(startDate, new ArrayList%3C%3E());%0A                predictions.add(scoredPrediction);%0A                predictionsByStart.put( startDate, predictions );
+.sorted((sp1, sp2) -%3E sp1.getPrediction().getStart().compareTo(sp2.getPrediction().getStart()))
 %0A   
@@ -4531,308 +4531,42 @@
     
- %7D%0A
      
-   %7D%0A%0A        for (Map.Entry%3CDate, List%3CScoredPrediction%3E%3E entry : predictionsByStart.entrySet()) %7B%0A            List%3CScoredPrediction%3E scored = entry.getValue();%0A            for (ScoredPrediction sp : scored) %7B%0A                ordered.add( sp);%0A            %7D%0A        %7D%0A%0A        return ordered
+.collect(Collectors.toList())
 ;%0A  

Use the Stream, Luke!