
package de.ivu.elect.business.stimmeingabe.entity;

import static java.util.Collections.emptyList;

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.annotation.Nonnull;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;

public class StimmeingabeGruppeStimmenErfassenModel extends AbstractStimmenErfassenModel {

  /**
   * Dummy-SystemGruppe (aus den Kopfzahlen), die normalerweise KEINE Direkt- oder Listenstimmen erhält. Dient zur Anzeige einer zusätzlichen "Custom"-Stimmeingabe neben den Stimmeingaben für Gültige
   * und Ungültige, bspw. "Erst- und Zweitstimmen insgesamt" oder "Gültige Stimmzettel".
   */
  private static final SystemGruppe SYSTEM_GRUPPE_CUSTOM = SystemGruppe.WAEHLER;

  private final Map<SystemGruppe, StimmeingabeGruppeStimmen> stimmenSystemGruppePerSystemGruppe;

  private StimmeingabeGruppeStimmen stimmenSystemGruppeUnbestimmte; // ELECT-13792

  private final List<StimmeingabeGruppeStimmen> stimmenPolitischeGruppenReadonly;

  private final List<StimmeingabeGruppeStimmen> stimmenPolitischeGruppen;

  public StimmeingabeGruppeStimmenErfassenModel(Wahldurchfuehrungsgebiet bezugGebiet) {
    super(bezugGebiet);

    this.stimmenSystemGruppePerSystemGruppe = new EnumMap<>(SystemGruppe.class);

    this.stimmenPolitischeGruppenReadonly = new ArrayList<>();
    this.stimmenPolitischeGruppen = new ArrayList<>();
  }

  public boolean isWaehlerBenannteWahlvorschlaegeErfassbar() {
    return hasStimmenPolitischeGruppen() && getStimmenPolitischeGruppen().stream().anyMatch(StimmeingabeGruppeStimmen::isWaehlerBenannteWahlvorschlaegeErfassbar);
  }

  public boolean hasStimmenSystemGruppe(SystemGruppe systemGruppe) {
    return getStimmenSystemGruppe(systemGruppe) != null;
  }

  public <T extends StimmeingabeGruppeStimmen> T getStimmenSystemGruppe(SystemGruppe systemGruppe) {
    return (T) stimmenSystemGruppePerSystemGruppe.get(systemGruppe);
  }

  public void setStimmenSystemGruppe(SystemGruppe systemGruppe, StimmeingabeGruppeStimmen stimmeingabeGruppeStimmen) {
    stimmenSystemGruppePerSystemGruppe.put(systemGruppe, stimmeingabeGruppeStimmen);
  }

  public Optional<Stimmeingabe> getStimmeingabeSystemGruppeDirektstimme(SystemGruppe systemGruppe) {
    if (hasStimmenSystemGruppe(systemGruppe)) {
      return Optional.ofNullable(getStimmenSystemGruppe(systemGruppe).getStimmeingabeDirektstimme());
    }

    return Optional.empty();
  }

  public Optional<Stimmeingabe> getStimmeingabeSystemGruppeListenstimme(SystemGruppe systemGruppe) {
    if (hasStimmenSystemGruppe(systemGruppe)) {
      return Optional.ofNullable(getStimmenSystemGruppe(systemGruppe).getStimmeingabeListenstimme());
    }

    return Optional.empty();
  }

  private <T extends StimmeingabeGruppeStimmen> List<T> getStimmenSystemGruppen() {
    List<StimmeingabeGruppeStimmen> result = new ArrayList<>(stimmenSystemGruppePerSystemGruppe.values());

    if (stimmenSystemGruppeUnbestimmte != null) {
      result.add(stimmenSystemGruppeUnbestimmte);
    }

    return (List<T>) result;
  }

  public boolean hasStimmenGruppeCustom() {
    return hasStimmenSystemGruppe(SYSTEM_GRUPPE_CUSTOM);
  }

  public <T extends StimmeingabeGruppeStimmen> T getStimmenGruppeCustom() {
    return getStimmenSystemGruppe(SYSTEM_GRUPPE_CUSTOM);
  }

  public void setStimmenGruppeCustom(StimmeingabeGruppeStimmen stimmeingabeGruppeStimmen) {
    setStimmenSystemGruppe(SYSTEM_GRUPPE_CUSTOM, stimmeingabeGruppeStimmen);
  }

  public Optional<Stimmeingabe> getStimmeingabeGruppeCustomDirektstimmen() {
    return getStimmeingabeSystemGruppeDirektstimme(SYSTEM_GRUPPE_CUSTOM);
  }

  public Optional<Stimmeingabe> getStimmeingabeGruppeCustomListenstimmen() {
    return getStimmeingabeSystemGruppeListenstimme(SYSTEM_GRUPPE_CUSTOM);
  }

  public boolean hasStimmenGruppeGueltige() {
    return hasStimmenSystemGruppe(SystemGruppe.GUELTIGE);
  }

  public <T extends StimmeingabeGruppeStimmen> T getStimmenGruppeGueltige() {
    return getStimmenSystemGruppe(SystemGruppe.GUELTIGE);
  }

  public void setStimmenGruppeGueltige(StimmeingabeGruppeStimmen stimmeingabeGruppeStimmen) {
    setStimmenSystemGruppe(SystemGruppe.GUELTIGE, stimmeingabeGruppeStimmen);
  }

  public Optional<Stimmeingabe> getStimmeingabeGruppeGueltigeDirektstimmen() {
    return getStimmeingabeSystemGruppeDirektstimme(SystemGruppe.GUELTIGE);
  }

  public Optional<Stimmeingabe> getStimmeingabeGruppeGueltigeListenstimmen() {
    return getStimmeingabeSystemGruppeListenstimme(SystemGruppe.GUELTIGE);
  }

  public boolean hasStimmenGruppeUngueltige() {
    return hasStimmenSystemGruppe(SystemGruppe.UNGUELTIGE);
  }

  public <T extends StimmeingabeGruppeStimmen> T getStimmenGruppeUngueltige() {
    return getStimmenSystemGruppe(SystemGruppe.UNGUELTIGE);
  }

  public void setStimmenGruppeUngueltige(StimmeingabeGruppeStimmen stimmeingabeGruppeStimmen) {
    setStimmenSystemGruppe(SystemGruppe.UNGUELTIGE, stimmeingabeGruppeStimmen);
  }

  public Optional<Stimmeingabe> getStimmeingabeGruppeUngueltigeDirektstimmen() {
    return getStimmeingabeSystemGruppeDirektstimme(SystemGruppe.UNGUELTIGE);
  }

  public Optional<Stimmeingabe> getStimmeingabeGruppeUngueltigeListenstimmen() {
    return getStimmeingabeSystemGruppeListenstimme(SystemGruppe.UNGUELTIGE);
  }

  public boolean hasStimmenGruppeUnbestimmte() {
    return stimmenSystemGruppeUnbestimmte != null;
  }

  public <T extends StimmeingabeGruppeStimmen> T getStimmenGruppeUnbestimmte() {
    if (stimmenSystemGruppeUnbestimmte == null) {
      return null;
    }

    return (T) stimmenSystemGruppeUnbestimmte;
  }

  public void setStimmenGruppeUnbestimmte(StimmeingabeGruppeStimmen stimmeingabeGruppeStimmen) {
    stimmenSystemGruppeUnbestimmte = stimmeingabeGruppeStimmen;
  }

  public Optional<Stimmeingabe> getStimmeingabeGruppeUnbestimmteDirektstimmen() {
    return Optional.ofNullable(stimmenSystemGruppeUnbestimmte).map(StimmeingabeGruppeStimmen::getStimmeingabeDirektstimme);
  }

  public Optional<Stimmeingabe> getStimmeingabeGruppeUnbestimmteListenstimmen() {
    return Optional.ofNullable(stimmenSystemGruppeUnbestimmte).map(StimmeingabeGruppeStimmen::getStimmeingabeListenstimme);
  }

  public boolean hasStimmenPolitischeGruppenReadonly() {
    return !getStimmenPolitischeGruppenReadonly().isEmpty();
  }

  public <T extends StimmeingabeGruppeStimmen> List<T> getStimmenPolitischeGruppenReadonly() {
    return (List<T>) stimmenPolitischeGruppenReadonly;
  }

  public boolean hasStimmenPolitischeGruppen() {
    return !getStimmenPolitischeGruppen().isEmpty();
  }

  public <T extends StimmeingabeGruppeStimmen> List<T> getStimmenPolitischeGruppen() {
    return (List<T>) stimmenPolitischeGruppen;
  }

  public Collection<Stimmeingabe> getStimmeingabenPolitischeGruppen() {
    return extractStimmeingaben(getStimmenPolitischeGruppen());
  }

  public Collection<Stimmeingabe> getStimmeingabenPolitischeGruppenByStimmenart(@Nonnull Stimmenart stimmenart) {
    Collection<Stimmeingabe> result = new ArrayList<>(getStimmenPolitischeGruppen().size());

    for (Stimmeingabe stimmeingabe : getStimmeingabenPolitischeGruppen()) {
      if (stimmenart.equals(stimmeingabe.getStimmenart())) {
        result.add(stimmeingabe);
      }
    }

    return result;
  }

  @Override
  public Collection<StimmeingabeGruppeStimmen> getAllStimmeingabeValueObjects() {
    return this.getAllStimmeingabeValueObjectsTyped();
  }

  private <T extends StimmeingabeGruppeStimmen> Collection<T> getAllStimmeingabeValueObjectsTyped() {
    List<T> result = new ArrayList<>();

    result.addAll(getStimmenSystemGruppen());
    result.addAll(getStimmenPolitischeGruppen());

    return result;
  }

  public <T extends StimmeingabeGruppeStimmen> T getStimmenPolitischeGruppenFuerGruppeImGebiet(@Nonnull GruppeImGebiet gruppeImGebiet) {
    for (T stimmeingabeGruppeStimmen : this.<T> getStimmenPolitischeGruppen()) {
      if (gruppeImGebiet.equals(stimmeingabeGruppeStimmen.getGruppeImGebiet())) {
        return stimmeingabeGruppeStimmen;
      }
    }

    throw new IllegalArgumentException("Das Modell [" + this + "] enthält keine Stimmeingaben zur GruppeImGebiet [" + gruppeImGebiet + "].");
  }

  public boolean hasStimmzetteleingabeGueltigeNurEinzelnerWahlvorschlagUnveraendert() {
    return getStimmzetteleingabeGueltigeNurEinzelnerWahlvorschlagUnveraendert().isPresent();
  }

  public Optional<Stimmeingabe> getStimmzetteleingabeGueltigeNurEinzelnerWahlvorschlagUnveraendert() {
    return Optional.empty(); // ELECT-9041
  }

  public boolean hasStimmzetteleingabeGueltigeNurEinzelnerWahlvorschlagVeraendert() {
    return getStimmzetteleingabeGueltigeNurEinzelnerWahlvorschlagVeraendert().isPresent();
  }

  public Optional<Stimmeingabe> getStimmzetteleingabeGueltigeNurEinzelnerWahlvorschlagVeraendert() {
    return Optional.empty(); // ELECT-9041
  }

  public boolean hasStimmzetteleingabeGueltigeNurEinzelnerWahlvorschlagPanaschiert() {
    return getStimmzetteleingabeGueltigeNurEinzelnerWahlvorschlagPanaschiert().isPresent();
  }

  public Optional<Stimmeingabe> getStimmzetteleingabeGueltigeNurEinzelnerWahlvorschlagPanaschiert() {
    return Optional.empty();
  }

  public boolean hasStimmzetteleingabeGueltigeKeinEinzelnerWahlvorschlagVeraendert() {
    return getStimmzetteleingabeGueltigeKeinEinzelnerWahlvorschlagVeraendert().isPresent();
  }

  public Optional<Stimmeingabe> getStimmzetteleingabeGueltigeKeinEinzelnerWahlvorschlagVeraendert() {
    return Optional.empty(); // ELECT-13792
  }

  public boolean hasStimmzetteleingabeGueltigeKeinEinzelnerWahlvorschlagPanaschiert() {
    return getStimmzetteleingabeGueltigeKeinEinzelnerWahlvorschlagPanaschiert().isPresent();
  }

  public Optional<Stimmeingabe> getStimmzetteleingabeGueltigeKeinEinzelnerWahlvorschlagPanaschiert() {
    return Optional.empty(); // ELECT-13792
  }

  public boolean hasStimmzetteleingabenPolitischeGruppeNurEinzelnerWahlvorschlagUnveraendert() {
    return !getStimmzetteleingabenPolitischeGruppeNurEinzelnerWahlvorschlagUnveraendert().isEmpty();
  }

  public Collection<Stimmeingabe> getStimmzetteleingabenPolitischeGruppeNurEinzelnerWahlvorschlagUnveraendert() {
    return emptyList(); // ELECT-9041
  }

  public boolean hasStimmzetteleingabenPolitischeGruppeNurEinzelnerWahlvorschlagVeraendert() {
    return !getStimmzetteleingabenPolitischeGruppeNurEinzelnerWahlvorschlagVeraendert().isEmpty();
  }

  public Collection<Stimmeingabe> getStimmzetteleingabenPolitischeGruppeNurEinzelnerWahlvorschlagVeraendert() {
    return emptyList(); // ELECT-9041
  }

  public boolean hasStimmzetteleingabenPolitischeGruppeNurEinzelnerWahlvorschlagPanaschiert() {
    return !getStimmzetteleingabenPolitischeGruppeNurEinzelnerWahlvorschlagPanaschiert().isEmpty();
  }

  public Collection<Stimmeingabe> getStimmzetteleingabenPolitischeGruppeNurEinzelnerWahlvorschlagPanaschiert() {
    return emptyList();
  }
}
