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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import de.ivu.elect.business.auffaelligkeit.entity.AuffaelligkeitBenfordTestChartErgebnis;
import de.ivu.elect.business.auffaelligkeit.entity.BetrachteteStellenImAuffaelligkeitstestNachBenford;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.hochrechnung.entity.GebietDaten;
import de.ivu.elect.business.hochrechnung.entity.ParteiStimmen;
import de.ivu.elect.business.hochrechnung.entity.ReferendumFrage;
import de.ivu.elect.business.hochrechnung.entity.StimmenHochrechnerEingabedaten;


import static java.util.Arrays.asList;

public class AuffaelligkeitNachBenfordTest{

  // P. S. Stellen nummerierung faengt immer mit 1 an.

  private StimmenHochrechnerEingabedaten<Long> eingabedaten;
  private Map<Gebiet, List<AuffaelligkeitBenfordTestChartErgebnis>> benfordTestErgebnis;

  private static double[][] benfordsVerteilungDerZiffernNachStellenAnzahl = initBenfordsVerteilungDerZiffernNachStellenanzahl();

  public AuffaelligkeitNachBenfordTest(StimmenHochrechnerEingabedaten<Long> eingabedaten) {
    this.eingabedaten = eingabedaten;
    List<BetrachteteStellenImAuffaelligkeitstestNachBenford> betrachteteStellenImAuffaelligkeitstestNachBenfords = getBetrachteteStellenImAuffaelligkeitstestNachBenfordsSortiert();
    this.benfordTestErgebnis = fuehreBenfordTestAus(betrachteteStellenImAuffaelligkeitstestNachBenfords);
  }

  private List<BetrachteteStellenImAuffaelligkeitstestNachBenford> getBetrachteteStellenImAuffaelligkeitstestNachBenfordsSortiert() {
    return asList(BetrachteteStellenImAuffaelligkeitstestNachBenford.ALLE_STELLEN,
        BetrachteteStellenImAuffaelligkeitstestNachBenford.ZWEITE_STELLE,
        BetrachteteStellenImAuffaelligkeitstestNachBenford.ERSTE_STELLE,
        BetrachteteStellenImAuffaelligkeitstestNachBenford.LETZTE_STELLE);
  }

  private static double[][] initBenfordsVerteilungDerZiffernNachStellenanzahl() {
    double [][] result = new double[50][10];
    for (double[] row : result) {
      Arrays.fill(row, 0);
    }

    int maximaleStellenanzahl = 8;

    for (int stelle = 1; stelle <= maximaleStellenanzahl; stelle++) {
      for (int ziffer = 0; ziffer <= 9; ziffer++) {
        result[stelle][ziffer] = berechneWahrscheinlichkeitNachBenford(stelle, ziffer);
      }
    }
    return result;
  }

  private static double berechneWahrscheinlichkeitNachBenford(int stelle, int ziffer) {
    if (stelle == 1) {
      if (ziffer == 0) {
        return 0;
      } else {
        return Math.log10(1 + 1.0 / ziffer);
      }
    }

    double summe = 0;
    int summeZaehlerStartwert = (int) Math.pow(10, stelle - 2); // nach der Formel von Benford
    int summeZaehlerEndwert = summeZaehlerStartwert * 10; // nach der Formel von Benford

    for (int i = summeZaehlerStartwert; i < summeZaehlerEndwert; i++) {
      summe += Math.log10(1 + 1.0 / (10 * i + ziffer));
    }
    return summe;
  }

  public List<AuffaelligkeitBenfordTestChartErgebnis> getAuffaelligkeitBenfordTestErgebnisFuerGebiet (Gebiet gebiet) {
    return benfordTestErgebnis.getOrDefault(gebiet, new ArrayList<>());
  }

  private Map<Gebiet, List<AuffaelligkeitBenfordTestChartErgebnis>> fuehreBenfordTestAus(List<BetrachteteStellenImAuffaelligkeitstestNachBenford> betrachteteStellenImAuffaelligkeitstestNachBenfords) {
    Map<Gebiet, List<AuffaelligkeitBenfordTestChartErgebnis>> result = new HashMap<>();
    Set<Gebiet> allegebiete = eingabedaten.getAktuelleWahl().keySet();
    Map<Gebiet, Set<Gebiet>> untergeordneteGebieteNachGebiet = berechneUntergeordneteGebiete(allegebiete);
    for (Gebiet gebiet: allegebiete) {
      result.put(gebiet, fuehreBenfordTestAufGebietAus(gebiet, untergeordneteGebieteNachGebiet, betrachteteStellenImAuffaelligkeitstestNachBenfords));
    }
    return result;
  }

  private Map<Gebiet, Set<Gebiet>> berechneUntergeordneteGebiete(Collection<Gebiet> alleGebiete) {
    Map<Gebiet, Set<Gebiet>> result = new HashMap<>();
    for (Gebiet gebiet: alleGebiete) {
      Gebiet uebergeordnetesGebiet = gebiet.getUebergeordnetesGebiet();
      if (uebergeordnetesGebiet == null) {
        continue;
      }
      result.putIfAbsent(uebergeordnetesGebiet, new HashSet<>());
      result.get(uebergeordnetesGebiet).add(gebiet);
    }
    alleGebiete.forEach(gebiet -> result.putIfAbsent(gebiet, new HashSet<>()));
    return result;
  }

  private List<AuffaelligkeitBenfordTestChartErgebnis> fuehreBenfordTestAufGebietAus(Gebiet gebiet, Map<Gebiet, Set<Gebiet>> untergeordneteGebieteNachGebiet, List<BetrachteteStellenImAuffaelligkeitstestNachBenford> betrachteteStellenImAuffaelligkeitstestNachBenfords) {
    List<Long> zahlenVomGebiet = getZahlenVomGebiet(gebiet, untergeordneteGebieteNachGebiet);
    List<AuffaelligkeitBenfordTestChartErgebnis> result = new ArrayList<>();
    for (BetrachteteStellenImAuffaelligkeitstestNachBenford stelle: betrachteteStellenImAuffaelligkeitstestNachBenfords) {
      result.add(fuehreBenfordTestFuerBestimmteStellen (stelle, zahlenVomGebiet));
    }
    return result;
  }

  private AuffaelligkeitBenfordTestChartErgebnis fuehreBenfordTestFuerBestimmteStellen(BetrachteteStellenImAuffaelligkeitstestNachBenford betrachteteStellen, List<Long> zahlen) {
    List<Long> untersuchteZahlen = new ArrayList<>();
    List<Long> nichtBetrachteZahlen = new ArrayList<>();
    for (Long zahl: zahlen) {
      if (betrachteteStellen.isSollZahlBetrachtetWerden(zahl)) {
        untersuchteZahlen.add(zahl);
      } else {
        nichtBetrachteZahlen.add(zahl);
      }
    }

    long[] tatsaechlicheHaeufigkeitDerZiffern = betrachteteStellen.getTatsaechlicheHaeufigkeitDerZiffern(untersuchteZahlen);
    double[] benfordsHaeufigkeitDerZiffern = betrachteteStellen.getBenfordsHaeufigkeitDerZiffern(untersuchteZahlen, benfordsVerteilungDerZiffernNachStellenAnzahl);

    return new AuffaelligkeitBenfordTestChartErgebnis(betrachteteStellen, untersuchteZahlen, nichtBetrachteZahlen,
        tatsaechlicheHaeufigkeitDerZiffern, benfordsHaeufigkeitDerZiffern);
  }

  private List<Long> getZahlenVomGebiet(Gebiet gebiet, Map<Gebiet, Set<Gebiet>> untergeordneteGebieteNachGebiet) {
    if (gebiet.isErfassungsgebiet()) {
      return getZahlenVonErfassungsgebiet(gebiet);
    } else {
      return getZahlenVonEinemUebergeordnetenGebiet(gebiet, untergeordneteGebieteNachGebiet);
    }
  }

  private List<Long> getZahlenVonEinemUebergeordnetenGebiet(Gebiet gebiet, Map<Gebiet, Set<Gebiet>> untergeordneteGebieteNachGebiet) {
    List<Long> result = new ArrayList<>();
    Set<Gebiet> untergeordneteGebiete = untergeordneteGebieteNachGebiet.get(gebiet);
    for (Gebiet g: untergeordneteGebiete) {
      result.addAll(getZahlenVomGebiet(g, untergeordneteGebieteNachGebiet));
    }
    return result;
  }

  private List<Long> getZahlenVonErfassungsgebiet(Gebiet gebiet) {
    List<Long> result = new ArrayList<>();
    GebietDaten<Long> gebietDaten = eingabedaten.getAktuelleWahl().get(gebiet);
    if (gebietDaten != null) {
      result.addAll(gebietDaten.getGueltige().values());
      //result.addAll(gebietDaten.getUngueltige().values());
      result.addAll(extractZahlenVonParteiStimmen(gebietDaten.getParteiStimmen()));

      result.addAll(extractZahlenVonReferendenStimmen(gebietDaten.getReferendumFragen()));

      //Zum Testen: result.addAll(extractZahlenVonAusgewaehltenParteiStimmen(gebietDaten.getParteiStimmen()));

    }
    return result;
  }

  private Collection<Long> extractZahlenVonReferendenStimmen(Map<Gruppe, ReferendumFrage<Long>> referendumFragen) {
    List<Long> result = new ArrayList<>();
    if (referendumFragen != null) {
      for (ReferendumFrage<Long> referendumFrage: referendumFragen.values()) {
        result.addAll(extractZahlenVonReferendumFrage(referendumFrage));
      }
    }
    return result;
  }

  private Collection<Long> extractZahlenVonReferendumFrage(ReferendumFrage<Long> referendumFrage) {
    Collection<Long> result = new ArrayList<>();
    if (referendumFrage != null && referendumFrage.getStimmenPerReferendumOption() != null) {
      result.addAll(referendumFrage.getStimmenPerReferendumOption().values());
    }
    return result;
  }

  private Collection<Long> extractZahlenVonParteiStimmen(Map<PolitischeGruppe, ParteiStimmen<Long>> parteiStimmen) {
    List<Long> result = new ArrayList<>();
    if (parteiStimmen != null) {
      for (ParteiStimmen<Long> parteiStimme: parteiStimmen.values()) {
        result.addAll(extractZahlenVonParteiStimme(parteiStimme));
      }
    }
    return result;
  }

  private Collection<Long> extractZahlenVonParteiStimme(ParteiStimmen<Long> parteiStimme) {
    Collection<Long> result = new ArrayList<>();
    if (parteiStimme != null && parteiStimme.getStimmen() != null) {
      result.addAll(parteiStimme.getStimmen().values());
    }
    if (parteiStimme != null && parteiStimme.getListenkandidaturStimmen() != null) {
      result.addAll(parteiStimme.getListenkandidaturStimmen().values());
    }
    return result;
  }

}
