
package de.ivu.elect.dokumente;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.annotation.Nullable;

import de.ivu.elect.business.ergebnis.control.ErgebnisDatenQuorum;
import de.ivu.elect.business.gebietsbaum.boundary.AbstractGebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietPK;
import de.ivu.elect.business.gruppe.boundary.AbstractGruppeDAO;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.kandidatur.boundary.WaehlerBenannteKandidaturKonfiguration;
import de.ivu.elect.business.kandidatur.control.StimmzettelFormatter;
import de.ivu.elect.business.stimmzettel.boundary.AnzahlStimmenProWaehlerBestimmer;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlausschuss.boundary.AbstractWahlausschussEntschaedigungDAO;
import de.ivu.elect.business.wahlvorschlag.boundary.AbstractWahlvorschlagDAO;
import de.ivu.elect.business.wahlvorschlag.boundary.WahlvorschlagUnterstuetzerNotwendigErmittler;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;

public class CoreElectContext extends BaseElectContext {

  private static final long serialVersionUID = 1L;

  private final Optional<BigDecimal> quorumProzenthuerde;

  private final AnzahlStimmenProWaehlerBestimmer anzahlStimmenProWaehlerBestimmer;

  private final StimmzettelFormatter stimmzettelFormatter;

  private final AbstractGruppeDAO gruppeDAO;

  private final AbstractGebietDAO gebietDAO;

  private final AbstractWahlvorschlagDAO wahlvorschlagDAO;

  private final AbstractWahlausschussEntschaedigungDAO wahlausschussEntschaedigungDAO;

  private final WahlvorschlagUnterstuetzerNotwendigErmittler wahlvorschlagUnterstuetzerNotwendigErmittler;

  private final WaehlerBenannteKandidaturKonfiguration waehlerBenannteKandidaturKonfiguration; // ELECT-14303

  private final Map<GebietPK, Boolean> sitzverteilungMehrheitswahlPerGebietPk; // ELECT-13860

  public CoreElectContext(CoreElectContext other) {
    this(other, other.getAnzahlStimmenProWaehlerBestimmer(), other.getStimmzettelFormatter(), other.getGruppeDAO(), other.getGebietDAO(), other.getWahlausschussEntschaedigungDAO(),
        other.getWahlvorschlagDAO(), other.getWahlvorschlagUnterstuetzerNotwendigErmittler(), other.getWaehlerBenannteKandidaturKonfiguration());
  }

  public CoreElectContext(BaseElectContext baseElectContext, AnzahlStimmenProWaehlerBestimmer anzahlStimmenProWaehlerBestimmer, StimmzettelFormatter stimmzettelFormatter, AbstractGruppeDAO gruppeDAO,
      AbstractGebietDAO gebietDAO, AbstractWahlausschussEntschaedigungDAO wahlausschussEntschaedigungDAO, AbstractWahlvorschlagDAO wahlvorschlagDAO,
      WahlvorschlagUnterstuetzerNotwendigErmittler wahlvorschlagUnterstuetzerNotwendigErmittler, WaehlerBenannteKandidaturKonfiguration waehlerBenannteKandidaturKonfiguration) {
    super(baseElectContext);

    this.quorumProzenthuerde = Optional.ofNullable(baseElectContext.getWahl()).map(Wahl::getQuorumProzenthuerde);

    this.anzahlStimmenProWaehlerBestimmer = anzahlStimmenProWaehlerBestimmer;

    this.stimmzettelFormatter = stimmzettelFormatter;

    this.gruppeDAO = gruppeDAO;
    this.gebietDAO = gebietDAO;
    this.wahlvorschlagDAO = wahlvorschlagDAO;

    this.wahlausschussEntschaedigungDAO = wahlausschussEntschaedigungDAO;

    this.wahlvorschlagUnterstuetzerNotwendigErmittler = wahlvorschlagUnterstuetzerNotwendigErmittler;

    this.waehlerBenannteKandidaturKonfiguration = waehlerBenannteKandidaturKonfiguration;

    this.sitzverteilungMehrheitswahlPerGebietPk = new HashMap<>();
  }

  public Optional<BigDecimal> getQuorumProzenthuerde() {
    return quorumProzenthuerde;
  }

  @Override
  public Long getAnzahlStimmenProWaehlerMaximal(Gebiet gebiet) {
    return getAnzahlStimmenProWaehlerBestimmer().getAnzahlStimmenProWaehlerMaximal(gebiet);
  }

  public AnzahlStimmenProWaehlerBestimmer getAnzahlStimmenProWaehlerBestimmer() {
    return anzahlStimmenProWaehlerBestimmer;
  }

  public StimmzettelFormatter getStimmzettelFormatter() {
    return stimmzettelFormatter;
  }

  public AbstractGruppeDAO getGruppeDAO() {
    return gruppeDAO;
  }

  public AbstractGebietDAO getGebietDAO() {
    return gebietDAO;
  }

  public AbstractWahlvorschlagDAO getWahlvorschlagDAO() {
    return wahlvorschlagDAO;
  }

  public AbstractWahlausschussEntschaedigungDAO getWahlausschussEntschaedigungDAO() {
    return wahlausschussEntschaedigungDAO;
  }

  public WahlvorschlagUnterstuetzerNotwendigErmittler getWahlvorschlagUnterstuetzerNotwendigErmittler() {
    return wahlvorschlagUnterstuetzerNotwendigErmittler;
  }

  public WaehlerBenannteKandidaturKonfiguration getWaehlerBenannteKandidaturKonfiguration() {
    return waehlerBenannteKandidaturKonfiguration;
  }

  public boolean isSitzverteilungPerMehrheitswahl(GebietPK gebietPk) {
    return gebietPk != null && Boolean.TRUE.equals(sitzverteilungMehrheitswahlPerGebietPk.computeIfAbsent(gebietPk, pk -> {
      Gebiet gebiet = getGebietDAO().findByPrimaryKey(pk);

      return getAnzahlStimmenProWaehlerBestimmer().isSitzverteilungPerMehrheitswahl(gebiet, () -> getWahlvorschlaege(gebiet.getWahl()));
    }));
  }

  @Nullable
  public static Long berechneAnzahlStimmenProzenthuerdeErreicht(@Nullable BigDecimal prozenthuerde, @Nullable Long anzahlStimmen) {
    if (prozenthuerde == null || anzahlStimmen == null) {
      return null;
    }

    return ErgebnisDatenQuorum.calculateAnzahlQuorumErreicht(prozenthuerde, anzahlStimmen);
  }

  @Nullable
  public static Long berechneAnzahlStimmenAbsoluteMehrheit(@Nullable Long anzahlStimmen) {
    Double anzahlStimmenHaelfte = berechneAnzahlStimmenHaelfte(anzahlStimmen);

    if (anzahlStimmenHaelfte == null) {
      return null;
    }

    if (anzahlStimmenHaelfte > 0L) {
      return (long) (anzahlStimmenHaelfte + 1L);
    }

    return 0L;
  }

  @Nullable
  public static Double berechneAnzahlStimmenHaelfte(@Nullable Long anzahlStimmen) {
    if (anzahlStimmen == null) {
      return null;
    }

    if (anzahlStimmen > 0L) {
      return anzahlStimmen / 2.0D;
    }

    return 0.0D;
  }

  @Override
  public CoreElectCustomDataDocumentBuilder getCustomDataDocumentBuilder() {
    return (CoreElectCustomDataDocumentBuilder) super.getCustomDataDocumentBuilder();
  }

  public List<Gruppe> getGruppen(Wahl wahl) {
    String key = "CoreElectContext-Gruppen" + wahl.getId();
    List result = computeIfAbsent(key, List.class, x -> getGruppeDAO().findByWahl(wahl));
    return result;
  }

  public List<Gebiet> getGebiete(Wahl wahl) {
    String key = "CoreElectContext-Gebiete" + wahl.getId();
    List result = computeIfAbsent(key, List.class, x -> getGebietDAO().findByWahl(wahl));
    return result;
  }

  public List<Wahlvorschlag> getWahlvorschlaege(Wahl wahl) {
    String key = "CoreElectContext-Wahlvorschlaege" + wahl.getId();
    List result = computeIfAbsent(key, List.class, x -> getWahlvorschlagDAO().findByWahl(wahl));
    return result;
  }
}
