package de.ivu.elect.business.sitzberechnung.control.wahlbeteiligung;

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

import de.ivu.elect.business.sitzberechnung.control.wahlbeteiligung.intern.WahlbeteiligungInObergebiet;
import de.ivu.elect.business.sitzberechnung.control.wahlbeteiligung.intern.WahlbeteiligungsgebietQueries;
import de.ivu.elect.business.sitzberechnung.control.wahlbeteiligung.intern.Wahlbeteiligungsparameter;

public class Wahlbeteiligungsberechner {

  private final BerechnungWahlbeteiligungMittelwertGewichtungsart mittelwertObergebiet;
  private final WahlberechnungContextDaten contextDaten;
  private final WahlbeteiligungsgebietQueries queries;

  public Wahlbeteiligungsberechner(BerechnungWahlbeteiligungMittelwertGewichtungsart mittelwertObergebiet, WahlberechnungContextDaten contextDaten) {
    this.mittelwertObergebiet = mittelwertObergebiet;
    this.contextDaten = contextDaten;
    queries = new WahlbeteiligungsgebietQueries(contextDaten);
  }

  public double berechneWahlbeteiligung(Wahlbeteiligungsgebiet wahlbeteiligungsgebiet) {

    if (isGebietHatNurBriefwahlgebiete(wahlbeteiligungsgebiet, contextDaten)) {
      Double result = queries.getTatsaechlicheRuecklaufquote(wahlbeteiligungsgebiet);
      return result != null? result : 0d;
    }

    if (contextDaten.isKopfzahlwerteVollstaendigErfasst(wahlbeteiligungsgebiet) && !isMussHoehereEbenenChecken(wahlbeteiligungsgebiet)) {
      Wahlbeteiligungsparameter wahlbeteiligungsparameter = new Wahlbeteiligungsparameter();
      berechneWahlbeteiligungWennVollstaendigAusgezaehlt(wahlbeteiligungsgebiet, wahlbeteiligungsparameter);
      return wahlbeteiligungsparameter.getWahlbeteiligung();
    }

    return berechneWahlbeteiligungrekursiv(wahlbeteiligungsgebiet);
  }

  private boolean isGebietHatNurBriefwahlgebiete(Wahlbeteiligungsgebiet wahlbeteiligungsgebiet, WahlberechnungContextDaten contextDaten) {
    if (wahlbeteiligungsgebiet.isUrnenwahlgebiet()) {
      return false;
    }
    if (wahlbeteiligungsgebiet.isBriefwahlgebiet()) {
      return true;
    }
    return contextDaten.getUntergeordneteGebiete(wahlbeteiligungsgebiet).stream().allMatch(kind -> isGebietHatNurBriefwahlgebiete(kind, contextDaten));
  }

  private boolean isMussHoehereEbenenChecken(Wahlbeteiligungsgebiet wahlbeteiligungsgebiet) {
    // Wenn das Wahlbeteiligungsgebiet ein Urnenwahlbezirk oder ein Obergebiet mit nur Urnenwahlbezirken
    // Dann muss man die hoeheren Ebenen auch betrachten, sofern sie ein Briefwahlgebiet haben.
    return (wahlbeteiligungsgebiet.isUrnenwahlgebiet() || isObergebietOhneBriefwahlgebiete(wahlbeteiligungsgebiet))
        && isBriefwahlgebietInHoeherenRelevantenEbenenVorhanden(wahlbeteiligungsgebiet);
  }

  private boolean isObergebietOhneBriefwahlgebiete(Wahlbeteiligungsgebiet wahlbeteiligungsgebiet) {
    return contextDaten.isUntergebieteVorhanden(wahlbeteiligungsgebiet) && queries.isBriefwahlgebietInUntergebietenNotPresentRekursiv(wahlbeteiligungsgebiet);
  }

  private boolean isBriefwahlgebietInHoeherenRelevantenEbenenVorhanden(Wahlbeteiligungsgebiet wahlbeteiligungsgebiet) {
    do {
      wahlbeteiligungsgebiet = contextDaten.getUebergeordnetesGebiet(wahlbeteiligungsgebiet);
    } while (wahlbeteiligungsgebiet != null && !wahlbeteiligungsgebiet.isWahlgebiet() && isObergebietOhneBriefwahlgebiete(wahlbeteiligungsgebiet));
    return wahlbeteiligungsgebiet != null && !isObergebietOhneBriefwahlgebiete(wahlbeteiligungsgebiet);
  }

  private void berechneWahlbeteiligungWennVollstaendigAusgezaehlt(Wahlbeteiligungsgebiet wahlbeteiligungsgebiet, Wahlbeteiligungsparameter wahlbeteiligungsparameter) {
    if (contextDaten.isErfassungsgebiet(wahlbeteiligungsgebiet)) {
      wahlbeteiligungsparameter.addWahlberechtigte(contextDaten.getAnzahlWahlberechtigteInsgesamt(wahlbeteiligungsgebiet));
      wahlbeteiligungsparameter.addWahlberechtigteAusgezaehlt(contextDaten.getAnzahlWahlberechtigteInsgesamt(wahlbeteiligungsgebiet));
      wahlbeteiligungsparameter.addLokalwaehler(contextDaten.getAnzahlWaehlerOhneWahlschein(wahlbeteiligungsgebiet));
      wahlbeteiligungsparameter.addBriefwaehler(contextDaten.getAnzahlWaehlerMitWahlschein(wahlbeteiligungsgebiet));
    }
    if (contextDaten.isUntergebieteVorhanden(wahlbeteiligungsgebiet)) {
      for (Wahlbeteiligungsgebiet untergebiet : contextDaten.getUntergeordneteGebiete(wahlbeteiligungsgebiet)) {
        berechneWahlbeteiligungWennVollstaendigAusgezaehlt(untergebiet, wahlbeteiligungsparameter);
      }
    }
  }

  private double berechneWahlbeteiligungrekursiv(Wahlbeteiligungsgebiet wahlbeteiligungsgebiet) {
    Wahlbeteiligungsparameter wahlbeteiligungsparameter = new Wahlbeteiligungsparameter();
    List<WahlbeteiligungInObergebiet> wahlbeteiligungInDenUntergeordnetenObergebieten = Collections.emptyList();
    // Dies enthaelt die summe der A- und B-Werte von den direkt untergeordneten Erfassungsgebiete.

    if (contextDaten.isErfassungsgebiet(wahlbeteiligungsgebiet)) {
      addErgebnisVonErfassungsgebiet(wahlbeteiligungsgebiet, wahlbeteiligungsparameter);
    }
    if (contextDaten.isUntergebieteVorhanden(wahlbeteiligungsgebiet)) {
      berechneWahlbeteiligungVonDenUntergeordnetenErfassungsgebieten(wahlbeteiligungsgebiet, wahlbeteiligungsparameter);
      wahlbeteiligungInDenUntergeordnetenObergebieten = getWahlbeteiligungInDenUntergeordnetenObergebieten(wahlbeteiligungsgebiet);
    }

    return mittelwertObergebiet.berechneMittelwert(wahlbeteiligungsparameter, wahlbeteiligungInDenUntergeordnetenObergebieten, queries);
  }

  private void addErgebnisVonErfassungsgebiet(Wahlbeteiligungsgebiet wahlbeteiligungsgebiet, Wahlbeteiligungsparameter parameter) {
    if (!wahlbeteiligungsgebiet.isBriefwahlgebiet()) {
      parameter.addWahlberechtigte(contextDaten.getAnzahlWahlberechtigteInsgesamt(wahlbeteiligungsgebiet));
      if (contextDaten.isKopfzahlwerteVollstaendigErfasst(wahlbeteiligungsgebiet)) {
        addErgebnisVonAusgezaehltesErfassungsgebiet(wahlbeteiligungsgebiet, parameter);
      }
    }
    // Ergebnis von Briefwahlgebieten wird durch Beruecksichtigen der tatsaechlichen Ruecklaufquote mitgerechnet.
  }

  private void addErgebnisVonAusgezaehltesErfassungsgebiet(Wahlbeteiligungsgebiet erfassungsgebiet, Wahlbeteiligungsparameter wahlbeteiligungsparameter) {
    wahlbeteiligungsparameter.addLokalwaehler(contextDaten.getAnzahlWaehlerOhneWahlschein(erfassungsgebiet));
    wahlbeteiligungsparameter.addWahlberechtigteAusgezaehlt(contextDaten.getAnzahlWahlberechtigteInsgesamt(erfassungsgebiet));
    addAnzahlVonErwartetenBriefwaehlern(erfassungsgebiet, wahlbeteiligungsparameter);
  }

  private void addAnzahlVonErwartetenBriefwaehlern(Wahlbeteiligungsgebiet erfassungsgebiet, Wahlbeteiligungsparameter wahlbeteiligungsparameter) {
    long anzahlWahlberechtigteMitWahlschein = contextDaten.getAnzahlWahlberechtigteMitWahlschein(erfassungsgebiet);
    if (anzahlWahlberechtigteMitWahlschein != 0) {
      Double ruecklaufquote = queries.getTatsaechlicheRuecklaufquote(erfassungsgebiet);
      if (ruecklaufquote == null) {
        ruecklaufquote = contextDaten.getRuecklaufquote(erfassungsgebiet);
      }
      wahlbeteiligungsparameter.addErwarteteBriefwaehler(ruecklaufquote, anzahlWahlberechtigteMitWahlschein);
    }
  }

  private void berechneWahlbeteiligungVonDenUntergeordnetenErfassungsgebieten(Wahlbeteiligungsgebiet obergebiet, Wahlbeteiligungsparameter wahlbeteiligungsparameter) {
    // addiere A und B werte von den direkt untergeordneten Gebieten zu der Wahlbeteiligungsparameter
    for (Wahlbeteiligungsgebiet gebiet : contextDaten.getUntergeordneteGebiete(obergebiet)) {
      if (contextDaten.isErfassungsgebiet(gebiet)) {
        addErgebnisVonErfassungsgebiet(gebiet, wahlbeteiligungsparameter);
      }
    }
  }

  private List<WahlbeteiligungInObergebiet> getWahlbeteiligungInDenUntergeordnetenObergebieten(Wahlbeteiligungsgebiet obergebiet) {
    List<WahlbeteiligungInObergebiet> wahlbeteiligungInDenUntergeordnetenObergebieten = new ArrayList<>();
    for (Wahlbeteiligungsgebiet gebiet : contextDaten.getUntergeordneteGebiete(obergebiet)) {
      if (contextDaten.isUntergebieteVorhanden(gebiet)) {
        wahlbeteiligungInDenUntergeordnetenObergebieten.add(new WahlbeteiligungInObergebiet(gebiet, berechneWahlbeteiligungrekursiv(gebiet)));
      }
    }
    return wahlbeteiligungInDenUntergeordnetenObergebieten;
  }

}