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

import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

import javax.annotation.Nullable;
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.GewaehltStatus;
import de.ivu.elect.business.ergebnis.entity.ParteienwechselSearchResult;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.wahl.entity.Wahl;


import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

/**
 * WASDE-589 - WAS: Ergebnisse -> Auswertung ergänzen: Parteienwechsel in Wahlkreisen  on 23.10.2015.
 */
@Stateless
public class ParteienwechselSearch extends AbstractKumulierteStimmenSearch<ParteienwechselSearchParameters, ParteienwechselSearchResult> implements Serializable {

  private static final long serialVersionUID = 1L;

  @Inject
  private transient ErgebnisDatenCreator ergebnisDatenCreator;

  public ParteienwechselSearch() {
    super();
  }

  @Override
  protected Comparator< ? super ParteienwechselSearchResult> getDefaultComparator() {
    return (u, v) -> u.getLaufendeNummer().compareTo(v.getLaufendeNummer());
  }

  @Override
  protected List<ParteienwechselSearchResult> suche(Wahl wahl, Gebiet gebietSelected, Ergebnis ergebnisSelected, ParteienwechselSearchParameters searchParameters) {
    ErgebnisDaten ergebnisDaten = ergebnisDatenCreator.createErgebnisDatenSitzberechnung(ergebnisSelected);

    AtomicLong laufendeNummer = new AtomicLong(1L);
    return bestimmeWahlkreise(gebietSelected, ergebnisDaten).stream().map(wahlkreis -> ermittleParteienwechsel(wahlkreis, ergebnisDaten, searchParameters)).filter(s -> s != null)
        .filter(ParteienwechselSearchResult::isWechsel).sorted(getParteireihenfolgeComparator(ergebnisDaten)).map(result -> new ParteienwechselSearchResult(laufendeNummer.getAndIncrement(), result))
        .collect(toList());
  }

  private List<Wahldurchfuehrungsgebiet> bestimmeWahlkreise(Gebiet gebietSelected, ErgebnisDaten ergebnisDaten) {
    if (gebietSelected == null) {
      return Collections.emptyList();
    }

    return ergebnisDaten.getDirektwahlGebiete().stream().filter(gebietSelected::isInGebietshierarchieUebergeordnetesGebietVon).collect(toList());
  }

  protected Comparator< ? super ParteienwechselSearchResult> getParteireihenfolgeComparator(ErgebnisDaten ergebnisDaten) {
    List<PolitischeGruppe> parteireihenfolge = ergebnisDaten.getPolitischeGruppenSortiertNachPositionAufGebiet(ergebnisDaten.getWurzelgebiet());

    return (u, v) -> {

      int result = vergleicheParteien(u.getAktuellerGewinner(), v.getAktuellerGewinner(), parteireihenfolge);

      if (result == 0) {
        result = vergleicheParteien(u.getVorperiodeGewinner(), v.getVorperiodeGewinner(), parteireihenfolge);
      }

      if (result == 0) {
        result = vergleicheWahlkreise(u.getWahlkreis(), v.getWahlkreis());
      }

      return result;
    };

  }

  private int vergleicheWahlkreise(Wahldurchfuehrungsgebiet w1, Wahldurchfuehrungsgebiet w2) {
    try {
      Long nummer1 = Long.parseLong(w1.getNummer());
      Long nummer2 = Long.parseLong(w2.getNummer());
      return nummer1.compareTo(nummer2);
    } catch (Exception e) {
      // ignorieren
    }
    return 0;
  }

  private int vergleicheParteien(PolitischeGruppe p1, PolitischeGruppe p2, List<PolitischeGruppe> parteireihenfolge) {
    if (p1 == null && p2 == null) {
      return 0;
    }
    if (p1 == null) {
      return 1;
    }
    if (p2 == null) {
      return -1;
    }

    Integer posP1 = parteireihenfolge.indexOf(p1);
    Integer posP2 = parteireihenfolge.indexOf(p2);
    return posP1.compareTo(posP2);
  }

  @Nullable
  private ParteienwechselSearchResult ermittleParteienwechsel(Wahldurchfuehrungsgebiet wahlkreis, ErgebnisDaten ergebnisDaten, ParteienwechselSearchParameters searchParameters) {

    Stimmabgabeart stimmabgabeart = searchParameters.getStimmabgabeart();
    List<PolitischeGruppe> alleParteien = ergebnisDaten.getPolitischeGruppenSortiertNachPositionAufGebiet(wahlkreis);

    Map<Long, List<PolitischeGruppe>> parteienMitStimmenAktuell = alleParteien.stream().filter(s -> ergebnisDaten.getErststimmenAktuell(s, wahlkreis, stimmabgabeart)!=null && ergebnisDaten.getErststimmenAktuell(s, wahlkreis, stimmabgabeart) > 0)
        .collect(groupingBy(s -> ergebnisDaten.getErststimmenAktuell(s, wahlkreis, stimmabgabeart)));
    Map<Long, List<PolitischeGruppe>> parteienMitStimmenVorperiode = alleParteien.stream().filter(s -> ergebnisDaten.getErststimmenVorperiode(s, wahlkreis, stimmabgabeart) !=null && ergebnisDaten.getErststimmenVorperiode(s, wahlkreis, stimmabgabeart) > 0)
        .collect(groupingBy(s -> ergebnisDaten.getErststimmenVorperiode(s, wahlkreis, stimmabgabeart)));

    List<Map.Entry<Long, List<PolitischeGruppe>>> sortiertNachStimmenAktuell = parteienMitStimmenAktuell.entrySet().stream().sorted((u, v) -> v.getKey().compareTo(u.getKey())).collect(toList());
    List<Map.Entry<Long, List<PolitischeGruppe>>> sortiertNachStimmenVorperiode = parteienMitStimmenVorperiode.entrySet().stream().sorted((u, v) -> v.getKey().compareTo(u.getKey())).collect(toList());

    if (sortiertNachStimmenAktuell.isEmpty() || sortiertNachStimmenVorperiode.isEmpty()) {
      return null;
    }

    Map.Entry<Long, List<PolitischeGruppe>> ersteAktuell = sortiertNachStimmenAktuell.get(0);
    Map.Entry<Long, List<PolitischeGruppe>> ersteVorperiode = sortiertNachStimmenVorperiode.get(0);

    // kein Eindeutiger Sieger
    List<PolitischeGruppe> ersteAktuelleKandidaten = ersteAktuell.getValue();
    if (ersteAktuelleKandidaten.isEmpty()) {
      return null;
    }

    PolitischeGruppe aktuellerGewinner;
    if (ersteAktuelleKandidaten.size() > 1) {
      // Losentscheide prüfen WASDE-600, das heißt also Gewaehlte betrachten...
      List<Gewaehlt> gewaehlteFuerWahlkreis = ergebnisDaten.getGewaehlteFuerGebiet(wahlkreis);

      Set<PolitischeGruppe> gewaelteGruppen = gewaehlteFuerWahlkreis.stream().filter(s -> s.getKandidatur().isDirektkandidatur()) // Nur Direktkandidaten entscheiden
          .filter(s -> GewaehltStatus.GEWAEHLT.equals(s.getGewaehltStatus())) // Kandidat ist nach Losentscheid gewaehlt
          .map(s -> s.getKandidatur().getGruppe()).collect(toSet());

      List<PolitischeGruppe> gruppenMitGewaehltenKandidaten = ersteAktuelleKandidaten.stream().filter(gewaelteGruppen::contains).collect(toList());
      if (gruppenMitGewaehltenKandidaten.size() == 1) {
        aktuellerGewinner = gruppenMitGewaehltenKandidaten.get(0);
      } else {
        // Losentscheid noch nicht durch => kein Gewinner
        return null;
      }
    } else {
      aktuellerGewinner = ersteAktuelleKandidaten.get(0);
    }

    // kein Eindeutiger Sieger Vorperiode
    List<PolitischeGruppe> ersteVorperiodeKandidaten = ersteVorperiode.getValue();
    if (ersteVorperiodeKandidaten.isEmpty() || ersteVorperiodeKandidaten.size() > 1) {
      return null;
    }

    PolitischeGruppe vorperiodeGewinner = ersteVorperiodeKandidaten.get(0);

    Long anzahlStimmenGueltigAktuell = ergebnisDaten.getErststimmenAktuellGueltige(wahlkreis, stimmabgabeart);
    Long anzahlStimmenGueltigVorperiode = ergebnisDaten.getErststimmenVorperiodeGueltige(wahlkreis, stimmabgabeart);

    Long anzahlStimmenAktuellerGewinnerAktuell = ergebnisDaten.getErststimmenAktuell(aktuellerGewinner, wahlkreis, stimmabgabeart);
    Long anzahlStimmenAktuellerGewinnerVorperiode = ergebnisDaten.getErststimmenVorperiode(aktuellerGewinner, wahlkreis, stimmabgabeart);
    Long anzahlStimmenVorperiodeGewinnerAktuell = ergebnisDaten.getErststimmenAktuell(vorperiodeGewinner, wahlkreis, stimmabgabeart);
    Long anzahlStimmenVorperiodeGewinnerVorperiode = ergebnisDaten.getErststimmenVorperiode(vorperiodeGewinner, wahlkreis, stimmabgabeart);

    return new ParteienwechselSearchResult(wahlkreis, aktuellerGewinner, vorperiodeGewinner, anzahlStimmenGueltigAktuell, anzahlStimmenGueltigVorperiode, anzahlStimmenAktuellerGewinnerAktuell,
        anzahlStimmenAktuellerGewinnerVorperiode, anzahlStimmenVorperiodeGewinnerAktuell, anzahlStimmenVorperiodeGewinnerVorperiode);
  }
}
