
package de.ivu.elect.was.exchange.ergebnis.zusammenstellung;

import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableCollection;
import static java.util.stream.Collectors.toSet;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformation;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGemeindeInformation;

public class ErgebnisZusammenstellung {

  private final ErgebnisZusammenstellungDokument dokument;

  public ErgebnisZusammenstellung(ErgebnisZusammenstellungDokument dokument) {
    super();

    this.dokument = dokument;
  }

  /**
   * @return Tabelle für das Stimmzettelgebiet
   */
  public Optional<StimmzettelgebietTabelle> getStimmzettelgebietTabelle(WahlverwaltungGebietInformation stimmzettelgebiet) {
    Collection<WahlverwaltungGemeindeInformation> gemeindenInTabelleStimmzettelgebiet = collectGemeindeTabellen(stimmzettelgebiet).stream().map(GemeindeTabelle::getGemeinde).collect(toSet());

    return dokument.getTabelle(stimmzettelgebiet)
        .map(tabelleStimmzettelgebiet -> new StimmzettelgebietTabelle(tabelleStimmzettelgebiet.getStimmzettelgebiet(), gemeindenInTabelleStimmzettelgebiet, tabelleStimmzettelgebiet));
  }

  /**
   * @return Tabelle für das Stimmzettelgebiet PRO GRUPPE im Stimmzettelgebiet
   */
  public Collection<GruppeStimmzettelgebietTabelle> collectGruppeStimmzettelgebietTabellen(WahlverwaltungGebietInformation stimmzettelgebiet) {
    Collection<GruppeStimmzettelgebietTabelle> result = new ArrayList<>();

    Optional<ErgebnisZusammenstellungTabelle> tabelleStimmzettelgebietIfPresent = dokument.getTabelle(stimmzettelgebiet);

    if (tabelleStimmzettelgebietIfPresent.isPresent()) {
      ErgebnisZusammenstellungTabelle tabelleStimmzettelgebiet = tabelleStimmzettelgebietIfPresent.get();

      Map<ErgebnisZusammenstellungGruppe, Collection<WahlverwaltungGemeindeInformation>> gemeindenPerGruppe = new HashMap<>();

      for (GruppeGemeindeTabellen gruppeGemeindeTabellen : collectGruppeGemeindeTabellen(stimmzettelgebiet)) {
        gemeindenPerGruppe.computeIfAbsent(gruppeGemeindeTabellen.getGruppe(), ignored -> new HashSet<>())
            .addAll(gruppeGemeindeTabellen.getTabellen().stream().map(GemeindeTabelle::getGemeinde).collect(toSet()));
      }

      List<ErgebnisZusammenstellungGruppe> gruppenSorted = new ArrayList<>(tabelleStimmzettelgebiet.getGruppen());
      gruppenSorted.sort(ErgebnisZusammenstellungGruppe.DEFAULT_COMPARATOR);

      for (ErgebnisZusammenstellungGruppe gruppe : gruppenSorted) {
        ErgebnisZusammenstellungTabelleFilter tabelleGruppeFilter = new ErgebnisZusammenstellungTabelleFilter(tabelleStimmzettelgebiet).filterByGruppe(gruppe);

        ErgebnisZusammenstellungTabelle tabelleGruppeStimmzettelgebiet = tabelleGruppeFilter.get();
        Collection<WahlverwaltungGemeindeInformation> gemeindenInTabelleGruppeStimmzettelgebiet = gemeindenPerGruppe.getOrDefault(gruppe, emptySet());

        result.add(new GruppeStimmzettelgebietTabelle(gruppe, new StimmzettelgebietTabelle(stimmzettelgebiet, gemeindenInTabelleGruppeStimmzettelgebiet, tabelleGruppeStimmzettelgebiet)));
      }
    }

    return result;
  }

  /**
   * @return Tabellen für jeden Gemeindeverband bzw. jede verbandsfreie Gemeinde PRO GRUPPE im Stimmzettelgebiet
   */
  public Collection<GruppeGemeindeTabellen> collectGruppeGemeindeTabellen(WahlverwaltungGebietInformation stimmzettelgebiet) {
    Collection<GruppeGemeindeTabellen> result = new ArrayList<>();

    Optional<ErgebnisZusammenstellungTabelle> tabelleStimmzettelgebietIfPresent = dokument.getTabelle(stimmzettelgebiet);

    if (tabelleStimmzettelgebietIfPresent.isPresent()) {
      ErgebnisZusammenstellungTabelle tabelleStimmzettelgebiet = tabelleStimmzettelgebietIfPresent.get();

      Collection<WahlverwaltungGemeindeInformation> gemeindeverbaende = tabelleStimmzettelgebiet.getGemeindeverbaende();
      Collection<WahlverwaltungGemeindeInformation> gemeindenVerbandsfrei = tabelleStimmzettelgebiet.getGemeindenVerbandsfrei();

      for (ErgebnisZusammenstellungGruppe gruppe : tabelleStimmzettelgebiet.getGruppen()) {
        ErgebnisZusammenstellungTabelleFilter tabelleGruppeFilter = new ErgebnisZusammenstellungTabelleFilter(tabelleStimmzettelgebiet).filterByGruppe(gruppe);

        Collection<GemeindeTabelle> tabellenGemeinde = new ArrayList<>();

        for (WahlverwaltungGemeindeInformation gemeindeverband : gemeindeverbaende) {
          ErgebnisZusammenstellungTabelle tabelleGemeindeverband = tabelleGruppeFilter.filterByGemeindeverband(gemeindeverband).get();

          if (!tabelleGemeindeverband.isEmpty()) {
            tabellenGemeinde.add(new GemeindeTabelle(gemeindeverband, false, tabelleGemeindeverband));
          }
        }

        for (WahlverwaltungGemeindeInformation gemeinde : gemeindenVerbandsfrei) {
          ErgebnisZusammenstellungTabelle tabelleGemeinde = tabelleGruppeFilter.filterByGemeinde(gemeinde).get();

          if (!tabelleGemeinde.isEmpty()) {
            tabellenGemeinde.add(new GemeindeTabelle(gemeinde, true, tabelleGemeinde));
          }
        }

        if (!tabellenGemeinde.isEmpty()) {
          result.add(new GruppeGemeindeTabellen(gruppe, tabellenGemeinde));
        }
      }
    }

    return result;
  }

  /**
   * @return Tabellen für jede Gruppe PRO Gemeindeverband bzw. verbandsfreier Gemeinde im Stimmzettelgebiet
   */
  public Collection<GemeindeGruppeTabellen> collectGemeindeGruppeTabellen(WahlverwaltungGebietInformation stimmzettelgebiet) {
    Collection<GemeindeGruppeTabellen> result = new ArrayList<>();

    for (GemeindeTabelle gemeindeTabelle : collectGemeindeTabellen(stimmzettelgebiet)) {
      ErgebnisZusammenstellungTabelle tabelleGemeinde = gemeindeTabelle.getTabelle();
      ErgebnisZusammenstellungTabelleFilter tabelleGemeindeFilter = new ErgebnisZusammenstellungTabelleFilter(tabelleGemeinde);

      List<ErgebnisZusammenstellungGruppe> gruppenSorted = new ArrayList<>(tabelleGemeinde.getGruppen());
      gruppenSorted.sort(ErgebnisZusammenstellungGruppe.DEFAULT_COMPARATOR);

      Collection<GruppeTabelle> tabellenGruppe = new ArrayList<>();

      for (ErgebnisZusammenstellungGruppe gruppe : gruppenSorted) {
        ErgebnisZusammenstellungTabelle tabelleGruppe = tabelleGemeindeFilter.filterByGruppe(gruppe).get();

        if (!tabelleGruppe.isEmpty()) {
          tabellenGruppe.add(new GruppeTabelle(gruppe, tabelleGruppe));
        }
      }

      if (!tabellenGruppe.isEmpty()) {
        result.add(new GemeindeGruppeTabellen(gemeindeTabelle.getGemeinde(), tabellenGruppe));
      }
    }

    return result;
  }

  /**
   * @return Tabelle für jeden Gemeindeverband bzw. jede verbandsfreie Gemeinde im Stimmzettelgebiet
   */
  public Collection<GemeindeTabelle> collectGemeindeTabellen(WahlverwaltungGebietInformation stimmzettelgebiet) {
    List<GemeindeTabelle> result = new ArrayList<>();

    Optional<ErgebnisZusammenstellungTabelle> tabelleStimmzettelgebietIfPresent = dokument.getTabelle(stimmzettelgebiet);

    if (tabelleStimmzettelgebietIfPresent.isPresent()) {
      ErgebnisZusammenstellungTabelle tabelleStimmzettelgebiet = tabelleStimmzettelgebietIfPresent.get();
      ErgebnisZusammenstellungTabelleFilter tabelleStimmzettelgebietFilter = new ErgebnisZusammenstellungTabelleFilter(tabelleStimmzettelgebiet);

      for (WahlverwaltungGemeindeInformation gemeindeverband : tabelleStimmzettelgebiet.getGemeindeverbaende()) {
        ErgebnisZusammenstellungTabelle tabelleGemeindeverband = tabelleStimmzettelgebietFilter.filterByGemeindeverband(gemeindeverband).get();

        if (!tabelleGemeindeverband.isEmpty()) {
          result.add(new GemeindeTabelle(gemeindeverband, false, tabelleGemeindeverband));
        }
      }

      for (WahlverwaltungGemeindeInformation gemeinde : tabelleStimmzettelgebiet.getGemeindenVerbandsfrei()) {
        ErgebnisZusammenstellungTabelle tabelleGemeinde = tabelleStimmzettelgebietFilter.filterByGemeinde(gemeinde).get();

        if (!tabelleGemeinde.isEmpty()) {
          result.add(new GemeindeTabelle(gemeinde, true, tabelleGemeinde));
        }
      }
    }

    return result;
  }

  public static class GruppeStimmzettelgebietTabelle {

    private final ErgebnisZusammenstellungGruppe gruppe;

    private final StimmzettelgebietTabelle tabelle;

    private GruppeStimmzettelgebietTabelle(ErgebnisZusammenstellungGruppe gruppe, StimmzettelgebietTabelle tabelle) {
      super();

      this.gruppe = gruppe;

      this.tabelle = tabelle;
    }

    public ErgebnisZusammenstellungGruppe getGruppe() {
      return gruppe;
    }

    public WahlverwaltungGebietInformation getStimmzettelgebiet() {
      return tabelle.getStimmzettelgebiet();
    }

    public Collection<WahlverwaltungGemeindeInformation> getGemeinden() {
      return tabelle.getGemeinden();
    }

    public StimmzettelgebietTabelle getTabelle() {
      return tabelle;
    }
  }

  public static class StimmzettelgebietTabelle {

    private final WahlverwaltungGebietInformation stimmzettelgebiet;

    private final Collection<WahlverwaltungGemeindeInformation> gemeinden;

    private final ErgebnisZusammenstellungTabelle tabelle;

    private StimmzettelgebietTabelle(WahlverwaltungGebietInformation stimmzettelgebiet, Collection<WahlverwaltungGemeindeInformation> gemeinden, ErgebnisZusammenstellungTabelle tabelle) {
      super();

      this.stimmzettelgebiet = stimmzettelgebiet;
      this.gemeinden = gemeinden;

      this.tabelle = tabelle;
    }

    public WahlverwaltungGebietInformation getStimmzettelgebiet() {
      return stimmzettelgebiet;
    }

    public Collection<WahlverwaltungGemeindeInformation> getGemeinden() {
      return gemeinden;
    }

    public ErgebnisZusammenstellungTabelle getTabelle() {
      return tabelle;
    }
  }

  public static class GruppeGemeindeTabellen {

    private final ErgebnisZusammenstellungGruppe gruppe;

    private final Collection<GemeindeTabelle> tabellen;

    private GruppeGemeindeTabellen(ErgebnisZusammenstellungGruppe gruppe, Collection<GemeindeTabelle> tabellen) {
      super();

      this.gruppe = gruppe;

      this.tabellen = unmodifiableCollection(tabellen);
    }

    public ErgebnisZusammenstellungGruppe getGruppe() {
      return gruppe;
    }

    public Collection<GemeindeTabelle> getTabellen() {
      return tabellen;
    }
  }

  public static class GemeindeGruppeTabellen {

    private final WahlverwaltungGemeindeInformation gemeinde;

    private final Collection<GruppeTabelle> tabellen;

    private GemeindeGruppeTabellen(WahlverwaltungGemeindeInformation gemeinde, Collection<GruppeTabelle> tabellen) {
      super();

      this.gemeinde = gemeinde;

      this.tabellen = unmodifiableCollection(tabellen);
    }

    public WahlverwaltungGemeindeInformation getGemeinde() {
      return gemeinde;
    }

    public Collection<GruppeTabelle> getTabellen() {
      return tabellen;
    }
  }

  public static class GemeindeTabelle {

    private final WahlverwaltungGemeindeInformation gemeinde;

    private final boolean gemeindeVerbandsfrei;

    private final ErgebnisZusammenstellungTabelle tabelle;

    private GemeindeTabelle(WahlverwaltungGemeindeInformation gemeinde, boolean gemeindeVerbandsfrei, ErgebnisZusammenstellungTabelle tabelle) {
      super();

      this.gemeinde = gemeinde;
      this.gemeindeVerbandsfrei = gemeindeVerbandsfrei;

      this.tabelle = tabelle;
    }

    public WahlverwaltungGemeindeInformation getGemeinde() {
      return gemeinde;
    }

    public boolean isGemeindeVerbandsfrei() {
      return gemeindeVerbandsfrei;
    }

    public ErgebnisZusammenstellungTabelle getTabelle() {
      return tabelle;
    }
  }

  public static class GruppeTabelle {

    private final ErgebnisZusammenstellungGruppe gruppe;

    private final ErgebnisZusammenstellungTabelle tabelle;

    private GruppeTabelle(ErgebnisZusammenstellungGruppe gruppe, ErgebnisZusammenstellungTabelle tabelle) {
      super();

      this.gruppe = gruppe;

      this.tabelle = tabelle;
    }

    public ErgebnisZusammenstellungGruppe getGruppe() {
      return gruppe;
    }

    public ErgebnisZusammenstellungTabelle getTabelle() {
      return tabelle;
    }
  }
}
