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

import org.apache.commons.math3.distribution.NormalDistribution;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.hochrechnung.entity.WahlteilnehmerAnzahl;
import de.ivu.elect.business.hochrechnung.entity.ZufaelligeAnzahlWahlberechtigteNormalverteilungKonfiguration;

public class ZufaelligeAnzahlWahlberechtigteNormalverteilungGenerator extends ZufaelligeAnzahlWahlberechtigteGenerator  {
  private ZufaelligeAnzahlWahlberechtigteNormalverteilungKonfiguration konfiguration;

  public ZufaelligeAnzahlWahlberechtigteNormalverteilungGenerator(ZufaelligeAnzahlWahlberechtigteNormalverteilungKonfiguration konfiguration) {
    this.konfiguration = konfiguration;
  }

  @Override
  public WahlteilnehmerAnzahl getZufaelligeAnzahlWahlberechtigteFuerUrnengebiet() {
    long anzahlGesamt = getAnzahlGesamtAmUrnenwahlgebiet();
    long anzahlMitWahlschein = getAnzahlMitWahlschein(anzahlGesamt);
    long anzahlNichtImWaehlerverzeichnis = getAnzahlNichtImWaehlerverzeichnis(anzahlGesamt);
    return getAnzahlWahlberechtigteFuerGebiet(anzahlGesamt, anzahlMitWahlschein, anzahlNichtImWaehlerverzeichnis);
  }

  @Override
  public WahlteilnehmerAnzahl getPseudoAnzahlWahlberechtigteFuerBriefgebiet(WahlteilnehmerAnzahl wahlberechtigteVomUebergeordnetenGebiet, long anzahlVonBriefgebietenUntermUebergeordnetenGebiet) {
    long anzahlMitWahlschein = anzahlVonBriefgebietenUntermUebergeordnetenGebiet != 0 && wahlberechtigteVomUebergeordnetenGebiet != null? wahlberechtigteVomUebergeordnetenGebiet.getMitWahlscheinNotNull() / anzahlVonBriefgebietenUntermUebergeordnetenGebiet : 0;
    return getAnzahlWahlberechtigteFuerGebiet(anzahlMitWahlschein, anzahlMitWahlschein, 0);
  }

  @Override
  public WahlteilnehmerAnzahl getZufaelligeAnzahlWahlberechtigteFuerEinUebergeordnetesGebietMitNurBriefwahlgebieteAlsUntergeordneteGebiete(Long anzahlUntergeordneteBriefgebieteVomGebiet) {
    long anzahlGesamt = getAnzahlGesamtAmUrnenwahlgebiet() * anzahlUntergeordneteBriefgebieteVomGebiet;
    return getAnzahlWahlberechtigteFuerGebiet(anzahlGesamt, anzahlGesamt, 0);
  }

  private WahlteilnehmerAnzahl getAnzahlWahlberechtigteFuerGebiet(long anzahlGesamt, long anzahlMitWahlschein, long anzahlNichtImWaehlerverzeichnis) {
    long anzahlOhneWahlschein = getAnzahlOhneWahlschein(anzahlGesamt, anzahlMitWahlschein, anzahlNichtImWaehlerverzeichnis);
    WahlteilnehmerAnzahl result = new WahlteilnehmerAnzahl();
    result.setAnzahl(Stimmenart.GESAMT, anzahlGesamt);
    result.setAnzahl(Stimmenart.OHNE_WAHLSCHEIN, anzahlOhneWahlschein);
    result.setAnzahl(Stimmenart.MIT_WAHLSCHEIN, anzahlMitWahlschein);
    result.setAnzahl(Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS, anzahlNichtImWaehlerverzeichnis);
    return result;
  }

  private long getAnzahlOhneWahlschein(long anzahlGesamt, long anzahlMitWahlschein, long anzahlNichtImWaehlerverzeichnis) {
    return trimNegativeNumbers(anzahlGesamt - anzahlMitWahlschein - anzahlNichtImWaehlerverzeichnis);
  }

  private long getAnzahlNichtImWaehlerverzeichnis(long anzahlGesamt) {
    NormalDistribution normalDistributionFuerWahlberechtigteNichtImWaehlerverzeichnis = konfiguration
        .getNormalDistributionFuerWahlberechtigteAmUrnenwahlgebiet(Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS);
    double anteil = trimAnteil(normalDistributionFuerWahlberechtigteNichtImWaehlerverzeichnis.sample());
    long resultAlsLong = Math.round(anteil * anzahlGesamt);
    return trimNegativeNumbers(resultAlsLong);
  }

  private double trimAnteil(double anteil) {
    if (anteil >= 0 && anteil <= 1) {
      return anteil;
    }
    return anteil > 1? 1 : 0;
  }

  private long getAnzahlMitWahlschein(long anzahlGesamt) {
    NormalDistribution normalDistributionFuerWahlberechtigteMitWahlschein = konfiguration
        .getNormalDistributionFuerWahlberechtigteAmUrnenwahlgebiet(Stimmenart.MIT_WAHLSCHEIN);
    double anteil = trimAnteil(normalDistributionFuerWahlberechtigteMitWahlschein.sample()) + konfiguration.getWahlberechtigteMitWahlscheinUrnenwahlMinPreventigZeros();
    long resultAlsLong = Math.round(anteil * anzahlGesamt);
    return trimNegativeNumbers(resultAlsLong);
  }

  private long getAnzahlGesamtAmUrnenwahlgebiet() {
    NormalDistribution normalDistributionFuerAnzahlGesamt = konfiguration.getNormalDistributionFuerWahlberechtigteAmUrnenwahlgebiet(Stimmenart.GESAMT);
    double result = normalDistributionFuerAnzahlGesamt.sample();
    long resultAlsLong = Math.round(result);
    return trimNegativeNumbers(resultAlsLong) + konfiguration.getWahlberechtigteGesamtUrnenwahlMinPreventigZeros();
  }

  private long trimNegativeNumbers(long number) {
    return Math.max(0, number);
  }
}
