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

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

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

import javax.annotation.Nullable;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeErfassenContext;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeReferendumOptionStimmenErfassenLabelGenerator;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeReferendumOptionStimmen;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeReferendumVorlageStimmen;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeReferendumVorlageStimmenErfassenModel;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabefehler;
import de.ivu.elect.business.stimmeingabe.entity.ZwischensummeEingabe;
import de.ivu.elect.business.wahlvorschlag.control.ReferendumOptionByPositionComparator;
import de.ivu.elect.business.wahlvorschlag.entity.ReferendumOption;

public abstract class AbstractStimmeingabeReferendumVorlageStimmenModelBuilder extends AbstractStimmeingabeModelBuilder {

  private static final long serialVersionUID = 1L;

  public static final Long GRUPPE_STIMMEN_ABGEGEBEN_GESAMT_ZWISCHENSUMME_INDEX = -1L;

  public static final String GRUPPE_STIMMEN_ABGEGEBEN_GESAMT_ZWISCHENSUMME_NAME = "Stimmen gesamt";

  public static final Long GRUPPE_STIMMEN_GUELTIGE_GESAMT_ZWISCHENSUMME_INDEX = -2L;

  public static final String GRUPPE_STIMMEN_GUELTIGE_GESAMT_ZWISCHENSUMME_NAME = "Gültige Stimmen gesamt";

  public static final Long GRUPPE_STIMMEN_UNGUELTIGE_GESAMT_ZWISCHENSUMME_INDEX = -3L;

  public static final String GRUPPE_STIMMEN_UNGUELTIGE_GESAMT_ZWISCHENSUMME_NAME = "Ungültige Stimmen";

  public static final Long GRUPPE_STIMMEN_UEBRIGE_GESAMT_ZWISCHENSUMME_INDEX = -4L;

  public static final String GRUPPE_STIMMEN_UEBRIGE_GESAMT_ZWISCHENSUMME_NAME = ""; // Kundenspezifisch, bspw. "Blanko Stimmen" in NL...

  public AbstractStimmeingabeReferendumVorlageStimmenModelBuilder() {
    super();
  }

  public StimmeingabeReferendumVorlageStimmenErfassenModel createModelStimmeingabeReferendumVorlageStimmen(StimmeingabeErfassenContext context) {
    StimmeingabeReferendumVorlageStimmenErfassenModel model = createStimmeingabeReferendumVorlageStimmenErfassenModel(context.getWahldurchfuehrungsgebiet());

    for (GruppeImGebiet gruppeImGebiet : collectReferendumVorlagenImGebietSorted(context)) {
      initializeModelStimmeingabeReferendumVorlageStimmen(model, gruppeImGebiet, context);
    }

    context.getErgebniseingang().setStimmeingabeReferendumVorlageStimmenErfassenModel(model);

    return model;
  }

  protected StimmeingabeReferendumVorlageStimmenErfassenModel createStimmeingabeReferendumVorlageStimmenErfassenModel(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return new StimmeingabeReferendumVorlageStimmenErfassenModel(wahldurchfuehrungsgebiet);
  }

  private List<GruppeImGebiet> collectReferendumVorlagenImGebietSorted(StimmeingabeErfassenContext context) {
    return sortiereGruppenImGebietNachPosition(context.getGruppenImGebiet()).stream() //
        .filter(gruppeImGebiet -> !gruppeImGebiet.isSystemGruppe()) //
        .collect(toList());
  }

  protected void initializeModelStimmeingabeReferendumVorlageStimmen(StimmeingabeReferendumVorlageStimmenErfassenModel model, GruppeImGebiet gruppeImGebiet, StimmeingabeErfassenContext context) {
    String kennung = formatGruppeStimmenKennung(gruppeImGebiet, context);
    String bezeichnung = formatGruppeStimmenBezeichnung(gruppeImGebiet, context);

    model.getStimmenReferendumVorlage()
        .add(new StimmeingabeReferendumVorlageStimmen(bezeichnung, kennung, gruppeImGebiet.getGruppe(), createStimmeingabeReferendumOptionStimmen(gruppeImGebiet, context)));
  }

  public List<StimmeingabeReferendumVorlageStimmen> layoutModelStimmeingabeReferendumVorlageStimmen(StimmeingabeReferendumVorlageStimmenErfassenModel model,
      StimmeingabeGruppeStimmenErfassenModelContext context) {
    return model.getStimmenReferendumVorlage();
  }

  private List<StimmeingabeReferendumOptionStimmen> createStimmeingabeReferendumOptionStimmen(GruppeImGebiet gruppeImGebiet, StimmeingabeErfassenContext context) {
    List<StimmeingabeReferendumOptionStimmen> referendumOptionStimmen = new ArrayList<>();

    List<Stimmeingabe> stimmeingaben = new ArrayList<>();

    for (ReferendumOption referendumOption : collectReferendumOptionenSorted(gruppeImGebiet)) {
      StimmeingabeReferendumOptionStimmen stimmeingabeReferendumOptionStimmen = createStimmeingabeReferendumOptionStimmen(gruppeImGebiet, referendumOption, Stimmenart.REFERENDUM_STIMME, context);

      referendumOptionStimmen.add(stimmeingabeReferendumOptionStimmen);
      stimmeingaben.add(stimmeingabeReferendumOptionStimmen.getStimmeingabe());
    }

    StimmeingabeReferendumOptionStimmen stimmeingabeReferendumVorlageStimmenGueltig = createStimmeingabeReferendumOptionStimmenZwischensumme(gruppeImGebiet,
        GRUPPE_STIMMEN_GUELTIGE_GESAMT_ZWISCHENSUMME_INDEX, GRUPPE_STIMMEN_GUELTIGE_GESAMT_ZWISCHENSUMME_NAME, stimmeingaben, context);

    StimmeingabeReferendumOptionStimmen stimmeingabeReferendumVorlageStimmenUngueltig = createStimmeingabeReferendumOptionStimmenZwischensumme(gruppeImGebiet,
        GRUPPE_STIMMEN_UNGUELTIGE_GESAMT_ZWISCHENSUMME_INDEX, GRUPPE_STIMMEN_UNGUELTIGE_GESAMT_ZWISCHENSUMME_NAME, Collections.<Stimmeingabe> emptyList(), context);

    StimmeingabeReferendumOptionStimmen stimmeingabeReferendumVorlageStimmenUebrige = createStimmeingabeReferendumOptionStimmenZwischensumme(gruppeImGebiet,
        GRUPPE_STIMMEN_UEBRIGE_GESAMT_ZWISCHENSUMME_INDEX, GRUPPE_STIMMEN_UEBRIGE_GESAMT_ZWISCHENSUMME_NAME, Collections.<Stimmeingabe> emptyList(), context);

    StimmeingabeReferendumOptionStimmen stimmeingabeReferendumVorlageStimmenAbgegeben = createStimmeingabeReferendumOptionStimmenZwischensumme(gruppeImGebiet,
        GRUPPE_STIMMEN_ABGEGEBEN_GESAMT_ZWISCHENSUMME_INDEX, GRUPPE_STIMMEN_ABGEGEBEN_GESAMT_ZWISCHENSUMME_NAME, Collections.<Stimmeingabe> emptyList(), context);

    List<StimmeingabeReferendumOptionStimmen> result = new ArrayList<>();

    addStimmenGueltigUngueltigGesamt(result, referendumOptionStimmen, stimmeingabeReferendumVorlageStimmenGueltig, stimmeingabeReferendumVorlageStimmenUngueltig,
        stimmeingabeReferendumVorlageStimmenUebrige, stimmeingabeReferendumVorlageStimmenAbgegeben);

    return result;
  }

  private Collection<ReferendumOption> collectReferendumOptionenSorted(GruppeImGebiet gruppeImGebiet) {
    List<ReferendumOption> result = new ArrayList<>(gruppeImGebiet.getReferendumOptionenInUebergeordneterGebietshierarchie());
    Collections.sort(result, new ReferendumOptionByPositionComparator());

    return result;
  }

  protected void addStimmenGueltigUngueltigGesamt(List<StimmeingabeReferendumOptionStimmen> result, List<StimmeingabeReferendumOptionStimmen> referendumOptionStimmen,
      StimmeingabeReferendumOptionStimmen stimmeingabeReferendumVorlageStimmenGueltig, StimmeingabeReferendumOptionStimmen stimmeingabeReferendumVorlageStimmenUngueltig,
      StimmeingabeReferendumOptionStimmen stimmeingabeReferendumVorlageStimmenUebrige, StimmeingabeReferendumOptionStimmen stimmeingabeReferendumVorlageStimmenAbgegeben) {
    result.add(stimmeingabeReferendumVorlageStimmenUngueltig);
    result.add(stimmeingabeReferendumVorlageStimmenGueltig);
    result.addAll(referendumOptionStimmen);

    // NOTE ELECTDD-369, dass 'Übrige' (bspw. Blanko) und 'Abgegebene Stimmen insgesamt' standardmäßig nicht erfasst werden.
  }

  private StimmeingabeReferendumOptionStimmen createStimmeingabeReferendumOptionStimmen(GruppeImGebiet gruppeImGebiet, ReferendumOption referendumOption, Stimmenart stimmenart,
      StimmeingabeErfassenContext context) {
    Stimmeingabe stimmeingabe = createStimmeingabeIfStimmenartApplicable(gruppeImGebiet, referendumOption, stimmenart, context);

    return createStimmeingabeReferendumOptionStimmen(gruppeImGebiet, stimmeingabe, context);
  }

  private StimmeingabeReferendumOptionStimmen createStimmeingabeReferendumOptionStimmenZwischensumme(GruppeImGebiet gruppeImGebiet, long summenIndex, String summenName,
      Collection<Stimmeingabe> stimmeingaben, StimmeingabeErfassenContext context) {
    ZwischensummeEingabe zwischensummeEingabe = createZwischensummeEingabe(gruppeImGebiet, Stimmenart.REFERENDUM_STIMME, summenIndex, summenName, stimmeingaben, context);

    return createStimmeingabeReferendumOptionStimmen(gruppeImGebiet, zwischensummeEingabe, context);
  }

  private StimmeingabeReferendumOptionStimmen createStimmeingabeReferendumOptionStimmen(GruppeImGebiet gruppeImGebiet, Stimmeingabe stimmeingabe, StimmeingabeErfassenContext context) {
    String kennung = formatReferendumOptionStimmenKennung(gruppeImGebiet, stimmeingabe, context);
    String bezeichnung = formatReferendumOptionStimmenBezeichnung(gruppeImGebiet, stimmeingabe, context);

    String stimmeingabeKennung = formatReferendumOptionStimmenStimmeingabeKennung(stimmeingabe, context);
    String stimmeingabeBezeichnung = formatReferendumOptionStimmenStimmeingabeBezeichnung(stimmeingabe, context);

    List<Stimmeingabefehler> stimmeingabefehlerLetzterErgebniseingang = getStimmeingabefehlerLetzterErgebniseingang(stimmeingabe, context);

    StimmeingabeReferendumOptionStimmen result = new StimmeingabeReferendumOptionStimmen(kennung, bezeichnung, stimmeingabe, stimmeingabeKennung, stimmeingabeBezeichnung,
        stimmeingabefehlerLetzterErgebniseingang);

    StimmeingabeReferendumOptionStimmenErfassenLabelGenerator stimmeingabeReferendumOptionStimmenErfassenLabelGenerator = context.getReferendumOptionStimmenErfassenLabelGenerator();

    if (stimmeingabeReferendumOptionStimmenErfassenLabelGenerator.isStimmeingabeZwischensummeAnzahlUngueltigeGesamt(stimmeingabe)) {
      result.setStimmeingabeZwischensummeAnzahlUngueltigeGesamt(true);
    }

    if (stimmeingabeReferendumOptionStimmenErfassenLabelGenerator.isStimmeingabeZwischensummeAnzahlGueltigeGesamt(stimmeingabe)) {
      result.setStimmeingabeZwischensummeAnzahlGueltigeGesamt(true);
    }

    if (stimmeingabeReferendumOptionStimmenErfassenLabelGenerator.isStimmeingabeZwischensummeAnzahlUebrigeGesamt(stimmeingabe)) {
      result.setStimmeingabeZwischensummeAnzahlUebrigeGesamt(true);
    }

    if (stimmeingabeReferendumOptionStimmenErfassenLabelGenerator.isStimmeingabeZwischensummeAnzahlAbgegebenGesamt(stimmeingabe)) {
      result.setStimmeingabeZwischensummeAnzahlAbgegebeneGesamt(true);
    }

    return result;
  }

  protected String formatReferendumOptionStimmenKennung(GruppeImGebiet gruppeImGebiet, Stimmeingabe stimmeingabe, StimmeingabeErfassenContext context) {
    return context.getReferendumOptionStimmenErfassenLabelGenerator().getReferendumOptionStimmenKennung(context.getEntityAufStimmzettelDaten(), stimmeingabe.getReferendumOption(), gruppeImGebiet,
        context.getGruppenImGebiet());
  }

  protected String formatReferendumOptionStimmenBezeichnung(GruppeImGebiet gruppeImGebiet, Stimmeingabe stimmeingabe, StimmeingabeErfassenContext context) {
    return context.getReferendumOptionStimmenErfassenLabelGenerator().getReferendumOptionStimmenBezeichnung(context.getEntityAufStimmzettelDaten(), stimmeingabe.getReferendumOption(), gruppeImGebiet,
        context.getGruppenImGebiet());
  }

  protected String formatReferendumOptionStimmenStimmeingabeKennung(Stimmeingabe stimmeingabe, StimmeingabeErfassenContext context) {
    return context.getReferendumOptionStimmenErfassenLabelGenerator().getReferendumOptionStimmenStimmeingabeKennung(context.getEntityAufStimmzettelDaten(), stimmeingabe, context.getGruppenImGebiet());
  }

  protected String formatReferendumOptionStimmenStimmeingabeBezeichnung(Stimmeingabe stimmeingabe, StimmeingabeErfassenContext context) {
    return context.getReferendumOptionStimmenErfassenLabelGenerator().getReferendumOptionStimmenStimmeingabeBezeichnung(context.getEntityAufStimmzettelDaten(), stimmeingabe,
        context.getGruppenImGebiet());
  }

  @Nullable
  protected Stimmeingabe createStimmeingabeIfStimmenartApplicable(GruppeImGebiet gruppeImGebiet, ReferendumOption referendumOption, Stimmenart stimmenart, StimmeingabeErfassenContext context) {
    if (isStimmenartApplicable(gruppeImGebiet, stimmenart, context)) {
      return createStimmeingabe(gruppeImGebiet, null, referendumOption, stimmenart, context);
    }

    return null;
  }
}
