
package de.ivu.elect.business.stimmeingabe.control.plausi;

import java.util.Collection;

import javax.inject.Inject;

import de.ivu.elect.business.CurrentCustomer;
import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.NlSystemGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppeEigenschaften;
import de.ivu.elect.business.security.Customer;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebniseingangKonfiguration;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.NlStimmeingabeKopfzahlErfassenModel;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeGruppeStimmenErfassenModel;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeKopfzahlErfassenModel;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiFehlerart;
import de.ivu.elect.business.wahl.entity.NlWahlart;

public abstract class AbstractNlKopfzahlPlausiBuilder extends AbstractKopfzahlPlausiBuilder {

  @Inject
  private transient CurrentCustomer currentCustomer;

  @Inject
  private transient SystemGruppeEigenschaften systemGruppeEigenschaften;

  @Inject
  private transient ErgebniseingangKonfiguration ergebniseingangKonfiguration;

  protected AbstractNlKopfzahlPlausiBuilder() {
    super();
  }

  protected ErgebniseingangKonfiguration getErgebniseingangKonfiguration() {
    return ergebniseingangKonfiguration;
  }

  public abstract boolean containsAnyStimmeingabefehlerEndstufeSpezifisch(Ergebniseingang ergebniseingang);

  public void fillKopfzahlPlausis(StimmeingabePlausiBuilderContext context, NlStimmeingabeKopfzahlErfassenModel kopfzahlErfassenModel,
      StimmeingabeGruppeStimmenErfassenModel gruppeStimmenErfassenModel) {
    fillKopfzahlPlausisWahlberechtigte(context, kopfzahlErfassenModel, gruppeStimmenErfassenModel);

    fillKopfzahlPlausisWaehler(context, kopfzahlErfassenModel, gruppeStimmenErfassenModel);

    fillKopfzahlPlausisStimmzettel(context, kopfzahlErfassenModel, gruppeStimmenErfassenModel);

    fillKopfzahlPlausisUnspezifisch(context, kopfzahlErfassenModel, gruppeStimmenErfassenModel, findKopfzahlPlausiUnspezifischStimmeingabe(context.getErgebniseingang()));

    logStimmeingabePlausis(context, kopfzahlErfassenModel.getAllStimmeingaben());
  }

  protected void fillKopfzahlPlausisWahlberechtigte(StimmeingabePlausiBuilderContext context, NlStimmeingabeKopfzahlErfassenModel kopfzahlErfassenModel,
      StimmeingabeGruppeStimmenErfassenModel gruppeStimmenErfassenModel) {
    fillPruefkennzeichenWahlberechtigtePlausis(kopfzahlErfassenModel, context);

    fillUrnenwahlbezirkWahlberechtigteAnzahlGleichWahlberechtigteAnzahlAmGebietPlausis(kopfzahlErfassenModel, context);

    addUrnenwahlbezirkWahlberechtigteGesamtGroesserNullPlausi(kopfzahlErfassenModel, context, StimmeingabePlausiFehlerart.WARNING); // ELECTVAPP-37, #13, Anzahl Wahlberechtigte > 0
  }

  protected void fillKopfzahlPlausisWaehler(StimmeingabePlausiBuilderContext context, NlStimmeingabeKopfzahlErfassenModel kopfzahlErfassenModel,
      StimmeingabeGruppeStimmenErfassenModel gruppeStimmenErfassenModel) {
    fillDefaultUrnenwahlbezirkKopfzahlSummenWaehlerPlausis(kopfzahlErfassenModel, context,
        NlWahlart.EERSTE_KAMER.equalsWahlart(context.getWahl()) || isGsb(context) ? StimmeingabePlausiFehlerart.ERROR : StimmeingabePlausiFehlerart.WARNING); // ELECTVAPP-37, #2, D = A + B + C, ELECT-16067 -> NlWahlart#EERSTE_KAMER IMMER ERROR, ELECTVAPP-2061 -> GSB = ERROR, HSB/CSB = WARNING
  }

  protected void fillKopfzahlPlausisStimmzettel(StimmeingabePlausiBuilderContext context, NlStimmeingabeKopfzahlErfassenModel kopfzahlErfassenModel,
      StimmeingabeGruppeStimmenErfassenModel gruppeStimmenErfassenModel) {
    fillKopfzahlPlausisStimmzettelEingenommen(context, kopfzahlErfassenModel, gruppeStimmenErfassenModel);
    fillKopfzahlPlausisStimmzettelEingenommenAbweichend(context, kopfzahlErfassenModel, gruppeStimmenErfassenModel);
  }

  protected void fillKopfzahlPlausisStimmzettelEingenommen(StimmeingabePlausiBuilderContext context, NlStimmeingabeKopfzahlErfassenModel kopfzahlErfassenModel,
      StimmeingabeGruppeStimmenErfassenModel gruppeStimmenErfassenModel) {
    kopfzahlErfassenModel.getStimmzettelEingenommenGesamtStimmeingabe().ifPresent(stimmzetteleingabeStimmzettelEingenommenGesamt -> {
      addPlausi(stimmzetteleingabeStimmzettelEingenommenGesamt, // ELECTVAPP-37, #7, Anzahl Eingenommene Stimmzettel > 0
          new StimmeingabeAnzahlGroesserAnzahlPlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_STIMMZETTEL_GESAMT,
              findStimmeingabePlausiKonfigurationOrDefaults(context, AbstractNlStimmeingabePlausiDefinitions.STIMMZETTEL_EINGENOMMEN_GESAMT_ANZAHL_GROESSER_NULL, StimmeingabePlausiFehlerart.WARNING),
              getMessages()),
          context);
    });
  }

  protected void fillKopfzahlPlausisStimmzettelEingenommenAbweichend(StimmeingabePlausiBuilderContext context, NlStimmeingabeKopfzahlErfassenModel kopfzahlErfassenModel,
      StimmeingabeGruppeStimmenErfassenModel gruppeStimmenErfassenModel) {
    kopfzahlErfassenModel.getStimmzettelEingenommenGesamtStimmeingabe().ifPresent(stimmzetteleingabeStimmzettelEingenommenGesamt -> {
      Collection<Stimmeingabe> stimmeingabenWaehlerGesamt = kopfzahlErfassenModel.getWaehlerGesamtStimmeingaben();

      Stimmeingabe stimmzetteleingabeStimmzettelEingenommenAbweichendZuwenig = kopfzahlErfassenModel.getStimmzettelEingenommenAbweichendZuwenigStimmeingabe().orElse(null);
      Stimmeingabe stimmzetteleingabeStimmzettelEingenommenAbweichendZuviel = kopfzahlErfassenModel.getStimmzettelEingenommenAbweichendZuvielStimmeingabe().orElse(null);

      Collection<Stimmeingabe> stimmzetteleingabenStimmzettelEingenommenAbweichendGrund = kopfzahlErfassenModel.getStimmzettelEingenommenAbweichendGrundStimmeingaben();

      addPlausi(stimmzetteleingabeStimmzettelEingenommenGesamt, // ELECTVAPP-37, #8 (Stimmeingaben für Stimmzettel eingenommen abweichend vorhanden) && ELECTVAPP-37, #16 (Stimmeingaben für Stimmzettel eingenommen abweichend NICHT vorhanden, nur Eerste Kamer)
          new NlStimmzettelEingenommenGesamtAnzahlGleichWaehlerGesamtAnzahlPlausi(stimmeingabenWaehlerGesamt, stimmzetteleingabeStimmzettelEingenommenAbweichendZuwenig,
              stimmzetteleingabeStimmzettelEingenommenAbweichendZuviel, stimmzetteleingabenStimmzettelEingenommenAbweichendGrund, findStimmeingabePlausiKonfigurationOrDefaults(context,
                  AbstractNlStimmeingabePlausiDefinitions.STIMMZETTEL_EINGENOMMEN_GESAMT_ANZAHL_GLEICH_WAEHLER_GESAMT_ANZAHL, StimmeingabePlausiFehlerart.WARNING),
              getMessages()),
          context);

      if (stimmzetteleingabeStimmzettelEingenommenAbweichendZuviel != null || stimmzetteleingabeStimmzettelEingenommenAbweichendZuwenig != null) {
        addPlausi(stimmzetteleingabeStimmzettelEingenommenGesamt, // ELECTVAPP-93
            new NlStimmzettelEingenommenGesamtAnzahlUngleichWaehlerGesamtAnzahlPlausi(stimmeingabenWaehlerGesamt, stimmzetteleingabeStimmzettelEingenommenAbweichendZuwenig,
                stimmzetteleingabeStimmzettelEingenommenAbweichendZuviel, stimmzetteleingabenStimmzettelEingenommenAbweichendGrund, findStimmeingabePlausiKonfigurationOrDefaults(context,
                    AbstractNlStimmeingabePlausiDefinitions.STIMMZETTEL_EINGENOMMEN_GESAMT_ANZAHL_UNGLEICH_WAEHLER_GESAMT_ANZAHL, StimmeingabePlausiFehlerart.WARNING),
                getMessages()),
            context);

        // ELECTVAPP-2147
        //  List<Stimmeingabe> stimmzetteleingabenStimmzettelEingenommenAbweichendGrundUniversalSummanden = stimmzetteleingabenStimmzettelEingenommenAbweichendGrund.stream() //
        //      .filter(stimmeingabe -> NlVoteType.valueOf(systemGruppeEigenschaften, stimmeingabe).map(NlVoteType::isStimmzettelEingenommenAbweichendGrundUniversal).orElse(false)) //
        //      .collect(toList());

        StimmeingabePlausiFehlerart stimmzettelEingenommenAbweichendFehlerart = isGsb(context) ? StimmeingabePlausiFehlerart.ERROR : StimmeingabePlausiFehlerart.WARNING;

        if (stimmzetteleingabeStimmzettelEingenommenAbweichendZuviel != null) {
          addPlausi(stimmzetteleingabeStimmzettelEingenommenAbweichendZuviel, // ELECTVAPP-37, #10
              new NlStimmzettelEingenommenAbweichendZuvielAnzahlGleichStimmzettelEingenommenGesamtAnzahlMinusWaehlerGesamtAnzahlPlausi(stimmeingabenWaehlerGesamt,
                  stimmzetteleingabeStimmzettelEingenommenGesamt,
                  findStimmeingabePlausiKonfigurationOrDefaults(context,
                      AbstractNlStimmeingabePlausiDefinitions.STIMMZETTEL_EINGENOMMEN_ABWEICHEND_ZUVIEL_ANZAHL_GLEICH_STIMMZETTEL_EINGENOMMEN_GESAMT_ANZAHL_MINUS_WAEHLER_GESAMT_ANZAHL,
                      stimmzettelEingenommenAbweichendFehlerart // ELECTVAPP-2063 -> GSB = ERROR, HSB/CSB = WARNING
          ), getMessages()), context);

          // ELECTVAPP-2147
          //  if (isGsb(context)) { // Auf höheren Ebenen sind Werte kumuliert -> Gründe zuviel/zuwenig nicht mehr unterscheidbar...
          //    List<Stimmeingabe> stimmzetteleingabenStimmzettelEingenommenAbweichendGrundZuvielSummanden = stimmzetteleingabenStimmzettelEingenommenAbweichendGrund.stream() //
          //        .filter(stimmeingabe -> NlVoteType.valueOf(systemGruppeEigenschaften, stimmeingabe).map(NlVoteType::isStimmzettelEingenommenAbweichendGrundZuviel).orElse(false)) //
          //        .collect(toList());
          //
          //    boolean stimmzetteleingabenStimmzettelEingenommenAbweichendGrundUniversalSummandenOptional = stimmzetteleingabeStimmzettelEingenommenAbweichendZuwenig != null
          //        && stimmzetteleingabeStimmzettelEingenommenAbweichendZuwenig.hasAnzahlGroesserNull();
          //
          //    addPlausi(stimmzetteleingabeStimmzettelEingenommenAbweichendZuviel, // ELECT-15497
          //        new NlStimmzettelEingenommenAbweichendAnzahlGleichStimmzettelEingenommenAbweichendGruendeAnzahlenSummePlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_STIMMZETTEL_ZUVIEL,
          //            stimmzetteleingabenStimmzettelEingenommenAbweichendGrundZuvielSummanden, stimmzetteleingabenStimmzettelEingenommenAbweichendGrundUniversalSummanden,
          //            stimmzetteleingabenStimmzettelEingenommenAbweichendGrundUniversalSummandenOptional,
          //            findStimmeingabePlausiKonfigurationOrDefaults(context,
          //                AbstractNlStimmeingabePlausiDefinitions.STIMMZETTEL_EINGENOMMEN_ABWEICHEND_ZUVIEL_ANZAHL_GLEICH_STIMMZETTEL_EINGENOMMEN_ABWEICHEND_ZUVIEL_GRUENDE_ANZAHLEN_SUMME,
          //                stimmzettelEingenommenAbweichendFehlerart),
          //            getMessages()),
          //        context);
          //  }
        }

        if (stimmzetteleingabeStimmzettelEingenommenAbweichendZuwenig != null) {
          addPlausi(stimmzetteleingabeStimmzettelEingenommenAbweichendZuwenig, // ELECTVAPP-37, #11
              new NlStimmzettelEingenommenAbweichendZuwenigAnzahlGleichWaehlerGesamtAnzahlMinusStimmzettelEingenommenGesamtAnzahlPlausi(stimmeingabenWaehlerGesamt,
                  stimmzetteleingabeStimmzettelEingenommenGesamt,
                  findStimmeingabePlausiKonfigurationOrDefaults(context,
                      AbstractNlStimmeingabePlausiDefinitions.STIMMZETTEL_EINGENOMMEN_ABWEICHEND_ZUWENIG_ANZAHL_GLEICH_WAEHLER_GESAMT_ANZAHL_MINUS_STIMMZETTEL_EINGENOMMEN_GESAMT_ANZAHL,
                      stimmzettelEingenommenAbweichendFehlerart // ELECTVAPP-2064 -> GSB = ERROR, HSB/CSB = WARNING
          ), getMessages()), context);

          // ELECTVAPP-2147
          //  if (isGsb(context)) { // Auf höheren Ebenen sind Werte kumuliert -> Gründe zuviel/zuwenig nicht mehr unterscheidbar...
          //    List<Stimmeingabe> stimmzetteleingabenStimmzettelEingenommenAbweichendGrundZuwenigSummanden = stimmzetteleingabenStimmzettelEingenommenAbweichendGrund.stream() //
          //        .filter(stimmeingabe -> NlVoteType.valueOf(systemGruppeEigenschaften, stimmeingabe).map(NlVoteType::isStimmzettelEingenommenAbweichendGrundZuwenig).orElse(false)) //
          //        .collect(toList());
          //
          //    boolean stimmzetteleingabenStimmzettelEingenommenAbweichendGrundUniversalSummandenOptional = stimmzetteleingabeStimmzettelEingenommenAbweichendZuviel != null
          //        && stimmzetteleingabeStimmzettelEingenommenAbweichendZuviel.hasAnzahlGroesserNull();
          //
          //    addPlausi(stimmzetteleingabeStimmzettelEingenommenAbweichendZuwenig, // ELECT-15498
          //        new NlStimmzettelEingenommenAbweichendAnzahlGleichStimmzettelEingenommenAbweichendGruendeAnzahlenSummePlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_STIMMZETTEL_ZUWENIG,
          //            stimmzetteleingabenStimmzettelEingenommenAbweichendGrundZuwenigSummanden, stimmzetteleingabenStimmzettelEingenommenAbweichendGrundUniversalSummanden,
          //            stimmzetteleingabenStimmzettelEingenommenAbweichendGrundUniversalSummandenOptional,
          //            findStimmeingabePlausiKonfigurationOrDefaults(context,
          //                AbstractNlStimmeingabePlausiDefinitions.STIMMZETTEL_EINGENOMMEN_ABWEICHEND_ZUWENIG_ANZAHL_GLEICH_STIMMZETTEL_EINGENOMMEN_ABWEICHEND_ZUWENIG_GRUENDE_ANZAHLEN_SUMME,
          //                stimmzettelEingenommenAbweichendFehlerart),
          //            getMessages()),
          //        context);
          //  }
        }
      }

      if (isGsb(context)) {
        Stimmeingabe stimmzetteleingabeStimmzettelEingenommenAbweichendGrundUnbekannt = kopfzahlErfassenModel.getStimmzettelEingenommenAbweichendGrundUnbekanntStimmeingabe().orElse(null);

        if (stimmzetteleingabeStimmzettelEingenommenAbweichendGrundUnbekannt != null) {
          addPlausi(stimmzetteleingabeStimmzettelEingenommenAbweichendGrundUnbekannt, // ELECTVAPP-2136
              new StimmeingabeAnzahlGleichAnzahlPlausi(StimmeingabeAnzahlReference.SYSTEM_GRUPPE_STIMMZETTEL_GESAMT, findStimmeingabePlausiKonfigurationOrDefaults(context,
                  AbstractNlStimmeingabePlausiDefinitions.STIMMZETTEL_EINGENOMMEN_ABWEICHEND_GRUND_UNBEKANNT_ANZAHL_GLEICH_NULL, StimmeingabePlausiFehlerart.WARNING), getMessages()),
              context);
        }
      }
    });
  }

  protected void fillKopfzahlPlausisUnspezifisch(StimmeingabePlausiBuilderContext context, NlStimmeingabeKopfzahlErfassenModel kopfzahlErfassenModel,
      StimmeingabeGruppeStimmenErfassenModel gruppeStimmenErfassenModel, Stimmeingabe kopfzahlPlausiUnspezifischStimmeingabe) {
    // To be overridden...
  }

  @Override
  protected void fillKopfzahlPlausisAllgemein(StimmeingabeKopfzahlErfassenModel model, StimmeingabePlausiBuilderContext context) {
    // Cf. AbstractNlKopfzahlPlausiBuilder#fillKopfzahlPlausis...
  }

  @Override
  protected void fillKopfzahlPlausisWahlartSpezifisch(StimmeingabeKopfzahlErfassenModel kopfzahlErfassenModel, StimmeingabePlausiBuilderContext context) {
    // Cf. AbstractNlKopfzahlPlausiBuilder#fillKopfzahlPlausis...
  }

  /**
   * @return Liefert eine EXISTIERENDE Kopfzahl-Stimmeingabe, um an diese einen allgemeinen Stimmeingabefehler zu hängen, der nicht zwangsläufig mit dem Wert der gelieferten Stimmeingabe zu tun hat,
   *         aber eben an IRGENDEINER Stimmeingabe hängen muss. Diese Logik ist notwendig, da nicht in jeder Wahlart und vorallem nicht auf jeder Ebene (GSB, HSB, CSB) dieselben Kopfzahl-Stimmeingaben
   *         existieren!
   */
  public Stimmeingabe findKopfzahlPlausiUnspezifischStimmeingabe(Ergebniseingang ergebniseingang) {
    Collection<Stimmeingabe> stimmeingaben = ergebniseingang.getStimmeingaben();

    if (stimmeingaben.isEmpty()) {
      throw new IllegalStateException("Ergebniseingang OHNE Stimmeingaben!");
    }

    Stimmeingabe result = null;

    for (Stimmeingabe stimmeingabe : stimmeingaben) {
      Gruppe gruppe = stimmeingabe.getGruppe();
      Stimmenart stimmenart = stimmeingabe.getStimmenart();

      if (NlSystemGruppe.WAHLBERECHTIGTE.equalsSystemGruppe(gruppe, systemGruppeEigenschaften)) {
        if (Stimmenart.GESAMT.equals(stimmenart)) {
          result = stimmeingabe;
        }
      } else if (NlSystemGruppe.WAEHLER.equalsSystemGruppe(gruppe, systemGruppeEigenschaften)) {
        if (Stimmenart.GESAMT.equals(stimmenart)) {
          result = stimmeingabe;
        }
      } else if (NlSystemGruppe.STIMMZETTEL_EINGENOMMEN.equalsSystemGruppe(gruppe, systemGruppeEigenschaften)) {
        if (Stimmenart.GESAMT_EINGENOMMEN.equals(stimmenart)) {
          result = stimmeingabe;
        }
      } else if (NlSystemGruppe.GUELTIGE.equalsSystemGruppe(gruppe, systemGruppeEigenschaften)) {
        if (Stimmenart.LISTENSTIMME.equals(stimmenart) || Stimmenart.REFERENDUM_STIMME.equals(stimmenart)) {
          result = stimmeingabe;
        }
      }

      if (result != null) {
        break;
      }
    }

    if (result == null) {
      result = stimmeingaben.iterator().next();
    }

    return result;
  }

  protected boolean isGsb(StimmeingabePlausiBuilderContext context) {
    return NlWahlart.isWasGSB(context.getWahl(), getCustomer());
  }

  protected boolean isHsb(StimmeingabePlausiBuilderContext context) {
    return NlWahlart.isWasHSB(context.getWahl(), getCustomer());
  }

  protected boolean isHsbc(StimmeingabePlausiBuilderContext context) {
    return NlWahlart.isWasHSBC(context.getWahl(), getCustomer());
  }

  protected boolean isCsb(StimmeingabePlausiBuilderContext context) {
    return NlWahlart.isWasCSB(context.getWahl(), getCustomer());
  }

  protected Customer getCustomer() {
    return currentCustomer.getCustomer();
  }
}
