package de.ivu.elect.business.ergebnis.boundary;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.ejb.Stateless;
import javax.inject.Inject;

import de.ivu.elect.business.ergebnis.control.AbstractKumulierteStimmenSearch;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.control.ErgebnisDatenCreator;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
import de.ivu.elect.business.ergebnis.entity.GewinnerUnterlegenerStimmendifferenzSearchResult;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.kandidatur.entity.Direktkandidatur;
import de.ivu.elect.business.wahl.entity.Wahl;

/**
 *  on 28.05.2015.
 */
@Stateless
public class GewinnerUnterlegenerStimmendifferenzSearch extends AbstractKumulierteStimmenSearch<GewinnerUnterlegenerStimmendifferenzSearchParameters, GewinnerUnterlegenerStimmendifferenzSearchResult> implements Serializable {

  @Inject
  ErgebnisDatenCreator ergebnisDatenCreator;

  @Inject
  WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  @Override
  protected List<GewinnerUnterlegenerStimmendifferenzSearchResult> suche(Wahl wahl, Gebiet gebietSelected, Ergebnis ergebnisSelected, GewinnerUnterlegenerStimmendifferenzSearchParameters searchParameters) {

    ErgebnisDaten ergebnisDaten = ergebnisDatenCreator.createErgebnisDatenSitzberechnung(ergebnisSelected);

    Map<Wahldurchfuehrungsgebiet, List<Direktkandidatur>> kandidaturenImGebiet = bestimmeKandidaturen(wahldurchfuehrungsgebietDAO.findByPrimaryKey(gebietSelected.getPk()), ergebnisDaten.getDirektkandidaturen());

    ArrayList<GewinnerUnterlegenerStimmendifferenzSearchResult> result = new ArrayList<>();

    kandidaturenImGebiet.forEach((wahldurchfuehrungsgebiet, direktkandidaturen) -> {

      GewinnerUnterlegenerStimmendifferenzSearchResult resultFuerGebiet = bestimmeErstenUndUnterlegenen(wahldurchfuehrungsgebiet, direktkandidaturen,
          ergebnisDaten, searchParameters);
      if (resultFuerGebiet != null) {
        result.add(resultFuerGebiet);
      }
    });

    return result;
  }

  private GewinnerUnterlegenerStimmendifferenzSearchResult bestimmeErstenUndUnterlegenen(Wahldurchfuehrungsgebiet gebiet, List<Direktkandidatur> kandidaturen, ErgebnisDaten ergebnisDaten, GewinnerUnterlegenerStimmendifferenzSearchParameters searchParameters) {

    Map<Direktkandidatur, Long> anzahlStimmenProKandidatur = new HashMap<>();
    for (Direktkandidatur direktkandidatur : kandidaturen) {
      Long stimmenFuerDirektkandidatur = ergebnisDaten.getErgebnisDatenDirektkandidaturen().getStimmenFuerDirektkandidatur(direktkandidatur, gebiet);
      if (stimmenFuerDirektkandidatur > 0L) {
        anzahlStimmenProKandidatur.put(direktkandidatur, stimmenFuerDirektkandidatur);
      }
    }

    List<Direktkandidatur> direktkandidaturen = new ArrayList<>(anzahlStimmenProKandidatur.keySet());
    if (direktkandidaturen.size() > 1) {
      direktkandidaturen.sort((o1, o2) -> anzahlStimmenProKandidatur.get(o2).compareTo(anzahlStimmenProKandidatur.get(o1)));

      Direktkandidatur erster = direktkandidaturen.get(0);
      Direktkandidatur zweiter = direktkandidaturen.get(1);

      // WASDE-1289
      if (Objects.equals(anzahlStimmenProKandidatur.get(erster), anzahlStimmenProKandidatur.get(zweiter))) {
        if (isLosentscheidOffen(erster, ergebnisDaten.getGewaehlte()) || isLosentscheidOffen(zweiter, ergebnisDaten.getGewaehlte())) {
          return null; // kein eindeutiger Gewinner
        }

        Direktkandidatur ersterGewaehlt = null;
        Direktkandidatur zweiterNichtGewaehlt = null;

        for (Direktkandidatur direktkandidatur : direktkandidaturen) {
          if (ersterGewaehlt == null || zweiterNichtGewaehlt == null) {

            boolean gewaehlt = bestimmeGewaehlt(direktkandidatur, ergebnisDaten.getGewaehlte());
            if (ersterGewaehlt == null && gewaehlt) {
              ersterGewaehlt = direktkandidatur;
            }
            if (zweiterNichtGewaehlt == null && !gewaehlt) {
              zweiterNichtGewaehlt = direktkandidatur;
            }
          }
        }

        if (ersterGewaehlt != null) {
          erster = ersterGewaehlt;
        }
        if (zweiterNichtGewaehlt != null) {
          zweiter = zweiterNichtGewaehlt;
        }

      }

      Long gueltige = ergebnisDaten.getErststimmenAktuellGueltige(gebiet, searchParameters.getStimmabgabeart());

      return new GewinnerUnterlegenerStimmendifferenzSearchResult(gebiet, erster, anzahlStimmenProKandidatur.get(erster), zweiter, anzahlStimmenProKandidatur.get(zweiter), gueltige);
    }

    return null;

  }

  private boolean bestimmeGewaehlt(Direktkandidatur direktkandidatur, List<Gewaehlt> gewaehlte) {
    return gewaehlte.stream()
        .filter(gewaehlt -> gewaehlt.getKandidatur().isDirektkandidatur())
        .filter(gewaehlt -> direktkandidatur.equals(gewaehlt.getKandidatur()))
        .anyMatch(gewaehlt -> gewaehlt.getGewaehltStatus().isGewaehlt());
  }

  private boolean isLosentscheidOffen(Direktkandidatur direktkandidatur, List<Gewaehlt> gewaehlte) {
    return gewaehlte.stream()
        .filter(gewaehlt -> gewaehlt.getKandidatur().isDirektkandidatur())
        .filter(gewaehlt -> direktkandidatur.equals(gewaehlt.getKandidatur()))
        .anyMatch(gewaehlt -> gewaehlt.getGewaehltStatus().isLosOffen());
  }

  private Map<Wahldurchfuehrungsgebiet, List<Direktkandidatur>> bestimmeKandidaturen(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, List<Direktkandidatur> direktkandidaturen) {
    return direktkandidaturen.stream().filter(d -> d.getGebiet().isEqualOderUntergebietVon(wahldurchfuehrungsgebiet)).collect(Collectors.groupingBy(s->s.getGebiet().castTo(Wahldurchfuehrungsgebiet.class).getEntity()));
  }

  @Override
  protected Comparator<? super GewinnerUnterlegenerStimmendifferenzSearchResult> getDefaultComparator() {
    return Comparator.comparing(GewinnerUnterlegenerStimmendifferenzSearchResult::getDifferenzStimmenErsterZweiter);
  }

}
