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

import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppeEigenschaften;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietDAO;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeErfassenDescriptionResolver;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeKopfzahlErfassenModelBuilder;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingabestufe;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.ErgebniseingangErfasser;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsherkunft;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.ZwischensummeEingabe;
import de.ivu.elect.business.stimmzettel.boundary.EntityAufStimmzettelDatenDAOFactory;
import de.ivu.elect.business.wahlvorschlag.boundary.WahlvorschlagDAO;
import de.ivu.elect.jee.entity.EntityUtil;
import de.ivu.elect.jee.entity.tk.EntityTK;

public class ErgebniseingangKopieren {

  @Inject
  private transient SystemGruppeEigenschaften systemGruppeEigenschaften;

  @Inject
  private transient GruppeImGebietDAO gruppeImGebietDAO;

  @Inject
  private transient WahlvorschlagDAO wahlvorschlagDAO;

  @Inject
  private transient EntityAufStimmzettelDatenDAOFactory entityAufStimmzettelDatenDAOFactory;

  @Inject
  private transient StimmeingabeErfassenDescriptionResolver stimmeingabeErfassenDescriptionResolver;

  @Inject
  private transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  public ErgebniseingangKopieren() {
    super();
  }

  public Ergebniseingang kopiereErgebniseingang(ErgebniseingangErfasser erfasser, Long eingangssequenznummer, Ergebniseingangsart ergebniseingangsart, Ergebniseingabestufe ergebniseingabestufe,
                                                Ergebniseingangsherkunft ergebniseingangsherkunft, Ergebniseingang template, boolean stimmeingabenZuPlausibilisieren) {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = template.getWahldurchfuehrungsgebiet();

    Ergebniseingang ergebniseingang = createErgebniseingang(wahldurchfuehrungsgebiet, erfasser, eingangssequenznummer, ergebniseingangsart,
        ergebniseingabestufe, ergebniseingangsherkunft, template);

    for (Stimmeingabe stimmeingabe : template.getStimmeingaben()) {
      ergebniseingang.addStimmeingabe(createStimmeingabe(stimmeingabe));
    }

    if (template.isErgebniseingangsartVorlaeufig() && ergebniseingang.isErgebniseingangsartEndgueltig()) {
      kopiereErgebniseingangPostProcessingVorlaeufigZuEndgueltig(wahldurchfuehrungsgebiet, ergebniseingang);
    }

    if (stimmeingabenZuPlausibilisieren) {
      Set<Stimmeingabe> stimmeingabenKopiert = new HashSet<>(ergebniseingang.getStimmeingabesByErgebniseingang());
      ergebniseingang.removeAllStimmeingabeByErgebniseingangs();

      // Im geleerten Ergebniseingang erneut alle erforderlichen (ggf. in Anzahl und Vorhandensein abweichenden) Stimmeingaben mit leeren/initialen Werten durch Aufruf der Model-Builder hinzufügen.
      buildStimmeingabeErfassenModels(ergebniseingang);

      Set<Stimmeingabe> stimmeingabenRendered = new HashSet<>(ergebniseingang.getStimmeingabesByErgebniseingang());
      ergebniseingang.removeAllStimmeingabeByErgebniseingangs();

      Map<EntityTK, Stimmeingabe> stimmeingabeRenderedPerTk = EntityUtil.mappeNachKonkretestemTk(stimmeingabenRendered);

      for (Stimmeingabe stimmeingabeKopiert : stimmeingabenKopiert) {
        Stimmeingabe stimmeingabeRendered = stimmeingabeRenderedPerTk.get(stimmeingabeKopiert.getTk());

        if (stimmeingabeRendered == null) {
          ergebniseingang.addStimmeingabe(stimmeingabeKopiert);
        } else {
          stimmeingabeRendered.setAnzahlEingabe(stimmeingabeKopiert.getAnzahlEingabe()); // Kopierte Anzahl wieder vorbelegen...

          ergebniseingang.addStimmeingabe(stimmeingabeRendered);
        }
      }
    }

    return ergebniseingang;
  }

  protected void buildStimmeingabeErfassenModels(Ergebniseingang ergebniseingang) {
    ergebniseingang.fillTransientModels(stimmeingabeErfassenDescriptionResolver, null, gruppeImGebietDAO, wahlvorschlagDAO,
        entityAufStimmzettelDatenDAOFactory.getEntityAufStimmzettelDaten(), wahldurchfuehrungsgebietDAO);
  }

  protected void kopiereErgebniseingangPostProcessingVorlaeufigZuEndgueltig(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingang ergebniseingangEndgueltig) {
    updateAnzahlenWahlberechtigteFallsAmGebietHinterlegt(wahldurchfuehrungsgebiet, ergebniseingangEndgueltig);
  }

  // ELECT-8465, Anzahlen Wahlberechtigte aktualisieren, FALLS DIESE AM GEBIET HINTERLEGT WURDEN und deshalb immer mit den Werten im Ergebnis übereinstimmen müssen!!!
  private void updateAnzahlenWahlberechtigteFallsAmGebietHinterlegt(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingang ergebniseingangEndgueltig) {
    if (wahldurchfuehrungsgebiet.hasAnyAnzahlWahlberechtigte()) {
      Ergebniseingangsart ergebniseingangsartEndgueltig = Ergebniseingangsart.ENDGUELTIG;
      Ergebniseingangsart ergebniseingangsartVorlaeufig = Ergebniseingangsart.VORLAEUFIG;

      StimmeingabeKopfzahlErfassenModelBuilder stimmeingabeKopfzahlErfassenModelBuilder = getStimmeingabeKopfzahlErfassenModelBuilder(wahldurchfuehrungsgebiet);

      boolean anzahlWahlberechtigteGesamtEndgueltigSummiertErfasst = stimmeingabeKopfzahlErfassenModelBuilder.isWahlberechtigteOhneWahlscheinCreated(wahldurchfuehrungsgebiet,
          ergebniseingangsartEndgueltig) || stimmeingabeKopfzahlErfassenModelBuilder.isWahlberechtigteMitWahlscheinCreated(wahldurchfuehrungsgebiet, ergebniseingangsartEndgueltig);

      if (anzahlWahlberechtigteGesamtEndgueltigSummiertErfasst) {
        Stimmeingabe stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisEndgueltig = null;
        Stimmeingabe stimmeingabeWahlberechtigteGesamtEndgueltig = null;

        for (Stimmeingabe stimmeingabeEndgueltig : ergebniseingangEndgueltig.getStimmeingaben()) {
          if (isSystemGruppenStimmeingabe(stimmeingabeEndgueltig, SystemGruppe.WAHLBERECHTIGTE)) {
            Stimmenart stimmenart = stimmeingabeEndgueltig.getStimmenart();

            if (Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS.equals(stimmenart)) {
              stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisEndgueltig = stimmeingabeEndgueltig;
            } else if (Stimmenart.GESAMT.equals(stimmenart)) {
              stimmeingabeWahlberechtigteGesamtEndgueltig = stimmeingabeEndgueltig;
            }
          }
        }

        // Ist bspw. "Nur anzeigen" für alle Anzahlen Wahlberechtigte konfiguriert und wird A3 nur endgültig erfasst, kann "Wahlberechtigte insgesamt" nicht manuell korrigiert werden.
        if (stimmeingabeWahlberechtigteGesamtEndgueltig != null) {
          long anzahlWahlberechtigteNichtImWaehlerverzeichnisAmGebiet = wahldurchfuehrungsgebiet.getAnzahlWahlberechtigteNichtImWaehlerverzeichnisNotNull();

          if (anzahlWahlberechtigteNichtImWaehlerverzeichnisAmGebiet > 0) {
            boolean anzahlWahlberechtigteNichtImWaehlerverzeichnisVorlaeufigErfasst = stimmeingabeKopfzahlErfassenModelBuilder
                .isWahlberechtigteNichtImWaehlerverzeichnisCreated(wahldurchfuehrungsgebiet, ergebniseingangsartVorlaeufig);
            boolean anzahlWahlberechtigteNichtImWaehlerverzeichnisEndgueltigErfasst = stimmeingabeKopfzahlErfassenModelBuilder
                .isWahlberechtigteNichtImWaehlerverzeichnisCreated(wahldurchfuehrungsgebiet, ergebniseingangsartEndgueltig);

            long anzahlWahlberechtigteGesamtVorlaeufig = stimmeingabeWahlberechtigteGesamtEndgueltig.getAnzahl();
            long anzahlWahlberechtigteGesamtEndgueltig = anzahlWahlberechtigteGesamtVorlaeufig;

            if (anzahlWahlberechtigteNichtImWaehlerverzeichnisVorlaeufigErfasst) {
              if (!anzahlWahlberechtigteNichtImWaehlerverzeichnisEndgueltigErfasst) {
                if (stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisEndgueltig != null) {
                  stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisEndgueltig.setAnzahl(0L);
                }

                anzahlWahlberechtigteGesamtEndgueltig = Math.max(0, anzahlWahlberechtigteGesamtVorlaeufig - anzahlWahlberechtigteNichtImWaehlerverzeichnisAmGebiet);
              }
            } else if (anzahlWahlberechtigteNichtImWaehlerverzeichnisEndgueltigErfasst) {
              if (stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisEndgueltig != null) {
                stimmeingabeWahlberechtigteNichtImWaehlerverzeichnisEndgueltig.setAnzahl(anzahlWahlberechtigteNichtImWaehlerverzeichnisAmGebiet);
              }

              anzahlWahlberechtigteGesamtEndgueltig = anzahlWahlberechtigteGesamtVorlaeufig + anzahlWahlberechtigteNichtImWaehlerverzeichnisAmGebiet;
            }

            stimmeingabeWahlberechtigteGesamtEndgueltig.setAnzahl(anzahlWahlberechtigteGesamtEndgueltig);
          }
        }
      }
    }
  }

  private boolean isSystemGruppenStimmeingabe(Stimmeingabe stimmeingabeEndgueltig, SystemGruppe systemGruppe) {
    return stimmeingabeEndgueltig.isSystemGruppenStimmeingabe() && getSystemGruppeEigenschaften().isGruppeEqualsSystemGruppe(stimmeingabeEndgueltig.getGruppe(), systemGruppe);
  }

  protected SystemGruppeEigenschaften getSystemGruppeEigenschaften() {
    return systemGruppeEigenschaften;
  }

  protected StimmeingabeKopfzahlErfassenModelBuilder getStimmeingabeKopfzahlErfassenModelBuilder(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return stimmeingabeErfassenDescriptionResolver.getStimmeingabeErfassenDescriptionByGebiet(wahldurchfuehrungsgebiet).getStimmeingabeKopfzahlErfassenModelBuilder();
  }

  private Ergebniseingang createErgebniseingang(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, ErgebniseingangErfasser erfasser, Long eingangssequenznummer,
                                                Ergebniseingangsart ergebniseingangsart, Ergebniseingabestufe ergebniseingabestufe, Ergebniseingangsherkunft ergebniseingangsherkunft, Ergebniseingang template) {
    Ergebniseingang ergebniseingang = new Ergebniseingang();

    template.copyEntityAttributesUsingSetter(ergebniseingang);

    ergebniseingang.setWahldurchfuehrungsgebiet(wahldurchfuehrungsgebiet);

    if (eingangssequenznummer != null) {
      ergebniseingang.setEingangssequenznummer(eingangssequenznummer); // ELECT-12060
    }
    ergebniseingang.setErgebnissequenznummer(null); // ELECT-12060 soll neu vergeben werden
    ergebniseingang.setErgebniseingabestufe(ergebniseingabestufe);
    ergebniseingang.setErgebniseingangsart(ergebniseingangsart);
    ergebniseingang.setErgebniseingangsherkunft(ergebniseingangsherkunft);

    ergebniseingang.setZeitstempel(new Date());
    ergebniseingang.setErfasser(erfasser.getKennung());
    ergebniseingang.setEingabeHashCode(null);

    if (template.isGueltigKeinFehler()) {
      ergebniseingang.setGueltigKeinFehler();
    } else if (template.isGueltigKannFehler()) {
      ergebniseingang.setGueltigKannFehler();
    } else if (template.isUngueltigKannFehler()) {
      ergebniseingang.setUngueltigKannFehler();
    } else if (template.isUngueltigMussFehler()) {
      ergebniseingang.setUngueltigMussFehler();
    }

    return ergebniseingang;
  }

  private Stimmeingabe createStimmeingabe(Stimmeingabe template) {
    if (template.isZwischensummeEingabe()) {
      return createStimmeingabe(new ZwischensummeEingabe(), (ZwischensummeEingabe) template);
    }

    return createStimmeingabe(new Stimmeingabe(), template);
  }

  private <T extends Stimmeingabe> T createStimmeingabe(T stimmeingabe, T template) {
    template.copyEntityAttributesUsingSetter(stimmeingabe);

    stimmeingabe.setDiscriminator(template.getDiscriminator());

    stimmeingabe.setGruppeImGebiet(template.getGruppeImGebiet());
    stimmeingabe.setListenkandidatur(template.getListenkandidatur());
    stimmeingabe.setReferendumOption(template.getReferendumOption());

    return stimmeingabe;
  }
}
