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

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toSet;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;

import javax.inject.Inject;

import de.ivu.elect.business.kandidatur.entity.Direktkandidatur;
import de.ivu.elect.business.kandidatur.entity.Ersatzkandidatur;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.stimmzettel.boundary.AnzahlStimmenProWaehlerBestimmer;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahlart;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;

public class ErsatzkandidaturPruefer implements Serializable {

  private static final long serialVersionUID = 1L;

  @Inject
  private transient WaehlerBenannteKandidaturKonfiguration waehlerBenannteKandidaturKonfiguration;

  @Inject
  private transient AnzahlStimmenProWaehlerBestimmer anzahlStimmenProWaehlerBestimmer;

  private static final Result UNDEFINED = new Result(0L, emptyList(), Result.ANZAHL_ERSATZKANDIDATUREN_IRRELEVANT);

  public ErsatzkandidaturPruefer() {
    super();
  }

  public Result checkListenkandidaturMarkiertAlsErsatzkandidaturHinzufuegen(Wahl wahl, Listenkandidatur listenkandidatur) {
    if (listenkandidatur != null) {
      return checkListenkandidaturMarkiertAlsErsatzkandidaturHinzufuegen(wahl, listenkandidatur.getListe());
    }

    return UNDEFINED;
  }

  public Result checkListenkandidaturMarkiertAlsErsatzkandidaturHinzufuegen(Wahl wahl, Liste liste) {
    if (isListenkandidaturMarkiertAlsErsatzkandidaturHinzufuegenZulaessig(wahl) && isListenkandidaturMarkiertAlsErsatzkandidaturHinzufuegenZulaessig(liste)) {
      Collection<Listenkandidatur> listenkandidaturenMarkiertAlsErsatzkandidatur = liste.getListenkandidaturen().stream() //
          .filter(Listenkandidatur::isListenkandidaturMarkiertAlsErsatzkandidatur) //
          .collect(toSet());

      return new Result( //
          getAnzahlStimmenProWaehlerBestimmer().getAnzahlStimmenProWaehlerMaximal(liste.getGebiet()), //
          listenkandidaturenMarkiertAlsErsatzkandidatur, //
          getMaxAnzahlOrDefaultIfBeliebig(wahl, Wahlart::isWahlMitBeliebigVielenListenkandidaturenMarkiertAlsErsatzkandidaturProListe) //
      );
    }

    return UNDEFINED;
  }

  private boolean isListenkandidaturMarkiertAlsErsatzkandidaturHinzufuegenZulaessig(Wahl wahl) {
    return isWahlMitErsatzkandidaturen(wahl) && !isListenkandidaturUntergeordneteErsatzkandidaturHinzufuegenZulaessig(wahl);
  }

  private boolean isListenkandidaturMarkiertAlsErsatzkandidaturHinzufuegenZulaessig(Liste liste) {
    return liste != null && !getWaehlerBenannteKandidaturKonfiguration().isWaehlerBenannteListe(liste);
  }

  public Result checkUntergeordneteErsatzkandidaturHinzufuegen(Wahl wahl, Listenkandidatur listenkandidatur) {
    return createResult(wahl, listenkandidatur);
  }

  public Result checkUntergeordneteErsatzkandidaturHinzufuegen(Wahl wahl, Direktkandidatur direktkandidatur) {
    return createResult(wahl, direktkandidatur);
  }

  public Result checkUntergeordneteErsatzkandidaturHinzufuegen(Wahl wahl, Ersatzkandidatur ersatzkandidatur) {
    return createResult(wahl, ersatzkandidatur);
  }

  private Result createResult(Wahl wahl, Kandidatur kandidatur) {
    if (kandidatur != null && !kandidatur.hasZugeordneteErsatzkandidatur()) {
      Kandidatur wahlvorschlagKandidatur = kandidatur;

      if (kandidatur.isErsatzkandidatur()) {
        wahlvorschlagKandidatur = castTo(kandidatur, Ersatzkandidatur.class).getWahlvorschlagKandidatur();
      }

      if (wahlvorschlagKandidatur != null) {
        Long anzahlKandidaturenMaximal = getAnzahlStimmenProWaehlerBestimmer().getAnzahlStimmenProWaehlerMaximal(wahlvorschlagKandidatur.getGebiet()); // ELECTKOM-649
        List<Ersatzkandidatur> ersatzkandidaturenAktuell = wahlvorschlagKandidatur.getUntergeordneteErsatzkandidaturen();

        if (wahlvorschlagKandidatur.isDirektkandidatur() && isDirektkandidaturUntergeordneteErsatzkandidaturHinzufuegenZulaessig(wahl)) {
          Long anzahlErsatzkandidaturenMaximal = getMaxAnzahlOrDefaultIfBeliebig(wahl, Wahlart::isWahlMitBeliebigVielenUntergeordnetenErsatzkandidaturenProDirektkandidatur);

          return new Result(anzahlKandidaturenMaximal, ersatzkandidaturenAktuell, anzahlErsatzkandidaturenMaximal);
        }

        if (wahlvorschlagKandidatur.isListenkandidatur() && isListenkandidaturUntergeordneteErsatzkandidaturHinzufuegenZulaessig(wahl)) {
          Long anzahlErsatzkandidaturenMaximal = getMaxAnzahlOrDefaultIfBeliebig(wahl, Wahlart::isWahlMitBeliebigVielenUntergeordnetenErsatzkandidaturenProListenkandidatur);

          return new Result(anzahlKandidaturenMaximal, ersatzkandidaturenAktuell, anzahlErsatzkandidaturenMaximal);
        }
      }
    }

    return UNDEFINED;
  }

  protected WaehlerBenannteKandidaturKonfiguration getWaehlerBenannteKandidaturKonfiguration() {
    return waehlerBenannteKandidaturKonfiguration;
  }

  protected AnzahlStimmenProWaehlerBestimmer getAnzahlStimmenProWaehlerBestimmer() {
    return anzahlStimmenProWaehlerBestimmer;
  }

  protected boolean isDirektkandidaturUntergeordneteErsatzkandidaturHinzufuegenZulaessig(Wahl wahl) {
    return isWahlMitErsatzkandidaturen(wahl);
  }

  protected boolean isListenkandidaturUntergeordneteErsatzkandidaturHinzufuegenZulaessig(Wahl wahl) {
    return false; // ELECT-8924
  }

  private boolean isWahlMitErsatzkandidaturen(Wahl wahl) {
    return wahl != null && wahl.isWahlMitErsatzkandidaturen();
  }

  private long getMaxAnzahlOrDefaultIfBeliebig(Wahl wahl, Predicate<Wahlart> anzahlErsatzkandidaturenBeliebigCheck) {
    if (wahl != null) {
      Wahlart wahlart = wahl.getWahlart();

      if (wahlart != null) {
        if (anzahlErsatzkandidaturenBeliebigCheck.test(wahlart)) {
          return Result.ANZAHL_ERSATZKANDIDATUREN_BELIEBIG;
        }

        Long result = wahlart.getMaxAnzahlErsatzkandidaturen();

        if (result != null) {
          return result;
        }
      }
    }

    return 0;
  }

  public static class Result implements Serializable {

    private static final long serialVersionUID = 1L;

    private static final long ANZAHL_ERSATZKANDIDATUREN_IRRELEVANT = Long.MIN_VALUE;

    private static final long ANZAHL_ERSATZKANDIDATUREN_BELIEBIG = Long.MAX_VALUE;

    private final long anzahlKandidaturenMaximal;

    private final long anzahlErsatzkandidaturenAktuell;

    private final long anzahlErsatzkandidaturenMaximal;

    public Result(long anzahlKandidaturenMaximal, Collection< ? extends Kandidatur> ersatzkandidaturenAktuell, Long anzahlErsatzkandidaturenMaximal) {
      super();

      this.anzahlKandidaturenMaximal = anzahlKandidaturenMaximal;

      this.anzahlErsatzkandidaturenAktuell = ersatzkandidaturenAktuell.size();
      this.anzahlErsatzkandidaturenMaximal = anzahlErsatzkandidaturenMaximal;
    }

    public long getAnzahlKandidaturenMaximal() {
      return anzahlKandidaturenMaximal;
    }

    public boolean isWeitereErsatzkandidaturZulaessig() {
      return isAnzahlErsatzkandidaturenMaximalBeliebig() || isAnzahlErsatzkandidaturenMaximalKonfiguriert() && getAnzahlErsatzkandidaturenAktuell() < getAnzahlErsatzkandidaturenMaximal();
    }

    public boolean isAnzahlErsatzkandidaturenMaximalKonfiguriert() {
      return getAnzahlErsatzkandidaturenMaximal() != ANZAHL_ERSATZKANDIDATUREN_IRRELEVANT && getAnzahlErsatzkandidaturenMaximal() > 0;
    }

    private boolean isAnzahlErsatzkandidaturenMaximalBeliebig() {
      return getAnzahlErsatzkandidaturenMaximal() == ANZAHL_ERSATZKANDIDATUREN_BELIEBIG;
    }

    private long getAnzahlErsatzkandidaturenAktuell() {
      return anzahlErsatzkandidaturenAktuell;
    }

    public long getAnzahlErsatzkandidaturenMaximal() {
      return anzahlErsatzkandidaturenMaximal;
    }
  }
}
