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

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Collectors;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.auffaelligkeit.control.AuffaelligkeitBemerkungenErzeuger;
import de.ivu.elect.business.auffaelligkeit.control.AuffaelligkeitByAbweichungssummeComparator;
import de.ivu.elect.business.auffaelligkeit.control.AuffaelligkeitNachBenfordTest;
import de.ivu.elect.business.ergebnis.boundary.ErgebnisExtendedDAO;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.control.ErgebnisDatenCreatorExtended;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.hochrechnung.boundary.Hochrechnungsalgorithm;
import de.ivu.elect.business.hochrechnung.boundary.HochrechnungsalgorithmusKonfiguration;
import de.ivu.elect.business.hochrechnung.control.StimmenHochrechnerEingabedatenVonErgebnisdaten;
import de.ivu.elect.business.hochrechnung.entity.HochrechneralgorithmusAusfuehrbarkeitStatus;
import de.ivu.elect.business.hochrechnung.entity.HochrechnungsalgorithmusArt;
import de.ivu.elect.business.hochrechnung.entity.StimmenHochrechnerEingabedaten;
import de.ivu.elect.business.hochrechnung.entity.StimmenhochrechnerAusgabedaten;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeAuffaelligkeit;
import de.ivu.elect.presentation.stimmeingabe.StimmeingabeAuffaelligkeitCache;


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

/**
 *  on 29.03.2016.
 */
public class StimmeingabeAuffaelligkeitSearch {

  private static final Logger LOG = LoggerFactory.getLogger(StimmeingabeAuffaelligkeitSearch.class);

  @Inject
  ErgebnisExtendedDAO ergebnisExtendedDAO;

  @Inject
  WahldurchfuehrungsgebietExtendedDAO wahldurchfuehrungsgebietExtendedDAO;

  @Inject
  private transient ErgebnisDatenCreatorExtended ergebnisDatenCreator;

  public List<StimmeingabeAuffaelligkeit> getSearchResult(StimmeingabeAuffaelligkeitSearchParameters searchParameters, StimmeingabeAuffaelligkeitCache stimmeingabeAuffaelligkeitCache) {
    LOG.info("Lade Auffaelligkeiten");
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = wahldurchfuehrungsgebietExtendedDAO.attach(searchParameters.getWahldurchfuehrungsgebiet());

    if (wahldurchfuehrungsgebiet == null || searchParameters.getHochrechnungsalgorithmusArt() == null) {
      return new ArrayList<>();
    }


    ErgebnisDaten ergebnisdaten = getErgebnisdaten(searchParameters);
    if (ergebnisdaten == null) {
      return new ArrayList<>();
    }

    Set<Wahldurchfuehrungsgebiet> anzuzeigendeGebiete = getAnzuzeigendeGebiete(ergebnisdaten, searchParameters);


    if (stimmeingabeAuffaelligkeitCache.isInitialized()) {
      LOG.info("Lade Auffaelligkeiten von Cache");
    } else {
    StimmenHochrechnerEingabedaten<Long> eingabeDaten = getEingabeDaten(ergebnisdaten);

    StimmenhochrechnerAusgabedaten stimmenhochrechnerAusgabedaten = berechneStimmenhochrechnerAusgabedaten(searchParameters, eingabeDaten, anzuzeigendeGebiete);

    log("Ermittle Auffaelligkeiten nach Benford-Test");
    AuffaelligkeitNachBenfordTest auffaelligkeitNachBenfordTest = new AuffaelligkeitNachBenfordTest(eingabeDaten);
    log("Ermittle Auffaelligkeiten nach Benford-Test - Done");

    AuffaelligkeitBemerkungenErzeuger bemerkungenErzeuger = new AuffaelligkeitBemerkungenErzeuger(searchParameters.getAuffaelligkeitsbemerkungenErzeugerToleranz().getEntscheidungsgrenzenFuerAuffaelligkeit());

      ergebnisdaten.getGebiete().forEach(gebiet -> {
        String erfasser = getErfasser(ergebnisdaten, gebiet);
        stimmeingabeAuffaelligkeitCache.put(gebiet,
                new StimmeingabeAuffaelligkeit(gebiet, stimmenhochrechnerAusgabedaten,
                    auffaelligkeitNachBenfordTest.getAuffaelligkeitBenfordTestErgebnisFuerGebiet(gebiet),
                    bemerkungenErzeuger.getBemerkungen(stimmenhochrechnerAusgabedaten.getAusgabedatenVomGebiet(gebiet)),
                    erfasser
                ));
      });
    }

    List<StimmeingabeAuffaelligkeit> result = anzuzeigendeGebiete.stream()
        .map(stimmeingabeAuffaelligkeitCache::findAuffaelligkeitByGebiet)
        .sorted(new AuffaelligkeitByAbweichungssummeComparator(wahldurchfuehrungsgebiet))
        .collect(Collectors.toList());

    LOG.info("Lade Auffaelligkeiten - done");
    return result;
  }

  private String getErfasser(ErgebnisDaten ergebnisdaten, Gebiet gebiet) {
    if (ergebnisdaten == null || gebiet == null) {
      return null;
    }
    Ergebniseingang letztenGueltigenEingangFuerGebiet = ergebnisdaten.getLetztenGueltigenEingangFuerGebiet(gebiet);
    if (letztenGueltigenEingangFuerGebiet == null) {
      return null;
    }
    return letztenGueltigenEingangFuerGebiet.getErfasser();
  }

  private StimmenhochrechnerAusgabedaten berechneStimmenhochrechnerAusgabedaten(StimmeingabeAuffaelligkeitSearchParameters searchParameters, StimmenHochrechnerEingabedaten<Long> stimmenHochrechnerEingabedaten, Set<Wahldurchfuehrungsgebiet> anzuzeigendeGebiete) {
    LOG.info("berechne StimmenhochrechnerAusgabedaten");

    if (stimmenHochrechnerEingabedaten == null) {
      return new StimmenhochrechnerAusgabedaten(HochrechneralgorithmusAusfuehrbarkeitStatus.NICHT_AUSFUEHRBAR_WEGEN_STIMMENHOCHRECHNEREINGABEDATEN);
    }

    HochrechnungsalgorithmusArt hochrechnungsalgorithmusArt = searchParameters.getHochrechnungsalgorithmusArt();
    HochrechneralgorithmusAusfuehrbarkeitStatus algorithmusAusfuehrbarkeitStatus = hochrechnungsalgorithmusArt
        .getAlgorithmusAusfuehrbarkeitStatus(stimmenHochrechnerEingabedaten, anzuzeigendeGebiete);
    if (!algorithmusAusfuehrbarkeitStatus.isAlgorithmusAusfuehrbar()) {
      return new StimmenhochrechnerAusgabedaten(algorithmusAusfuehrbarkeitStatus);
    }

    log("Ermittle hochgerechnete Stimmen");
    HochrechnungsalgorithmusKonfiguration konfiguration = searchParameters.getHochrechnungsalgorithmusKonfiguration();
    if (konfiguration == null) {
      return new StimmenhochrechnerAusgabedaten(HochrechneralgorithmusAusfuehrbarkeitStatus.NICHT_AUSFUEHRBAR_WEGEN_AUSWAHL_HOCHRECHNUNGSALGORITHMUSKONFIGURATION);
    }

    Hochrechnungsalgorithm hochrechnungsalgorithm = hochrechnungsalgorithmusArt.getHochrechnungsalgorithm(konfiguration);
    if (hochrechnungsalgorithm == null) {
      return new StimmenhochrechnerAusgabedaten(HochrechneralgorithmusAusfuehrbarkeitStatus.NICHT_AUSFUEHRBAR_WEGEN_AUSWAHL_HOCHRECHNUNGSALGORITHMUS);
    }
    StimmenhochrechnerAusgabedaten stimmenhochrechnerAusgabedaten = hochrechnungsalgorithm.ermittleHochgerechneteStimmen(stimmenHochrechnerEingabedaten);
    LOG.info("berechne StimmenhochrechnerAusgabedaten - done");
    return stimmenhochrechnerAusgabedaten;
  }

  private void log(String msg) {
    LOG.debug(msg);
  }

  private ErgebnisDaten getErgebnisdaten(StimmeingabeAuffaelligkeitSearchParameters searchParameters) {
    Ergebnis ergebnis = ergebnisExtendedDAO.attach(searchParameters.getErgebnis());
    if (ergebnis == null) {
      return null;
    }
    return ergebnisDatenCreator.createErgebnisDatenSitzberechnung(ergebnis);
  }

  private Set<Wahldurchfuehrungsgebiet> getAnzuzeigendeGebiete(ErgebnisDaten ergebnisDaten, StimmeingabeAuffaelligkeitSearchParameters searchParameters) {
    Wahldurchfuehrungsgebiet selectedGebiet = wahldurchfuehrungsgebietExtendedDAO.attach(searchParameters.getWahldurchfuehrungsgebiet());
    if (ergebnisDaten == null || selectedGebiet == null) {
      return new HashSet<>();
    }

    Set<Wahldurchfuehrungsgebiet> result = ergebnisDaten.getErfassungsgebieteErgebnisRelevantUnterhalbVon(selectedGebiet)
        .stream().filter(g -> searchParameters.getGebietsartenDerBetrachtetenGebieteInAuffaelligkeitssuche().isAnzuzeigendesGebiet(g))
        .collect(toSet());
    result.addAll(getAlleObergebieteUnterhalbVon(selectedGebiet, ergebnisDaten));
    return result;
  }

  private Set<Wahldurchfuehrungsgebiet> getAlleObergebieteUnterhalbVon(Wahldurchfuehrungsgebiet selectedGebiet, ErgebnisDaten ergebnisDaten) {
    Set<Wahldurchfuehrungsgebiet> result = new HashSet<>();
    Queue<Wahldurchfuehrungsgebiet> queue = new LinkedList<>();
    queue.add(selectedGebiet);
    result.add(selectedGebiet);

    while (!queue.isEmpty()) {
      selectedGebiet = queue.remove();
      List<Wahldurchfuehrungsgebiet> untergebiete = ergebnisDaten.getUntergeordneteGebiete(selectedGebiet);
      if (untergebiete != null && !untergebiete.isEmpty()) {
        result.add(selectedGebiet);
        queue.addAll(ergebnisDaten.getUntergeordneteGebiete(selectedGebiet));
      }
    }

    return result;
  }

  private StimmenHochrechnerEingabedaten<Long> getEingabeDaten(ErgebnisDaten ergebnisDaten) {
    log("Erzeuge StimmenHochrechnerEingabedaten");

    if (ergebnisDaten == null) {
      log("ergebnisDaten == null ");
      return null;
    }

    StimmenHochrechnerEingabedaten<Long> eingabedaten = new StimmenHochrechnerEingabedatenVonErgebnisdaten(ergebnisDaten);
    log("Erzeuge StimmenHochrechnerEingabedaten - done");
    return eingabedaten;
  }
}
