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

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

import javax.annotation.Nonnull;
import javax.inject.Inject;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.kandidatur.boundary.WaehlerBenannteKandidaturKonfiguration;
import de.ivu.elect.business.sitzberechnung.control.ListenwahlMehrheitswahlPruefer;
import de.ivu.elect.business.sitzberechnung.control.ListenwahlMehrheitswahlPrueferFactory;
import de.ivu.elect.business.wahl.entity.CommonWahlart;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;

public class AnzahlStimmenProWaehlerBestimmer {

  @Inject
  private transient WaehlerBenannteKandidaturKonfiguration waehlerBenannteKandidaturKonfiguration;

  @Inject
  private transient ListenwahlMehrheitswahlPrueferFactory listenwahlMehrheitswahlPrueferFactory;

  private final Map<Gebiet, Long> anzahlStimmenProWaehlerProDirektkandidaturMaximalPerGebiet;

  private final Map<Gebiet, Long> anzahlStimmenProWaehlerProListenkandidaturMaximalPerGebiet;

  public AnzahlStimmenProWaehlerBestimmer() {
    super();

    this.anzahlStimmenProWaehlerProDirektkandidaturMaximalPerGebiet = new HashMap<>();
    this.anzahlStimmenProWaehlerProListenkandidaturMaximalPerGebiet = new HashMap<>();
  }

  // ELECT-13860, ELECT-14304
  public boolean isSitzverteilungPerMehrheitswahl(Gebiet gebiet, Supplier<Collection<? extends Wahlvorschlag>> wahlvorschlaegeFct) {
    if (gebiet != null) {
      Wahl wahl = gebiet.getWahl();

      if (wahl != null) {
        if (CommonWahlart.BUERGERMEISTERWAHL.equalsWahlart(wahl)) {
          return true;
        }

        if (wahl.isWahlMitListengebieten()) {
          return getListenwahlMehrheitswahlPruefer(wahl).isMehrheitswahl(gebiet, () -> new ArrayList<>(wahlvorschlaegeFct.get()));
        } else if (wahl.isWahlMitDirektkandidatengebieten()) {
          return true;
        }
      }
    }

    return false;
  }

  public boolean isWaehlerBenannteWahlvorschlaegeErfassbar(Gebiet gebiet, Supplier<Collection<? extends Wahlvorschlag>> wahlvorschlaegeFct) {
    return getWaehlerBenannteKandidaturKonfiguration().isWaehlerBenannteWahlvorschlaegeManuellErfassbar(gebiet, wahlvorschlaegeFct);
  }

  public long getAnzahlStimmenProWaehlerMaximal(Gebiet gebiet) {
    if (gebiet != null) {
      Wahl wahl = gebiet.getWahl();

      if (wahl != null) {
        return getAnzahlStimmenProWaehlerMaximalConfigured(wahl, gebiet);
      }
    }

    return 0L;
  }

  protected long getAnzahlStimmenProWaehlerMaximalConfigured(@Nonnull Wahl wahl, @Nonnull Gebiet gebiet) {
    try {
      return fixNullToZero(wahl.getWahlart().getAnzahlStimmenProWaehler(gebiet));
    } catch (Exception ignored) { // Cf. WahlartStimmenProWaehlerArt#ANZAHL_SITZE_WAHLGEBIET#getAnzahlStimmenProWaehler!!!
    }

    return 0L;
  }

  public long getAnzahlStimmenProWaehlerProDirektkandidaturMaximal(Gebiet gebiet, Function<Gebiet, Collection<Direktkandidat>> wahlvorschlaegeInDirektwahlgebietFct) {
    if (gebiet != null) {
      Wahl wahl = gebiet.getWahl();

      if (wahl != null) {
        return getAnzahlStimmenProWaehlerProDirektkandidaturMaximal(wahl, gebiet, wahlvorschlaegeInDirektwahlgebietFct);
      }
    }

    return 0L;
  }

  protected long getAnzahlStimmenProWaehlerProDirektkandidaturMaximal(@Nonnull Wahl wahl, @Nonnull Gebiet gebiet, Function<Gebiet, Collection<Direktkandidat>> wahlvorschlaegeInDirektwahlgebietFct) {
    if (wahl.isWahlMitDirektkandidatengebieten()) {
      if (!anzahlStimmenProWaehlerProDirektkandidaturMaximalPerGebiet.containsKey(gebiet)) {
        long result = getAnzahlStimmenProWaehlerProDirektkandidaturMaximalByWahlart(wahl);

        if (result > 1L) {
          Gebiet direktwahlgebiet = gebiet.getUebergeordnetesDirektwahlgebiet();

          if (direktwahlgebiet != null) {
            if (anzahlStimmenProWaehlerProDirektkandidaturMaximalPerGebiet.containsKey(direktwahlgebiet)) {
              result = anzahlStimmenProWaehlerProDirektkandidaturMaximalPerGebiet.getOrDefault(direktwahlgebiet, result);
            } else {
              Collection<Direktkandidat> direktkandidaten = wahlvorschlaegeInDirektwahlgebietFct.apply(direktwahlgebiet);

              if (direktkandidaten == null || direktkandidaten.isEmpty()) {
                direktkandidaten = wahlvorschlaegeInDirektwahlgebietFct.apply(gebiet);

                if (direktkandidaten == null) {
                  direktkandidaten = new ArrayList<>();
                }
              }

              long resultByGebiet = getAnzahlStimmenProWaehlerProDirektkandidaturMaximalByGebiet(gebiet, direktkandidaten);

              if (resultByGebiet > 0L) {
                result = resultByGebiet;
              }

              anzahlStimmenProWaehlerProDirektkandidaturMaximalPerGebiet.put(direktwahlgebiet, result);
            }
          }
        }

        anzahlStimmenProWaehlerProDirektkandidaturMaximalPerGebiet.put(gebiet, result);
      }

      return anzahlStimmenProWaehlerProDirektkandidaturMaximalPerGebiet.getOrDefault(gebiet, 0L);
    }

    return 0L;
  }

  protected long getAnzahlStimmenProWaehlerProDirektkandidaturMaximalByWahlart(@Nonnull Wahl wahl) {
    return 1L; // Momentan noch nicht konfigurierbar!
  }

  protected long getAnzahlStimmenProWaehlerProDirektkandidaturMaximalByGebiet(@Nonnull Gebiet gebiet, Collection<Direktkandidat> direktkandidaten) {
    if (isWaehlerBenannteWahlvorschlaegeErfassbar(gebiet, () -> direktkandidaten)) {
      return 1L; // ELECT-9359
    }

    return 0L;
  }

  public long getAnzahlStimmenProWaehlerProListenkandidaturMaximal(Gebiet gebiet, Function<Gebiet, Collection<Liste>> wahlvorschlaegeInListenwahlgebietFct) {
    if (gebiet != null) {
      Wahl wahl = gebiet.getWahl();

      if (wahl != null) {
        return getAnzahlStimmenProWaehlerProListenkandidaturMaximal(wahl, gebiet, wahlvorschlaegeInListenwahlgebietFct);
      }
    }

    return 0L;
  }

  protected long getAnzahlStimmenProWaehlerProListenkandidaturMaximal(@Nonnull Wahl wahl, @Nonnull Gebiet gebiet, Function<Gebiet, Collection<Liste>> wahlvorschlaegeInListenwahlgebietFct) {
    if (!anzahlStimmenProWaehlerProListenkandidaturMaximalPerGebiet.containsKey(gebiet)) {
      long result = getAnzahlStimmenProWaehlerProListenkandidaturMaximalByWahlart(wahl);

      if (result > 1L) {
        Gebiet listenwahlgebiet = gebiet.getUebergeordnetesListenwahlgebiet();

        if (listenwahlgebiet != null) {
          if (anzahlStimmenProWaehlerProListenkandidaturMaximalPerGebiet.containsKey(listenwahlgebiet)) {
            result = anzahlStimmenProWaehlerProListenkandidaturMaximalPerGebiet.getOrDefault(listenwahlgebiet, result);
          } else {
            Collection<Liste> listen = wahlvorschlaegeInListenwahlgebietFct.apply(listenwahlgebiet);

            if (listen == null || listen.isEmpty()) {
              listen = wahlvorschlaegeInListenwahlgebietFct.apply(gebiet);

              if (listen == null) {
                listen = new ArrayList<>();
              }
            }

            long resultByGebiet = getAnzahlStimmenProWaehlerProListenkandidaturMaximalByGebiet(gebiet, listen);

            if (resultByGebiet > 0) {
              result = resultByGebiet;
            }

            anzahlStimmenProWaehlerProListenkandidaturMaximalPerGebiet.put(listenwahlgebiet, result);
          }
        }
      }

      anzahlStimmenProWaehlerProListenkandidaturMaximalPerGebiet.put(gebiet, result);
    }

    return anzahlStimmenProWaehlerProListenkandidaturMaximalPerGebiet.getOrDefault(gebiet, 0L);
  }

  protected long getAnzahlStimmenProWaehlerProListenkandidaturMaximalByWahlart(@Nonnull Wahl wahl) {
    try {
      // NOTE, dass momentan nicht zwischen Anzahl Nennungen pro Listenkandidatur maximal und Anzahl Stimmen pro Wähler pro Listenkandidatur maximal unterschieden wird!
      return fixNullToZero(wahl.getWahlart().getMaxAnzahlNennungen());
    } catch (Exception ignored) {
    }

    return 0L;
  }

  protected long getAnzahlStimmenProWaehlerProListenkandidaturMaximalByGebiet(@Nonnull Gebiet gebiet, Collection<Liste> listen) {
    if (isWaehlerBenannteWahlvorschlaegeErfassbar(gebiet, () -> listen)) {
      return 1L; // ELECT-9359
    }

    return 0L;
  }

  protected WaehlerBenannteKandidaturKonfiguration getWaehlerBenannteKandidaturKonfiguration() {
    return waehlerBenannteKandidaturKonfiguration;
  }

  protected ListenwahlMehrheitswahlPruefer getListenwahlMehrheitswahlPruefer(Wahl wahl) {
    return listenwahlMehrheitswahlPrueferFactory.createMehrheitswahlPruefer(wahl);
  }

  private long fixNullToZero(Long value) {
    if (value != null) {
      return value;
    }

    return 0L;
  }
}
