package de.ivu.elect.business.auffaelligkeit.control;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import de.ivu.elect.business.auffaelligkeit.entity.AuffaelligkeitBemerkung;
import de.ivu.elect.business.auffaelligkeit.entity.AuffaelligkeitBemerkungUngueltige;
import de.ivu.elect.business.auffaelligkeit.entity.AuffaelligkeitBemerkungVertauschteParteistimmen;
import de.ivu.elect.business.auffaelligkeit.entity.AuffaelligkeitBemerkungWahlbeteiligung;
import de.ivu.elect.business.auffaelligkeit.entity.AuffaelligkeitBermerkungAusreissenBeiEinerStimmenart;
import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gruppe.control.GruppeByRelativerPositionComparator;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.hochrechnung.entity.GebietDaten;
import de.ivu.elect.business.hochrechnung.entity.KoordinatenInformationen;
import de.ivu.elect.business.hochrechnung.entity.ParteiStimmen;
import de.ivu.elect.business.hochrechnung.entity.StimmenhochrechnerGebietAusgabedaten;

public class AuffaelligkeitBemerkungenErzeuger {

  private static final double INFINITY = 1E6;

  public static final double SEHR_KLEINE_ZAHL = 1E-4;

  private EntscheidungsgrenzenFuerAuffaelligkeit entscheidungsgrenzenFuerAuffaelligkeit;

  public AuffaelligkeitBemerkungenErzeuger(EntscheidungsgrenzenFuerAuffaelligkeit entscheidungsgrenzenFuerAuffaelligkeit) {
    this.entscheidungsgrenzenFuerAuffaelligkeit = entscheidungsgrenzenFuerAuffaelligkeit;
  }

  public List<AuffaelligkeitBemerkung> getBemerkungen(GebietDaten<Double> aktuell, GebietDaten<Double> abweichung, GebietDaten<Double> hochgerechnet, KoordinatenInformationen koordinatenInformationen) {
    List<AuffaelligkeitBemerkung> bemerkungen = new ArrayList<>();

    bemerkungen.addAll(getBemerkungenWegenVertauschterStimmen(aktuell, abweichung, hochgerechnet, koordinatenInformationen));
    bemerkungen.addAll(getBemerkungenWegenAusreissensEinerStimmenartFuerPolitischeGruppe(aktuell, abweichung, koordinatenInformationen));
    bemerkungen.addAll(getBemerkungenWegenHoherUngueltige(aktuell, koordinatenInformationen));
    bemerkungen.addAll(getBemerkungenWegenWahlbeteiligung(aktuell, abweichung));

    return bemerkungen;
  }

  public List<AuffaelligkeitBemerkung>  getBemerkungen(StimmenhochrechnerGebietAusgabedaten ausgabedatenVomGebiet) {
    if (ausgabedatenVomGebiet == null || ausgabedatenVomGebiet.getHochgerechnetAnteile() == null) {
      return new ArrayList<>();
    }
    return getBemerkungen(ausgabedatenVomGebiet.getAktuellAnteile(), ausgabedatenVomGebiet.getAbweichungAktuellVonHochgerechnet(), ausgabedatenVomGebiet.getHochgerechnetAnteile(), ausgabedatenVomGebiet.getKoordinatenInformationen());
  }

  private List<AuffaelligkeitBemerkung> getBemerkungenWegenWahlbeteiligung(GebietDaten<Double> aktuellGebietDaten, GebietDaten<Double> abweichung) {
    List<AuffaelligkeitBemerkung> result = new ArrayList<>();
    if (aktuellGebietDaten == null || aktuellGebietDaten.getWahlbeteiligung() == null) {
      return result;
    }
    Double aktuell = aktuellGebietDaten.getWahlbeteiligung().getGesamt();
    Double abweichungWahlbeteiligung = abweichung.getWahlbeteiligung().getGesamt();
    if (entscheidungsgrenzenFuerAuffaelligkeit.isWahlbeteiligungsabweichungAuffaellig(aktuell, abweichungWahlbeteiligung)) {
      result.add(new AuffaelligkeitBemerkungWahlbeteiligung(1, aktuell, abweichungWahlbeteiligung));
    }
    return result;
  }

  private List<AuffaelligkeitBemerkung> getBemerkungenWegenHoherUngueltige(GebietDaten<Double> aktuellGebietDaten, KoordinatenInformationen koordinatenInformationen) {
    List<AuffaelligkeitBemerkung> result = new ArrayList<>();
    if (aktuellGebietDaten == null) {
      return result;
    }
    for (Stimmenart stimmenart: koordinatenInformationen.getAlleStimmenartenSortiert()) {
      Double ungueltigeAktuell = aktuellGebietDaten.getUngueltige().get(stimmenart);
      if (entscheidungsgrenzenFuerAuffaelligkeit.isUngueltigeAnteilAuffaellig(ungueltigeAktuell)) {
        result.add(new AuffaelligkeitBemerkungUngueltige(1, stimmenart, ungueltigeAktuell));
      }
    }

    return result;
  }

  private List<AuffaelligkeitBemerkung> getBemerkungenWegenAusreissensEinerStimmenartFuerPolitischeGruppe(GebietDaten<Double> aktuellGebietDaten, GebietDaten<Double> abweichung, KoordinatenInformationen koordinatenInformationen) {
    // Das heisst z. B., der Direkt-Stimmenanteil einer Partei unterscheidet sich wesentlich von der Listen-Stimmenanteil dieser Partei
    List<AuffaelligkeitBemerkung> result = new ArrayList<>();
    List<Stimmenart> stimmenarten = koordinatenInformationen.getAlleStimmenartenSortiert();

    if (stimmenarten.size() < 2) {
      return result;
    }

    for (PolitischeGruppe politischeGruppe: abweichung.getParteiStimmen().keySet()) {
      result.addAll(testeAusreissenEinerStimmenartFuerPolitischeGruppe(politischeGruppe, stimmenarten, aktuellGebietDaten, abweichung));
    }

    return result;
  }

  private List<AuffaelligkeitBemerkung> testeAusreissenEinerStimmenartFuerPolitischeGruppe(PolitischeGruppe politischeGruppe, List<Stimmenart> stimmenarten, GebietDaten<Double> aktuellGebietDaten, GebietDaten<Double> abweichung) {
    List<AuffaelligkeitBemerkung> result = new ArrayList<>();
    for (int i = 0;i < stimmenarten.size(); i++) {
      for (int j = i + 1;j < stimmenarten.size();j++) {
        Double anteilAktuellErsterStimmenart = getStimmen(aktuellGebietDaten, stimmenarten.get(i), politischeGruppe);
        Double anteilAktuellZweiterStimmenart = getStimmen(aktuellGebietDaten, stimmenarten.get(j), politischeGruppe);

        Double abweichungErsterStimmenart = getStimmen(abweichung, stimmenarten.get(i), politischeGruppe);
        Double abweichungZweiterStimmenart = getStimmen(abweichung, stimmenarten.get(j), politischeGruppe);

        if (entscheidungsgrenzenFuerAuffaelligkeit.isEineStimmenartAuffaelligImVerglichenZuAnderenStimmenartenFuerDieGleichePolitischeGruppe(anteilAktuellErsterStimmenart, anteilAktuellZweiterStimmenart, abweichungErsterStimmenart, abweichungZweiterStimmenart)) {
          result.add(new AuffaelligkeitBermerkungAusreissenBeiEinerStimmenart(1, politischeGruppe, stimmenarten.get(i), stimmenarten.get(j), anteilAktuellErsterStimmenart, anteilAktuellZweiterStimmenart));
        }
      }
    }
    return result;
  }

  private Double getStimmen(GebietDaten<Double> gebietDaten, Stimmenart stimmenart, PolitischeGruppe politischeGruppe) {
    if (gebietDaten != null) {
      ParteiStimmen<Double> parteiStimmen = gebietDaten.getParteiStimmen().get(politischeGruppe);
      if (parteiStimmen != null) {
        return parteiStimmen.getStimmen(stimmenart);
      }
    }
    return null;
  }

  private List<AuffaelligkeitBemerkung> getBemerkungenWegenVertauschterStimmen(GebietDaten<Double> aktuellGebietDaten, GebietDaten<Double> abweichung, GebietDaten<Double> hochgerechnet, KoordinatenInformationen koordinatenInformationen) {
    List<AuffaelligkeitBemerkung> result = new ArrayList<>();

    for (Stimmenart stimmenart: koordinatenInformationen.getAlleStimmenartenSortiert()) {
      result.addAll(testeVertauschteStimmen(stimmenart, aktuellGebietDaten, abweichung, hochgerechnet, koordinatenInformationen));
    }

    return result;
  }

  private List<AuffaelligkeitBemerkung> testeVertauschteStimmen(Stimmenart stimmenart, GebietDaten<Double> aktuellGebietDaten, GebietDaten<Double> abweichung, GebietDaten<Double> hochgerechnet, KoordinatenInformationen koordinatenInformationen) {
    List<AuffaelligkeitBemerkung> result = new ArrayList<>();

    List<PolitischeGruppe> politischeGruppen = new ArrayList<>(abweichung.getParteiStimmen().keySet());
    politischeGruppen.sort(new GruppeByRelativerPositionComparator());

    for (int parteiIndex = 0; parteiIndex < politischeGruppen.size();parteiIndex++) {
      result.addAll(testeVertauschteStimmenFuerPartei (stimmenart, parteiIndex, politischeGruppen, aktuellGebietDaten, abweichung, hochgerechnet));
    }

    return result;
  }

  private List<AuffaelligkeitBemerkung> testeVertauschteStimmenFuerPartei(Stimmenart stimmenart, int parteiIndex, List<PolitischeGruppe> politischeGruppen, GebietDaten<Double> aktuellGebietDaten, GebietDaten<Double> abweichung, GebietDaten<Double> hochgerechnet) {

    Integer parteistimmenIndexFuerBesteAusgleich = findBestAusgleichMitGutemGewinnIndex(stimmenart, parteiIndex, politischeGruppen, aktuellGebietDaten, abweichung, hochgerechnet);

    if (parteistimmenIndexFuerBesteAusgleich == null) {
      return new ArrayList<>();
    }

    AuffaelligkeitBemerkungVertauschteParteistimmen auffaelligkeitBemerkungVertauschteParteistimmen = new AuffaelligkeitBemerkungVertauschteParteistimmen(1,
        stimmenart, politischeGruppen.get(parteiIndex), politischeGruppen.get(parteistimmenIndexFuerBesteAusgleich));

    return Collections.singletonList(auffaelligkeitBemerkungVertauschteParteistimmen);
  }

  private Integer findBestAusgleichMitGutemGewinnIndex(Stimmenart stimmenart, int parteiIndex, List<PolitischeGruppe> politischeGruppen, GebietDaten<Double> aktuellGebietDaten, GebietDaten<Double> abweichung, GebietDaten<Double> hochgerechnet) {
    // returns the index of the party, that would make the election result close to our prediction, if swiched with current party at "parteiIndex".
    Integer result = null;
    double besterGewinn = 0;
    PolitischeGruppe erstePartei = politischeGruppen.get(parteiIndex);
    PolitischeGruppe zweitePartei;
    double tempGewinn;

    for (int nextParteiIndex = parteiIndex + 1; nextParteiIndex < politischeGruppen.size(); nextParteiIndex++) {
      zweitePartei = politischeGruppen.get(nextParteiIndex);

      tempGewinn = entscheidungsgrenzenFuerAuffaelligkeit.getGewichteterGewinnBeiParteiStimmenVertauschung(
          getStimmen(aktuellGebietDaten, stimmenart, erstePartei),
          getStimmen(aktuellGebietDaten, stimmenart, zweitePartei),

          getStimmen(hochgerechnet, stimmenart, erstePartei),
          getStimmen(hochgerechnet, stimmenart, zweitePartei),

          getStimmen(abweichung, stimmenart, erstePartei),
          getStimmen(abweichung, stimmenart, zweitePartei)
      );

      if (tempGewinn > besterGewinn && entscheidungsgrenzenFuerAuffaelligkeit.isGewichteterGewinnBeiReduzierungDerAbweichungGut(tempGewinn)) {
        result = nextParteiIndex;
        besterGewinn = tempGewinn;
      }
    }

    return result;
  }
}
