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

import static java.util.Collections.emptyList;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.inject.Inject;

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.kandidatur.entity.KandidaturStimmzettelPersonendaten;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.kandidatur.entity.ListenkandidaturStimmzettelPosition;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeErfassenContext;
import de.ivu.elect.business.stimmeingabe.entity.AbstractStimmeingabeStimmen;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeListenkandidaturStimmen;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeListenkandidaturStimmenErfassenModel;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabefehler;
import de.ivu.elect.business.stimmeingabe.entity.ZwischensummeEingabe;
import de.ivu.elect.business.stimmen.boundary.StimmzettelListenkandidaturStrategie;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;

public abstract class AbstractStimmeingabeListenkandidaturStimmenModelBuilder extends AbstractStimmeingabeModelBuilder {

  private static final long serialVersionUID = 1L;

  protected static final StimmeingabeListenkandidaturStimmen KEINE_STIMMEINGABE = null;

  @Inject
  protected transient StimmzettelListenkandidaturStrategie stimmzettelListenkandidaturStrategie;

  protected AbstractStimmeingabeListenkandidaturStimmenModelBuilder() {
    super();
  }

  @Override
  protected boolean isStimmenartApplicable(GruppeImGebiet gruppeImGebiet, Stimmenart stimmenart, StimmeingabeErfassenContext context) {
    if (Stimmenart.DIREKTSTIMME.equals(stimmenart)) {
      return false;
    }

    return super.isStimmenartApplicable(gruppeImGebiet, stimmenart, context);
  }

  protected boolean isListenkandidaturAufStimmzettel(GruppeImGebiet gruppeImGebiet, @Nullable Listenkandidatur listenkandidatur) {
    return getStimmzettelListenkandidaturStrategie().isListenkandidaturAufStimmzettel(gruppeImGebiet, listenkandidatur);
  }

  protected boolean isListenkandidaturAufStimmzettelErfassbar(GruppeImGebiet gruppeImGebiet, @Nullable Listenkandidatur listenkandidatur) {
    return getStimmzettelListenkandidaturStrategie().isListenkandidaturAufStimmzettelErfassbar(gruppeImGebiet, listenkandidatur);
  }

  protected StimmzettelListenkandidaturStrategie getStimmzettelListenkandidaturStrategie() {
    return stimmzettelListenkandidaturStrategie;
  }

  public List<StimmeingabeListenkandidaturStimmenErfassenModel> createModelStimmeingabeListenkandidaturStimmenPerApplicableGruppeImGebiet(StimmeingabeErfassenContext context) {
    return context.getGruppenImGebiet().stream().filter(gruppeImGebiet -> gruppeImGebiet.getGruppe().isPolitischeGruppe() && isStimmenartApplicable(gruppeImGebiet, Stimmenart.LISTENSTIMME, context))
        .map(gruppeImGebiet -> createModelStimmeingabeListenkandidaturStimmen(gruppeImGebiet, context)).collect(Collectors.toList());
  }

  public StimmeingabeListenkandidaturStimmenErfassenModel createModelStimmeingabeListenkandidaturStimmen(GruppeImGebiet gruppeImGebiet, StimmeingabeErfassenContext context) {
    Liste liste = findListeInGebietshierarchie(gruppeImGebiet, context);
    LinkedHashMap<Listenkandidatur, GruppeImGebiet> gruppeImGebietPerListenkandidaturSorted = collectGruppeImGebietPerListenkandidaturSorted(gruppeImGebiet, liste, context);

    StimmeingabeListenkandidaturStimmenErfassenModel model = createStimmeingabeListenkandidaturStimmenErfassenModel(context.getWahldurchfuehrungsgebiet(), gruppeImGebiet, liste);
    initializeModelListenkandidaturStimmen(model, gruppeImGebiet, gruppeImGebietPerListenkandidaturSorted, context);

    context.getErgebniseingang().addStimmeingabeListenkandidaturStimmenErfassenModel(model);

    return model;
  }

  protected StimmeingabeListenkandidaturStimmenErfassenModel createStimmeingabeListenkandidaturStimmenErfassenModel(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, GruppeImGebiet gruppeImGebiet,
      Liste liste) {
    return new StimmeingabeListenkandidaturStimmenErfassenModel(wahldurchfuehrungsgebiet, gruppeImGebiet, liste);
  }

  @Nullable
  protected Liste findListeInGebietshierarchie(GruppeImGebiet gruppeImGebiet, StimmeingabeErfassenContext context) {
    if (gruppeImGebiet != null) {
      return gruppeImGebiet.getListeAufStimmzettelInUebergeordneterGebietshierarchie(context.getEntityAufStimmzettelDaten().collectListenInWahl(context.getWahldurchfuehrung()));
    }

    return null;
  }

  protected LinkedHashMap<Listenkandidatur, GruppeImGebiet> collectGruppeImGebietPerListenkandidaturSorted(GruppeImGebiet gruppeImGebiet, @Nullable Liste liste, StimmeingabeErfassenContext context) {
    LinkedHashMap<Listenkandidatur, GruppeImGebiet> result = new LinkedHashMap<>();

    if (liste == null) {
      throw new IllegalStateException("Modelle zur Erfassung von Listenkandidaturstimmen können nur für Gruppen mit Liste erzeugt werden! [gruppeImGebiet=" + gruppeImGebiet + ", gruppeSchluessel="
          + gruppeImGebiet.getGruppe().getSchluessel() + "]");
    }

    for (Listenkandidatur listenkandidatur : context.getEntityAufStimmzettelDaten().collectListenkandidaturenAufStimmzettelSortedByDefault(liste)) {
      result.put(listenkandidatur, gruppeImGebiet);
    }

    return result;
  }

  protected LinkedHashMap<Listenkandidatur, GruppeImGebiet> collectAllGruppeImGebietPerListenkandidaturSorted(StimmeingabeErfassenContext context, Comparator<Listenkandidatur> comparator) {
    SortedMap<Listenkandidatur, GruppeImGebiet> result = new TreeMap<>(comparator);

    EntityAufStimmzettelDaten entityAufStimmzettelDaten = context.getEntityAufStimmzettelDaten();

    for (GruppeImGebiet gruppeImGebiet : context.getGruppenImGebiet()) {
      if (gruppeImGebiet.hatListeAufStimmzettelInUebergeordneterGebietshierarchie(context.getWahlvorschlaege())) {
        Liste liste = findListeInGebietshierarchie(gruppeImGebiet, context);

        if (liste != null) { // ELECT-15753
          for (Listenkandidatur listenkandidatur : entityAufStimmzettelDaten.collectListenkandidaturenAufStimmzettelSortedByDefault(liste)) {
            result.put(listenkandidatur, gruppeImGebiet);
          }
        }
      }
    }

    return new LinkedHashMap<>(result);
  }

  protected void initializeModelListenkandidaturStimmen(StimmeingabeListenkandidaturStimmenErfassenModel model, GruppeImGebiet gruppeImGebiet,
      LinkedHashMap<Listenkandidatur, GruppeImGebiet> gruppeImGebietPerListenkandidaturSorted, StimmeingabeErfassenContext context) {
    List<StimmeingabeListenkandidaturStimmen> stimmeingabenListenkandidaturStimmenListenkandidaturStimmen = createStimmeingabenListenkandidaturStimmenListenkandidaturStimmen(gruppeImGebiet,
        gruppeImGebietPerListenkandidaturSorted, context);

    StimmeingabeListenkandidaturStimmen stimmeingabeListenkandidaturStimmenListenStimmen = createStimmeingabeListenkandidaturStimmenListenStimmen(gruppeImGebiet, context);
    StimmeingabeListenkandidaturStimmen stimmeingabeListenkandidaturStimmenListenkandidaturStimmenGesamt = createStimmeingabeListenkandidaturStimmenListenkandidaturStimmenGesamt(gruppeImGebiet,
        context);

    List<StimmeingabeListenkandidaturStimmen> stimmeingabenListenkandidaturStimmenListenkandidaturZwischensummen = createStimmeingabenListenkandidaturStimmenListenkandidaturZwischensummen(
        gruppeImGebiet, stimmeingabeListenkandidaturStimmenListenStimmen, stimmeingabenListenkandidaturStimmenListenkandidaturStimmen, stimmeingabeListenkandidaturStimmenListenkandidaturStimmenGesamt,
        context);

    model.setListenStimmen(stimmeingabeListenkandidaturStimmenListenStimmen);

    model.setListenkandidaturStimmen(stimmeingabenListenkandidaturStimmenListenkandidaturStimmen);
    model.setListenkandidaturStimmenGesamt(stimmeingabeListenkandidaturStimmenListenkandidaturStimmenGesamt);

    model.setListenkandidaturZwischensummen(stimmeingabenListenkandidaturStimmenListenkandidaturZwischensummen);
  }

  protected List<StimmeingabeListenkandidaturStimmen> createStimmeingabenListenkandidaturStimmenListenkandidaturStimmen(GruppeImGebiet gruppeImGebiet,
      LinkedHashMap<Listenkandidatur, GruppeImGebiet> gruppeImGebietPerListenkandidaturSorted, StimmeingabeErfassenContext context) {
    List<StimmeingabeListenkandidaturStimmen> result = new ArrayList<>();

    for (Entry<Listenkandidatur, GruppeImGebiet> gruppeImGebietPerListenkandidatur : gruppeImGebietPerListenkandidaturSorted.entrySet()) {
      StimmeingabeListenkandidaturStimmen stimmeingabeListenkandidaturStimmen = createStimmeingabeListenkandidaturStimmen(gruppeImGebietPerListenkandidatur.getValue(),
          gruppeImGebietPerListenkandidatur.getKey(), context);

      if (stimmeingabeListenkandidaturStimmen.hasStimmeingaben()) {
        result.add(stimmeingabeListenkandidaturStimmen);
      }
    }

    return result;
  }

  @Nullable
  protected StimmeingabeListenkandidaturStimmen createStimmeingabeListenkandidaturStimmenListenStimmen(GruppeImGebiet gruppeImGebiet, StimmeingabeErfassenContext context) {
    return KEINE_STIMMEINGABE; // To be overridden...
  }

  @Nullable
  protected StimmeingabeListenkandidaturStimmen createStimmeingabeListenkandidaturStimmenListenkandidaturStimmenGesamt(GruppeImGebiet gruppeImGebiet, StimmeingabeErfassenContext context) {
    return KEINE_STIMMEINGABE; // To be overridden...
  }

  protected List<StimmeingabeListenkandidaturStimmen> createStimmeingabenListenkandidaturStimmenListenkandidaturZwischensummen(GruppeImGebiet gruppeImGebiet,
      @Nullable StimmeingabeListenkandidaturStimmen stimmeingabeListenkandidaturStimmenListenStimmen,
      List<StimmeingabeListenkandidaturStimmen> stimmeingabenListenkandidaturStimmenListenkandidaturStimmen,
      @Nullable StimmeingabeListenkandidaturStimmen stimmeingabeListenkandidaturStimmenListenkandidaturStimmenGesamt, StimmeingabeErfassenContext context) {
    return emptyList(); // To be overridden...
  }

  protected StimmeingabeListenkandidaturStimmen createStimmeingabeListenkandidaturStimmen(GruppeImGebiet gruppeImGebiet, Listenkandidatur listenkandidatur, StimmeingabeErfassenContext context) {
    Stimmeingabe stimmeingabe = createStimmeingabeIfStimmenartApplicable(gruppeImGebiet, listenkandidatur, Stimmenart.LISTENSTIMME, context);

    StimmeingabeListenkandidaturStimmen stimmeingabeListenkandidaturStimmen = createStimmeingabeListenkandidaturStimmen(gruppeImGebiet, stimmeingabe, context);
    stimmeingabeListenkandidaturStimmen.setAnzahlEingabeErfassbar(isListenkandidaturAufStimmzettelErfassbar(gruppeImGebiet, listenkandidatur)); // ELECTKOM-2845

    return stimmeingabeListenkandidaturStimmen;
  }

  protected StimmeingabeListenkandidaturStimmen createStimmeingabeListenkandidaturStimmen(GruppeImGebiet gruppeImGebiet, Stimmeingabe stimmeingabe, StimmeingabeErfassenContext context) {
    String kennung = formatListenkandidaturStimmenKennung(gruppeImGebiet, context);
    String bezeichnung = formatListenkandidaturStimmenBezeichnung(gruppeImGebiet, context);

    String stimmeingabeKennung = formatListenkandidaturStimmenStimmeingabeKennung(stimmeingabe, context);
    String stimmeingabeBezeichnung = formatListenkandidaturStimmenStimmeingabeBezeichnung(stimmeingabe, context);
    String stimmeingabeTooltip = formatListenkandidaturStimmenStimmeingabeTooltip(stimmeingabe, context);

    ListenkandidaturStimmzettelPosition listenkandidaturStimmzettelPosition = getStimmzettelPosition(stimmeingabe, context);
    KandidaturStimmzettelPersonendaten listenkandidaturStimmzettelPersonendaten = getStimmzettelPersonendaten(stimmeingabe, context);

    return createStimmeingabeListenkandidaturStimmen(context, stimmeingabe, kennung, bezeichnung, stimmeingabeKennung, stimmeingabeBezeichnung, stimmeingabeTooltip,
        listenkandidaturStimmzettelPosition, listenkandidaturStimmzettelPersonendaten, getStimmeingabefehlerLetzterErgebniseingang(stimmeingabe, context));
  }

  protected StimmeingabeListenkandidaturStimmen createZwischensummeEingabenListenkandidaturStimmen(GruppeImGebiet gruppeImGebiet, Long summenIndex, String summenName, String summenTooltip,
      StimmeingabeErfassenContext context) {
    return createZwischensummeEingabenListenkandidaturStimmen(gruppeImGebiet, summenIndex, summenName, summenTooltip, emptyList(), context);
  }

  protected StimmeingabeListenkandidaturStimmen createZwischensummeEingabenListenkandidaturStimmen(GruppeImGebiet gruppeImGebiet, Long summenIndex, String summenName, String summenTooltip,
      Collection<StimmeingabeListenkandidaturStimmen> stimmeingabenListenkandidaturStimmen, StimmeingabeErfassenContext context) {
    List<Stimmeingabe> stimmeingaben = AbstractStimmeingabeStimmen.extractStimmeingaben(stimmeingabenListenkandidaturStimmen);
    ZwischensummeEingabe zwischensummeEingabe = createZwischensummeEingabe(gruppeImGebiet, Stimmenart.LISTENSTIMME, summenIndex, summenName, stimmeingaben, context);

    return createZwischensummeEingabeListenkandidaturStimmen(gruppeImGebiet, zwischensummeEingabe, summenTooltip, context);
  }

  protected StimmeingabeListenkandidaturStimmen createZwischensummeEingabeListenkandidaturStimmen(GruppeImGebiet gruppeImGebiet, ZwischensummeEingabe zwischensummeEingabe, String summenTooltip,
      StimmeingabeErfassenContext context) {
    String kennung = formatListenkandidaturStimmenStimmeingabeKennung(zwischensummeEingabe, context);
    String bezeichnung = formatListenkandidaturStimmenStimmeingabeBezeichnung(zwischensummeEingabe, context);

    String zwischensummeEingabeKennung = formatListenkandidaturStimmenStimmeingabeKennung(zwischensummeEingabe, context);
    String zwischensummeEingabeBezeichnung = formatListenkandidaturStimmenStimmeingabeBezeichnung(zwischensummeEingabe, context);
    String zwischensummeEingabeTooltip = defaultIfBlank(summenTooltip, formatListenkandidaturStimmenStimmeingabeTooltip(zwischensummeEingabe, context));

    ListenkandidaturStimmzettelPosition listenkandidaturStimmzettelPosition = ListenkandidaturStimmzettelPosition.EMPTY;
    KandidaturStimmzettelPersonendaten listenkandidaturStimmzettelPersonendaten = KandidaturStimmzettelPersonendaten.EMPTY;

    return createStimmeingabeListenkandidaturStimmen(context, zwischensummeEingabe, kennung, bezeichnung, zwischensummeEingabeKennung, zwischensummeEingabeBezeichnung, zwischensummeEingabeTooltip,
        listenkandidaturStimmzettelPosition, listenkandidaturStimmzettelPersonendaten, getStimmeingabefehlerLetzterErgebniseingang(zwischensummeEingabe, context));
  }

  protected String formatListenkandidaturStimmenKennung(GruppeImGebiet gruppeImGebiet, StimmeingabeErfassenContext context) {
    return context.getListenkandidaturStimmenErfassenLabelGenerator().getListenkandidaturStimmenKennung(context.getEntityAufStimmzettelDaten(), gruppeImGebiet, context.getErgebniseingangsart(),
        context.getGruppenImGebiet());
  }

  protected String formatListenkandidaturStimmenBezeichnung(GruppeImGebiet gruppeImGebiet, StimmeingabeErfassenContext context) {
    return context.getListenkandidaturStimmenErfassenLabelGenerator().getListenkandidaturStimmenBezeichnung(context.getEntityAufStimmzettelDaten(), gruppeImGebiet, context.getErgebniseingangsart(),
        context.getGruppenImGebiet());
  }

  protected String formatListenkandidaturStimmenStimmeingabeKennung(Stimmeingabe stimmeingabe, StimmeingabeErfassenContext context) {
    return context.getListenkandidaturStimmenErfassenLabelGenerator().getListenkandidaturStimmenStimmeingabeKennung(context.getEntityAufStimmzettelDaten(), stimmeingabe, context.getGruppenImGebiet());
  }

  protected String formatListenkandidaturStimmenStimmeingabeBezeichnung(Stimmeingabe stimmeingabe, StimmeingabeErfassenContext context) {
    return context.getListenkandidaturStimmenErfassenLabelGenerator().getListenkandidaturStimmenStimmeingabeBezeichnung(context.getEntityAufStimmzettelDaten(), stimmeingabe,
        context.getGruppenImGebiet());
  }

  protected String formatListenkandidaturStimmenStimmeingabeTooltip(Stimmeingabe stimmeingabe, StimmeingabeErfassenContext context) {
    return context.getListenkandidaturStimmenErfassenLabelGenerator().getListenkandidaturStimmenStimmeingabeTooltip(context.getEntityAufStimmzettelDaten(), stimmeingabe, context.getGruppenImGebiet());
  }

  @Nullable
  protected ListenkandidaturStimmzettelPosition getStimmzettelPosition(Stimmeingabe stimmeingabe, StimmeingabeErfassenContext context) {
    if (stimmeingabe == null || stimmeingabe.isZwischensummeEingabe() || stimmeingabe.getGruppeImGebiet() == null || stimmeingabe.getGruppeImGebiet().isSystemGruppe()) { // Cf. ELECT-3253 (Übergehen unnötiger GruppeImGebiet)
      return null;
    }

    ListenkandidaturStimmzettelPosition listenkandidaturStimmzettelPosition = context.getListenkandidaturStimmenErfassenLabelGenerator()
        .getListenkandidaturStimmzettelPosition(context.getEntityAufStimmzettelDaten(), stimmeingabe, context.getGruppenImGebiet());

    if (listenkandidaturStimmzettelPosition != null && listenkandidaturStimmzettelPosition.hasPosition()) {
      return listenkandidaturStimmzettelPosition;
    }

    return null;
  }

  protected KandidaturStimmzettelPersonendaten getStimmzettelPersonendaten(Stimmeingabe stimmeingabe, StimmeingabeErfassenContext context) {
    if (stimmeingabe == null || !stimmeingabe.hasListenkandidatur()) { // Cf. ELECT-3253 (Übergehen unnötiger GruppeImGebiet)
      return KandidaturStimmzettelPersonendaten.EMPTY;
    }

    return context.getListenkandidaturStimmenErfassenLabelGenerator().getListenkandidaturStimmzettelName(context.getEntityAufStimmzettelDaten(), stimmeingabe, context.getGruppenImGebiet());
  }

  @Nullable
  protected Stimmeingabe createStimmeingabeIfStimmenartApplicable(GruppeImGebiet gruppeImGebiet, Listenkandidatur listenkandidatur, Stimmenart stimmenart, StimmeingabeErfassenContext context) {
    if (isStimmenartApplicable(gruppeImGebiet, stimmenart, context) && isListenkandidaturAufStimmzettel(gruppeImGebiet, listenkandidatur)) {
      return createStimmeingabe(gruppeImGebiet, listenkandidatur, null, stimmenart, context);
    }

    return null;
  }

  protected StimmeingabeListenkandidaturStimmen createStimmeingabeListenkandidaturStimmen(StimmeingabeErfassenContext context, Stimmeingabe stimmeingabe, String kennung, String bezeichnung,
      String stimmeingabeKennung, String stimmeingabeBezeichnung, String stimmeingabeTooltip, ListenkandidaturStimmzettelPosition listenkandidaturStimmzettelPosition,
      KandidaturStimmzettelPersonendaten listenkandidaturStimmzettelPersonendaten, List<Stimmeingabefehler> stimmeingabefehlerLetzterErgebniseingang) {
    return new StimmeingabeListenkandidaturStimmen(kennung, bezeichnung, stimmeingabe, stimmeingabeKennung, stimmeingabeBezeichnung, stimmeingabeTooltip, listenkandidaturStimmzettelPosition,
        listenkandidaturStimmzettelPersonendaten, stimmeingabefehlerLetzterErgebniseingang);
  }
}
