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

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;

import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebnisGueltigerEingangDAO;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeErfassenDescriptionResolver;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabePlausiKonfigurationDAO;
import de.ivu.elect.business.stimmeingabe.control.DaoStimmeingabeKopfzahlKonfigurationContext;
import de.ivu.elect.business.stimmeingabe.control.ErgebniseingangEingabeHashCodeGenerator;
import de.ivu.elect.business.stimmeingabe.control.PlausiBuilder;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeKopfzahlKonfiguration;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeKopfzahlKonfigurationContext;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeGruppeStimmen;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeKopfzahl;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeKopfzahlErfassenModel;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeListenkandidaturStimmen;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiFehlerart;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiKonfiguration;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiKonfigurationCustomData;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp;
import de.ivu.elect.business.wahl.entity.AbstractWahlParameterLoader;
import de.ivu.elect.business.wahl.entity.WahlParameterLoader;
import de.ivu.elect.jee.faces.Messages;

@RequestScoped
public abstract class AbstractPlausiBuilder implements PlausiBuilder {

  @Inject
  private transient StimmeingabePlausiKonfigurationDAO stimmeingabePlausiKonfigurationDAO;

  @Inject
  private transient ErgebnisGueltigerEingangDAO ergebnisGueltigerEingangDAO;

  @Inject
  private transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  @Inject
  private transient Messages messages;

  @Inject
  private transient WahlParameterLoader wahlParameterLoader;

  @Inject
  private transient StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration;

  @Inject
  private transient ErgebniseingangEingabeHashCodeGenerator ergebniseingangEingabeHashCodeGenerator;

  public AbstractPlausiBuilder() {
    super();
  }

  @Deprecated // nur für Tests
  public AbstractPlausiBuilder(Messages messages, StimmeingabeErfassenDescriptionResolver stimmeingabeErfassenDescriptionResolver, StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration) {
    super();

    this.messages = messages;

    this.stimmeingabeKopfzahlKonfiguration = stimmeingabeKopfzahlKonfiguration;
  }

  protected StimmeingabePlausiKonfiguration findStimmeingabePlausiKonfigurationOrDefaults(StimmeingabePlausiBuilderContext context, StimmeingabePlausiTyp typ, StimmeingabePlausiFehlerart fehlerart) {
    return findStimmeingabePlausiKonfigurationOrDefaults(context, typ, fehlerart, typ.newCustomDataInstance());
  }

  protected StimmeingabePlausiKonfiguration findStimmeingabePlausiKonfigurationOrDefaults(StimmeingabePlausiBuilderContext context, StimmeingabePlausiTyp typ, StimmeingabePlausiFehlerart fehlerart,
      StimmeingabePlausiKonfigurationCustomData customData) {
    Ergebniseingang ergebniseingang = context.getErgebniseingang();
    return getPlausikonfigurationCache(context).computeIfAbsent(new StimmeingabePlausiKonfigurationCacheKey(ergebniseingang.getPk(), typ, fehlerart, customData),
        x -> getStimmeingabePlausiDefinitions(ergebniseingang).findStimmeingabePlausiKonfigurationOrLegacyDefaults(getStimmeingabePlausiDefinitionsContext(), getStimmeingabeKopfzahlKonfiguration(),
            ergebniseingang, typ, fehlerart, customData));
  }

  protected abstract AbstractStimmeingabePlausiDefinitions getStimmeingabePlausiDefinitions(Ergebniseingang ergebniseingang);

  protected StimmeingabePlausiDefinitionsContext getStimmeingabePlausiDefinitionsContext() {
    return new StimmeingabePlausiDefinitionsContext(getStimmeingabePlausiKonfigurationDAO(), getWahlParameterLoader());
  }

  public Map<StimmeingabePlausiKonfigurationCacheKey, StimmeingabePlausiKonfiguration> getPlausikonfigurationCache(StimmeingabePlausiBuilderContext context) {
    return (Map<StimmeingabePlausiKonfigurationCacheKey, StimmeingabePlausiKonfiguration>) context.getCache().computeIfAbsent("PlausikonfigurationCache", x -> new HashMap<>());
  }

  protected Messages getMessages() {
    return messages;
  }

  protected StimmeingabePlausiKonfigurationDAO getStimmeingabePlausiKonfigurationDAO() {
    return stimmeingabePlausiKonfigurationDAO;
  }

  protected ErgebnisGueltigerEingangDAO getErgebnisGueltigerEingangDAO() {
    return ergebnisGueltigerEingangDAO;
  }

  protected AbstractWahlParameterLoader getWahlParameterLoader() {
    return wahlParameterLoader;
  }

  protected StimmeingabeKopfzahlKonfiguration getStimmeingabeKopfzahlKonfiguration() {
    return stimmeingabeKopfzahlKonfiguration;
  }

  protected ErgebniseingangEingabeHashCodeGenerator getErgebniseingangEingabeHashCodeGenerator() {
    return ergebniseingangEingabeHashCodeGenerator;
  }

  protected boolean isBriefwahl(StimmeingabePlausiBuilderContext context) {
    return context.isBriefwahl();
  }

  protected boolean isSonderstimmbezirk(StimmeingabePlausiBuilderContext context) {
    return context.getGebiet().getSonderstimmbezirk();
  }

  protected void addPlausiListenkandidaturStimme(StimmeingabeListenkandidaturStimmen stimmeingabeListenkandidaturStimmen, StimmeingabePlausi stimmeingabePlausi,
      StimmeingabePlausiBuilderContext context) {
    addPlausi(stimmeingabeListenkandidaturStimmen.getStimmeingabe(), stimmeingabePlausi, context);
  }

  protected void addPlausiListenstimme(StimmeingabeGruppeStimmen stimmeingabeGruppeStimmen, StimmeingabePlausi stimmeingabePlausi, StimmeingabePlausiBuilderContext context) {
    addPlausi(stimmeingabeGruppeStimmen.getStimmeingabeListenstimme(), stimmeingabePlausi, context);
  }

  protected void addPlausiDirektstimme(StimmeingabeGruppeStimmen stimmeingabeGruppeStimmen, StimmeingabePlausi stimmeingabePlausi, StimmeingabePlausiBuilderContext context) {
    addPlausi(stimmeingabeGruppeStimmen.getStimmeingabeDirektstimme(), stimmeingabePlausi, context);
  }

  protected void addPlausiKopfzahl(StimmeingabeKopfzahl stimmeingabeKopfzahl, StimmeingabePlausi stimmeingabePlausi, StimmeingabePlausiBuilderContext context) {
    addPlausi(stimmeingabeKopfzahl.getStimmeingabe(), stimmeingabePlausi, context);
  }

  protected void addPlausi(Stimmeingabe stimmeingabe, StimmeingabePlausi stimmeingabePlausi, StimmeingabePlausiBuilderContext context) {
    if (context.isStimmeingabeZuPlausibilisieren(stimmeingabe)) {
      stimmeingabe.addStimmeingabePlausi(stimmeingabePlausi);
    }
  }

  protected boolean isWahlberechtigteOhneWahlscheinZuPlausibilisieren(StimmeingabeKopfzahlErfassenModel model) {
    return model.hasWahlberechtigteOhneWahlschein()
        && getStimmeingabeKopfzahlKonfiguration().isWahlberechtigteOhneWahlscheinBeiStimmeingabeZuPlausibilisieren(model.getBezugGebiet(),
        model.getErgebniseingangsart(), getStimmeingabeKopfzahlKonfigurationContext());
  }

  protected boolean isWahlberechtigteMitWahlscheinZuPlausibilisieren(StimmeingabeKopfzahlErfassenModel model) {
    return model.hasWahlberechtigteMitWahlschein()
        && getStimmeingabeKopfzahlKonfiguration().isWahlberechtigteMitWahlscheinBeiStimmeingabeZuPlausibilisieren(model.getBezugGebiet(), model.getErgebniseingangsart(), getStimmeingabeKopfzahlKonfigurationContext());
  }

  protected boolean isWahlberechtigteNichtImWaehlerverzeichnisZuPlausibilisieren(StimmeingabeKopfzahlErfassenModel model) {
    return model.hasWahlberechtigteNichtImWaehlerverzeichnis()
        && getStimmeingabeKopfzahlKonfiguration().isWahlberechtigteNichtImWaehlerverzeichnisBeiStimmeingabeZuPlausibilisieren(model.getBezugGebiet(), model.getErgebniseingangsart(), getStimmeingabeKopfzahlKonfigurationContext());
  }

  protected boolean isWahlberechtigteGesamtZuPlausibilisieren(StimmeingabeKopfzahlErfassenModel model) {
    return model.hasWahlberechtigteGesamt() && isWahlberechtigteGesamtZuPlausibilisieren(getStimmeingabeKopfzahlKonfiguration(), model.getBezugGebiet(),
        model.getErgebniseingangsart(), getStimmeingabeKopfzahlKonfigurationContext());
  }

  protected static boolean isWahlberechtigteGesamtZuPlausibilisieren(StimmeingabeKopfzahlKonfiguration stimmeingabeKopfzahlKonfiguration,
                                                               Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet,
      Ergebniseingangsart ergebniseingangsart,  StimmeingabeKopfzahlKonfigurationContext context) {
    return stimmeingabeKopfzahlKonfiguration.isWahlberechtigteGesamtBeiStimmeingabeZuPlausibilisieren(wahldurchfuehrungsgebiet, ergebniseingangsart, context);
  }

  protected boolean isWaehlerOhneWahlscheinZuPlausibilisieren(StimmeingabeKopfzahlErfassenModel model) {
    return model.hasWaehlerOhneWahlschein() && getStimmeingabeKopfzahlKonfiguration().isWaehlerOhneWahlscheinBeiStimmeingabeZuPlausibilisieren(model.getBezugGebiet(), model.getErgebniseingangsart());
  }

  protected boolean isWaehlerMitWahlscheinZuPlausibilisieren(StimmeingabeKopfzahlErfassenModel model) {
    return model.hasWaehlerMitWahlschein() && getStimmeingabeKopfzahlKonfiguration().isWaehlerMitWahlscheinBeiStimmeingabeZuPlausibilisieren(model.getBezugGebiet(), model.getErgebniseingangsart());
  }

  protected boolean isWaehlerMitWahlscheinInUrnenwahlAnteilAusUrnenwahlZuPlausibilisieren(StimmeingabeKopfzahlErfassenModel model) {
    return model.hasWaehlerMitWahlscheinInUrnenwahlAnteilAusUrnenwahl()
        && getStimmeingabeKopfzahlKonfiguration().isWaehlerMitWahlscheinInUrnenwahlAnteilAusUrnenwahlBeiStimmeingabeZuPlausibilisieren(model.getBezugGebiet(), model.getErgebniseingangsart());
  }

  protected boolean isWaehlerMitWahlscheinInUrnenwahlAnteilAusBriefwahlZuPlausibilisieren(StimmeingabeKopfzahlErfassenModel model) {
    return model.hasWaehlerMitWahlscheinInUrnenwahlAnteilAusBriefwahl()
        && getStimmeingabeKopfzahlKonfiguration().isWaehlerMitWahlscheinInUrnenwahlAnteilAusBriefwahlBeiStimmeingabeZuPlausibilisieren(model.getBezugGebiet(), model.getErgebniseingangsart());
  }

  protected boolean isWaehlerGesamtZuPlausibilisieren(StimmeingabeKopfzahlErfassenModel model) {
    return model.hasWaehlerGesamt() && getStimmeingabeKopfzahlKonfiguration().isWaehlerGesamtBeiStimmeingabeZuPlausibilisieren(model.getBezugGebiet(), model.getErgebniseingangsart());
  }


  protected StimmeingabeKopfzahlKonfigurationContext getStimmeingabeKopfzahlKonfigurationContext() {
    if (wahldurchfuehrungsgebietDAO==null) {
      return () -> x-> Collections.emptyList();
    }
    return new DaoStimmeingabeKopfzahlKonfigurationContext(wahldurchfuehrungsgebietDAO);
  }
}
