
package de.ivu.elect.business.wahl.control;

import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import de.ivu.elect.business.ergebnis.entity.Ergebnisart;
import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
import de.ivu.elect.business.ergebnis.entity.Sitze;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.fremdkandidatur.entity.Fremdkandidatur;
import de.ivu.elect.business.gebietsbaum.control.GebietByBreadthFirstComparator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietPolygonPoint;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsgruppierung;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.internet.entity.InternetSeite;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.person.entity.PersonenVerknuepfung;
import de.ivu.elect.business.staat.entity.StaatenGruppe;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiKonfiguration;
import de.ivu.elect.business.stimmzettel.control.StimmzettelGebietUndNummerComparator;
import de.ivu.elect.business.stimmzettel.entity.Stimmzettel;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatum;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatumArt;
import de.ivu.elect.business.veranstaltung.entity.Veranstaltung;
import de.ivu.elect.business.wahlausschuss.entity.WahlausschussEntschaedigung;
import de.ivu.elect.business.wahlausschuss.entity.Wahlausschussmitglied;
import de.ivu.elect.business.wahlbeteiligung.entity.WahlbeteiligungVerlauf;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlagsperson.entity.WahlvorschlagsPerson;

public class WahldatenLoader<Gebietobject extends Gebiet> implements Serializable {

  private static final long serialVersionUID = 1L;

  private final Loader<Gebietobject> gebieteLoader;
  private List<Gebietobject> gebiete;

  private final Loader<Gruppe> gruppenLoader;
  private List<Gruppe> gruppen;

  private final Loader<GruppeImGebiet> gruppenImGebietLoader;
  private List<GruppeImGebiet> gruppenImGebiet;

  private final Loader<Wahlvorschlag> wahlvorschlaegeLoader;
  private List<Wahlvorschlag> wahlvorschlaege;

  private final Loader<WahlvorschlagsPerson> wahlvorschlagsPersonenLoader;
  private List<WahlvorschlagsPerson> wahlvorschlagsPersonen;

  private final Loader<Wahlausschussmitglied> wahlausschussmitgliederLoader;
  private List<Wahlausschussmitglied> wahlausschussmitglieder;

  private final Loader<Kandidatur> kandidaturenLoader;
  private List<Kandidatur> kandidaturen;

  private final Loader<PersonenVerknuepfung> personenVerknuepfungenLoader;
  private List<PersonenVerknuepfung> personenVerknuepfungen;

  private final StimmenLoader stimmenLoader;
  private Map<Ergebnisart, List<Stimmen>> stimmen;

  private final Loader<Gewaehlt> gewaehlteVorperiodeLoader;
  protected List<Gewaehlt> gewaehlteVorperiode;

  private final Loader<Sitze> sitzeVorperiodeLoader;
  protected List<Sitze> sitzeVorperiode;

  private final Loader<Fremdkandidatur> fremdkandidaturenLoader;
  private List<Fremdkandidatur> fremdkandidaturen;

  private final Loader<StaatenGruppe> staatenGruppenLoader;
  private List<StaatenGruppe> staatenGruppen;

  private final Loader<Gebietsgruppierung> gebietsgruppierungenLoader;
  private List<Gebietsgruppierung> gebietsgruppierungen;

  private final Loader<WahlausschussEntschaedigung> wahlausschussEntschaedigungenLoader;
  private List<WahlausschussEntschaedigung> wahlausschussEntschaedigungen;

  private final Loader<GebietPolygonPoint> gebietPolygonPointsLoader;
  private List<GebietPolygonPoint> gebietPolygonPoints;
  private Map<Gebiet, List<GebietPolygonPoint>> gebietPolygonPointsFuerGebiet;

  private final Loader<InternetSeite> internetSeitenLoader;
  private List<InternetSeite> internetSeiten;

  private final Loader<WahlbeteiligungVerlauf> wahlbeteiligungVerlaeufeLoader;
  private List<WahlbeteiligungVerlauf> wahlbeteiligungVerlaeufe;

  private final Loader<StimmeingabePlausiKonfiguration> stimmeingabePlausiKonfigurationenLoader;
  private List<StimmeingabePlausiKonfiguration> stimmeingabePlausiKonfigurationen;

  private final Loader<Veranstaltung> veranstaltungenLoader;
  private List<Veranstaltung> veranstaltungen;

  private final Loader<Stimmzettel> stimmzettelLoader;
  private List<Stimmzettel> stimmzettel;
  private Map<Gebiet, List<Stimmzettel>> stimmzettelNachGebiet;

  private final Loader<StimmzettelKopfdatum> stimmzettelKopfdatenLoader;
  private List<StimmzettelKopfdatum> stimmzettelKopfdaten;
  private Map<Gebiet, Map<StimmzettelKopfdatumArt, List<StimmzettelKopfdatum>>> stimmzettelKopfdatenNachGebietUndArt;

  public WahldatenLoader(Loader<Gebietobject> gebieteLoader, Loader<Gruppe> gruppenLoader, Loader<GruppeImGebiet> gruppenImGebietLoader, Loader<Wahlvorschlag> wahlvorschlaegeLoader,
      Loader<WahlvorschlagsPerson> wahlvorschlagsPersonenLoader, Loader<Wahlausschussmitglied> wahlausschussmitgliederLoader, Loader<Kandidatur> kandidaturenLoader,
      Loader<PersonenVerknuepfung> personenVerknuepfungenLoader, StimmenLoader stimmenLoader, Loader<Gewaehlt> gewaehlteVorperiodeLoader, Loader<Sitze> sitzeVorperiodeLoader,
      Loader<StaatenGruppe> staatenGruppenLoader, Loader<Gebietsgruppierung> gebietsgruppierungenLoader, Loader<WahlausschussEntschaedigung> wahlausschussEntschaedigungenLoader,
      Loader<GebietPolygonPoint> gebietPolygonPointsLoader, Loader<Fremdkandidatur> fremdkandidaturenLoader, Loader<InternetSeite> internetSeitenLoader,
      Loader<WahlbeteiligungVerlauf> wahlbeteiligungVerlaeufeLoader, Loader<StimmeingabePlausiKonfiguration> stimmeingabePlausiKonfigurationenLoader, Loader<Veranstaltung> veranstaltungenLoader,
      Loader<Stimmzettel> stimmzettelLoader, Loader<StimmzettelKopfdatum> stimmzettelKopfdatenLoader) {
    super();

    this.gebieteLoader = gebieteLoader;
    this.gruppenLoader = gruppenLoader;
    this.gruppenImGebietLoader = gruppenImGebietLoader;
    this.wahlvorschlaegeLoader = wahlvorschlaegeLoader;
    this.wahlausschussmitgliederLoader = wahlausschussmitgliederLoader;
    this.wahlvorschlagsPersonenLoader = wahlvorschlagsPersonenLoader;
    this.kandidaturenLoader = kandidaturenLoader;
    this.personenVerknuepfungenLoader = personenVerknuepfungenLoader;
    this.stimmenLoader = stimmenLoader;
    this.gewaehlteVorperiodeLoader = gewaehlteVorperiodeLoader;
    this.sitzeVorperiodeLoader = sitzeVorperiodeLoader;
    this.staatenGruppenLoader = staatenGruppenLoader;
    this.gebietsgruppierungenLoader = gebietsgruppierungenLoader;
    this.wahlausschussEntschaedigungenLoader = wahlausschussEntschaedigungenLoader;
    this.gebietPolygonPointsLoader = gebietPolygonPointsLoader;
    this.fremdkandidaturenLoader = fremdkandidaturenLoader;
    this.internetSeitenLoader = internetSeitenLoader;
    this.wahlbeteiligungVerlaeufeLoader = wahlbeteiligungVerlaeufeLoader;
    this.stimmeingabePlausiKonfigurationenLoader = stimmeingabePlausiKonfigurationenLoader;
    this.veranstaltungenLoader = veranstaltungenLoader;
    this.stimmzettelLoader = stimmzettelLoader;
    this.stimmzettelKopfdatenLoader = stimmzettelKopfdatenLoader;
  }

  public List<Gebietobject> getGebiete(Predicate<Gebietobject> filter) {
    return getGebiete().stream().filter(filter).collect(Collectors.toList());
  }

  public List<Gebietobject> getGebiete() {
    if (gebiete == null) {
      gebiete = getGebieteLoader().load();
      gebiete.sort(new GebietByBreadthFirstComparator());
    }

    return gebiete;
  }

  protected Loader<Gebietobject> getGebieteLoader() {
    return gebieteLoader;
  }

  public List<InternetSeite> getInternetSeiten() {
    if (internetSeiten == null) {
      internetSeiten = getInternetSeitenLoader().load();
    }

    return internetSeiten;
  }

  public void setInternetSeiten(List<InternetSeite> internetSeiten) {
    this.internetSeiten = internetSeiten;
  }

  protected Loader<InternetSeite> getInternetSeitenLoader() {
    return internetSeitenLoader;
  }

  public List<WahlbeteiligungVerlauf> getWahlbeteiligungVerlaeufe() {
    if (wahlbeteiligungVerlaeufe == null) {
      wahlbeteiligungVerlaeufe = getWahlbeteiligungVerlaeufeLoader().load();
    }

    return wahlbeteiligungVerlaeufe;
  }

  protected Loader<WahlbeteiligungVerlauf> getWahlbeteiligungVerlaeufeLoader() {
    return wahlbeteiligungVerlaeufeLoader;
  }

  public List<StimmeingabePlausiKonfiguration> getStimmeingabePlausiKonfigurationen() {
    if (stimmeingabePlausiKonfigurationen == null) {
      stimmeingabePlausiKonfigurationen = getStimmeingabePlausiKonfigurationenLoader().load();
    }

    return stimmeingabePlausiKonfigurationen;
  }

  protected Loader<StimmeingabePlausiKonfiguration> getStimmeingabePlausiKonfigurationenLoader() {
    return stimmeingabePlausiKonfigurationenLoader;
  }

  public List<GebietPolygonPoint> getGebietPolygonPointsFuerGebiet(Gebiet gebietobject) {
    if (gebietPolygonPointsFuerGebiet == null) {
      gebietPolygonPointsFuerGebiet = getGebietPolygonPoints().stream().collect(Collectors.groupingBy(GebietPolygonPoint::getGebiet));
    }

    return Optional.ofNullable(gebietPolygonPointsFuerGebiet.get(gebietobject)).orElse(emptyList());
  }

  public List<GebietPolygonPoint> getGebietPolygonPoints() {
    if (gebietPolygonPoints == null) {
      gebietPolygonPoints = getGebietPolygonPointsLoader().load();
    }

    return gebietPolygonPoints;
  }

  protected Loader<GebietPolygonPoint> getGebietPolygonPointsLoader() {
    return gebietPolygonPointsLoader;
  }

  public List<Gruppe> getGruppen() {
    if (gruppen == null) {
      gruppen = getGruppenLoader().load();
    }

    return gruppen;
  }

  protected Loader<Gruppe> getGruppenLoader() {
    return gruppenLoader;
  }

  public List<GruppeImGebiet> getGruppenImGebiet() {
    if (gruppenImGebiet == null) {
      gruppenImGebiet = getGruppenImGebietLoader().load();
    }

    return gruppenImGebiet;
  }

  protected Loader<GruppeImGebiet> getGruppenImGebietLoader() {
    return gruppenImGebietLoader;
  }

  public List<Kandidatur> getKandidaturen() {
    if (kandidaturen == null) {
      kandidaturen = getKandidaturenLoader().load();
    }

    return kandidaturen;
  }

  protected Loader<Kandidatur> getKandidaturenLoader() {
    return kandidaturenLoader;
  }

  public List<PersonenVerknuepfung> getPersonenVerknuepfungen() {
    if (personenVerknuepfungen == null) {
      personenVerknuepfungen = getPersonenVerknuepfungenLoader().load();
    }

    return personenVerknuepfungen;
  }

  protected Loader<PersonenVerknuepfung> getPersonenVerknuepfungenLoader() {
    return personenVerknuepfungenLoader;
  }

  public List<StaatenGruppe> getStaatenGruppen() {
    if (staatenGruppen == null) {
      staatenGruppen = getStaatenGruppenLoader().load();
    }

    return staatenGruppen;
  }

  protected Loader<StaatenGruppe> getStaatenGruppenLoader() {
    return staatenGruppenLoader;
  }

  public List<Gebietsgruppierung> getGebietsgruppierungen(Predicate<Gebietsgruppierung> filter) {
    return getGebietsgruppierungen().stream().filter(filter).collect(Collectors.toList());
  }

  public List<Gebietsgruppierung> getGebietsgruppierungen() {
    if (gebietsgruppierungen == null) {
      gebietsgruppierungen = getGebietsgruppierungenLoader().load();
    }

    return gebietsgruppierungen;
  }

  protected Loader<Gebietsgruppierung> getGebietsgruppierungenLoader() {
    return gebietsgruppierungenLoader;
  }

  public List<Stimmen> getStimmenVorperiode() {
    return getStimmen(Ergebnisart.VORPERIODE);
  }

  public List<Stimmen> getStimmenVergleich() {
    return getStimmen(Ergebnisart.VERGLEICH);
  }

  public List<Stimmen> getStimmen(Ergebnisart ergebnisart) {
    if (stimmen == null) {
      stimmen = new EnumMap<>(Ergebnisart.class);
    }

    return stimmen.computeIfAbsent(ergebnisart, x -> getStimmenLoader().load(ergebnisart));
  }

  protected StimmenLoader getStimmenLoader() {
    return stimmenLoader;
  }

  public List<Gewaehlt> getGewaehltVorperiode() {
    if (gewaehlteVorperiode == null) {
      gewaehlteVorperiode = getGewaehlteVorperiodeLoader().load();
    }

    return gewaehlteVorperiode;
  }

  protected Loader<Gewaehlt> getGewaehlteVorperiodeLoader() {
    return gewaehlteVorperiodeLoader;
  }

  public List<Sitze> getSitzeVorperiode() {
    if (sitzeVorperiode == null) {
      sitzeVorperiode = getSitzeVorperiodeLoader().load();
    }

    return sitzeVorperiode;
  }

  protected Loader<Sitze> getSitzeVorperiodeLoader() {
    return sitzeVorperiodeLoader;
  }

  public List<Fremdkandidatur> getFremdkandidaturen() {
    if (fremdkandidaturen == null) {
      fremdkandidaturen = getFremdkandidaturenLoader().load();
    }

    return fremdkandidaturen;
  }

  protected Loader<Fremdkandidatur> getFremdkandidaturenLoader() {
    return fremdkandidaturenLoader;
  }

  public List<WahlausschussEntschaedigung> getWahlausschussEntschaedigungen() {
    if (wahlausschussEntschaedigungen == null) {
      wahlausschussEntschaedigungen = getWahlausschussEntschaedigungenLoader().load();
    }

    return wahlausschussEntschaedigungen;
  }

  protected Loader<WahlausschussEntschaedigung> getWahlausschussEntschaedigungenLoader() {
    return wahlausschussEntschaedigungenLoader;
  }

  public List<Wahlausschussmitglied> getWahlausschussmitglieder() {
    if (wahlausschussmitglieder == null) {
      wahlausschussmitglieder = getWahlausschussmitgliederLoader().load();
    }

    return wahlausschussmitglieder;
  }

  protected Loader<Wahlausschussmitglied> getWahlausschussmitgliederLoader() {
    return wahlausschussmitgliederLoader;
  }

  public List<Wahlvorschlag> getWahlvorschlaege() {
    if (wahlvorschlaege == null) {
      wahlvorschlaege = getWahlvorschlaegeLoader().load();
    }

    return wahlvorschlaege;
  }

  protected Loader<Wahlvorschlag> getWahlvorschlaegeLoader() {
    return wahlvorschlaegeLoader;
  }

  public List<WahlvorschlagsPerson> getWahlvorschlagsPersonen() {
    if (wahlvorschlagsPersonen == null) {
      wahlvorschlagsPersonen = getWahlvorschlagsPersonenLoader().load();
    }

    return wahlvorschlagsPersonen;
  }

  protected Loader<WahlvorschlagsPerson> getWahlvorschlagsPersonenLoader() {
    return wahlvorschlagsPersonenLoader;
  }

  public List<Stimmzettel> getStimmzettel() {
    if (stimmzettel == null) {
      stimmzettel = getStimmzettelLoader().load();
    }

    return stimmzettel;
  }

  protected Loader<Stimmzettel> getStimmzettelLoader() {
    return stimmzettelLoader;
  }

  public List<Stimmzettel> getStimmzettel(Gebietobject gebiet) {
    if (stimmzettelNachGebiet == null) {
      stimmzettelNachGebiet = new HashMap<>();
    }

    return stimmzettelNachGebiet.computeIfAbsent(gebiet,
        x -> getStimmzettel().stream().filter(s -> s.getGebiet().isEqualOderUntergebietVon(gebiet)).sorted(StimmzettelGebietUndNummerComparator.INSTANCE).collect(Collectors.toList()));

  }

  public List<StimmzettelKopfdatum> getStimmzettelKopfdaten(Gebietobject gebiet, StimmzettelKopfdatumArt stimmzettelKopfdatumArt) {
    if (stimmzettelKopfdatenNachGebietUndArt == null) {
      stimmzettelKopfdatenNachGebietUndArt = new HashMap<>();

      for (StimmzettelKopfdatum stimmzettelKopfdatum : getStimmzettelKopfdaten()) {
        stimmzettelKopfdatenNachGebietUndArt.computeIfAbsent(stimmzettelKopfdatum.getGebiet(), x -> new EnumMap<>(StimmzettelKopfdatumArt.class))
            .computeIfAbsent(stimmzettelKopfdatum.getStimmzettelKopfdatumArt(), x -> new ArrayList<>()).add(stimmzettelKopfdatum);
      }
    }

    return stimmzettelKopfdatenNachGebietUndArt.getOrDefault(gebiet, emptyMap()).getOrDefault(stimmzettelKopfdatumArt, emptyList());
  }

  public List<StimmzettelKopfdatum> getStimmzettelKopfdaten() {
    if (stimmzettelKopfdaten == null) {
      stimmzettelKopfdaten = getStimmzettelKopfdatenLoader().load();
    }

    return stimmzettelKopfdaten;
  }

  protected Loader<StimmzettelKopfdatum> getStimmzettelKopfdatenLoader() {
    return stimmzettelKopfdatenLoader;
  }

  public List<Veranstaltung> getVeranstaltungen() {
    if (veranstaltungen == null) {
      veranstaltungen = getVeranstaltungenLoader().load();
    }

    return veranstaltungen;
  }

  protected Loader<Veranstaltung> getVeranstaltungenLoader() {
    return veranstaltungenLoader;
  }
}
