
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.gebietsbaum.entity.Gebiet;
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.kandidatur.entity.WaehlerBenannteKandidaturen;
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 AnzahlStimmenProWaehlerBestimmer anzahlStimmenProWaehlerBestimmer;

  private static final Result UNDEFINED = new Result(0L, emptyList(), Result.ANZAHL_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());

      Gebiet gebiet = liste.getGebiet();

      return new Result( //
          getAnzahlListenkandidaturenProWahlvorschlagMaximal(gebiet), //
          listenkandidaturenMarkiertAlsErsatzkandidatur, //
          getAnzahlErsatzkandidaturenProWahlvorschlagMaximal(gebiet, Wahlart::isWahlMitBeliebigVielenListenkandidaturenMarkiertAlsErsatzkandidaturProListe) //
      );
    }

    return UNDEFINED;
  }

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

  private boolean isListenkandidaturMarkiertAlsErsatzkandidaturHinzufuegenZulaessig(Liste liste) {
    return liste != null && !WaehlerBenannteKandidaturen.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) {
        Gebiet gebiet = wahlvorschlagKandidatur.getGebiet();

        List<Ersatzkandidatur> ersatzkandidaturenAktuell = wahlvorschlagKandidatur.getUntergeordneteErsatzkandidaturen();

        boolean direktkandidaturArtMitUntergeordneterErsatzkandidatur = wahlvorschlagKandidatur.isDirektkandidatur();

        if (direktkandidaturArtMitUntergeordneterErsatzkandidatur && isDirektkandidaturUntergeordneteErsatzkandidaturHinzufuegenZulaessig(wahl)) {
          Long anzahlDirektkandidaturenMaximal = getAnzahlDirektkandidaturenProWahlvorschlagMaximal(gebiet);
          Long anzahlErsatzkandidaturenMaximal = getAnzahlErsatzkandidaturenProWahlvorschlagMaximal(gebiet, Wahlart::isWahlMitBeliebigVielenUntergeordnetenErsatzkandidaturenProDirektkandidatur);

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

        boolean listenkandidaturArtMitUntergeordneterErsatzkandidatur = wahlvorschlagKandidatur.isListenkandidatur() && !wahlvorschlagKandidatur.isEinzelbewerber(); // ELECT-12006

        if (listenkandidaturArtMitUntergeordneterErsatzkandidatur && isListenkandidaturUntergeordneteErsatzkandidaturHinzufuegenZulaessig(wahl)) {
          Long anzahlListenkandidaturenMaximal = getAnzahlListenkandidaturenProWahlvorschlagMaximal(gebiet); // ELECTKOM-649
          Long anzahlErsatzkandidaturenMaximal = getAnzahlErsatzkandidaturenProWahlvorschlagMaximal(gebiet, Wahlart::isWahlMitBeliebigVielenUntergeordnetenErsatzkandidaturenProListenkandidatur);

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

    return UNDEFINED;
  }

  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 getAnzahlDirektkandidaturenProWahlvorschlagMaximal(Gebiet gebiet) {
    return 1L;
  }

  private long getAnzahlListenkandidaturenProWahlvorschlagMaximal(Gebiet gebiet) {
    return getAnzahlStimmenProWaehlerBestimmer().getAnzahlStimmenProWaehlerMaximal(gebiet);
  }

  private long getAnzahlErsatzkandidaturenProWahlvorschlagMaximal(Gebiet gebiet, Predicate<Wahlart> anzahlBeliebigCheck) {
    if (gebiet != null) {
      Wahl wahl = gebiet.getWahl();

      if (wahl != null) {
        Wahlart wahlart = wahl.getWahlart();

        if (wahlart != null) {
          if (anzahlBeliebigCheck.test(wahlart)) {
            return Result.ANZAHL_BELIEBIG;
          }

          return wahlart.getMaxAnzahlErsatzkandidaturen(gebiet);
        }
      }
    }

    return 0;
  }

  public static class Result implements Serializable {

    private static final long serialVersionUID = 1L;

    private static final long ANZAHL_IRRELEVANT = Long.MIN_VALUE;

    private static final long ANZAHL_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_IRRELEVANT && getAnzahlErsatzkandidaturenMaximal() > 0;
    }

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

    private long getAnzahlErsatzkandidaturenAktuell() {
      return anzahlErsatzkandidaturenAktuell;
    }

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