
package de.ivu.elect.dokumente.wahltermin;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import de.ivu.document.generator.template.annotations.DocumentRepeat;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.wahl.entity.WahlOderWahltermin;
import de.ivu.elect.dokumente.BaseElectContext;
import de.ivu.elect.dokumente.ElectDocument;

public abstract class AbstractWahlOderWahlterminDokument extends ElectDocument {

  private static final long serialVersionUID = 1L;

  @DocumentRepeat(value = "Gebiete", elementClass = WahlterminGebietDokument.class, itemName = "gebiet")
  public final List<WahlterminGebietDokument> gebiete;

  @DocumentRepeat(value = "Urnenwahlbezirke", elementClass = WahlterminGebietDokument.class, itemName = "urnenwahlbezirk")
  public final List<WahlterminGebietDokument> urnenwahlbezirke;

  @DocumentRepeat(value = "Briefwahlbezirke", elementClass = WahlterminGebietDokument.class, itemName = "briefwahlbezirk")
  public final List<WahlterminGebietDokument> briefwahlbezirke;

  // ELECT-8835
  @DocumentRepeat(value = "Sonderstimmbezirke", elementClass = WahlterminGebietDokument.class, itemName = "sonderstimmbezirk")
  public final List<WahlterminGebietDokument> sonderstimmbezirke;

  public AbstractWahlOderWahlterminDokument(WahlOderWahltermin wahlOderWahltermin, BaseElectContext baseElectContext, Function<WahlOderWahltermin, List<Gebiet>> gebieteLoader,
      Function<Gebiet, PlatzhalterWahlhelfereinsatzortDokument> wahlhelfereinsatzortBuilder) {
    super(baseElectContext);

    this.gebiete = new ArrayList<>();

    this.urnenwahlbezirke = new ArrayList<>();
    this.briefwahlbezirke = new ArrayList<>();
    this.sonderstimmbezirke = new ArrayList<>();

    List<Gebiet> gebieteInWahlOderWahltermin = gebieteLoader.apply(wahlOderWahltermin);

    Map<Gebiet, WahlterminGebietDokument> wahlterminGebietDokumentNachGebiet = new HashMap<>();

    Map<Gebiet, List<WahlterminGebietDokument>> untergeordneteUrnenwahlbezirkeNachGebiet = new HashMap<>();
    Map<Gebiet, List<WahlterminGebietDokument>> untergeordneteBriefwahlbezirkeNachGebiet = new HashMap<>();
    Map<Gebiet, List<WahlterminGebietDokument>> untergeordneteSonderstimmbezirkeNachGebiet = new HashMap<>();

    for (Gebiet gebiet : gebieteInWahlOderWahltermin) {
      List<WahlterminGebietDokument> untergeordneteUrnenwahlbezirke = untergeordneteUrnenwahlbezirkeNachGebiet.computeIfAbsent(gebiet, x -> new ArrayList<>());
      List<WahlterminGebietDokument> untergeordneteBriefwahlbezirke = untergeordneteBriefwahlbezirkeNachGebiet.computeIfAbsent(gebiet, x -> new ArrayList<>());
      List<WahlterminGebietDokument> untergeordneteSonderstimmbezirke = untergeordneteSonderstimmbezirkeNachGebiet.computeIfAbsent(gebiet, x -> new ArrayList<>());

      WahlterminGebietDokument gebietDokument = new WahlterminGebietDokument(baseElectContext, gebiet, wahlhelfereinsatzortBuilder.apply(gebiet), untergeordneteUrnenwahlbezirke,
          untergeordneteBriefwahlbezirke, untergeordneteSonderstimmbezirke);

      wahlterminGebietDokumentNachGebiet.put(gebiet, gebietDokument);

      this.gebiete.add(gebietDokument);

      if (Boolean.TRUE.equals(gebiet.getSonderstimmbezirk())) {
        this.sonderstimmbezirke.add(gebietDokument);
      } else {
        if (gebiet.isUrnenwahlgebiet()) {
          this.urnenwahlbezirke.add(gebietDokument);
        }

        if (gebiet.isBriefwahlgebiet()) {
          this.briefwahlbezirke.add(gebietDokument);
        }
      }
    }

    for (Gebiet gebiet : gebieteInWahlOderWahltermin) {
      if (gebiet.isUrnenwahlgebiet() || gebiet.isBriefwahlgebiet()) {
        addUebergeordnet(gebiet, gebiet.getUebergeordnetesGebiet(), wahlterminGebietDokumentNachGebiet.get(gebiet), untergeordneteUrnenwahlbezirkeNachGebiet, untergeordneteBriefwahlbezirkeNachGebiet,
            untergeordneteSonderstimmbezirkeNachGebiet);
      }
    }
  }

  private void addUebergeordnet(Gebiet gebiet, Gebiet uebergeordnetesGebiet, WahlterminGebietDokument wahlterminGebietDokumentFuerGebiet,
      Map<Gebiet, List<WahlterminGebietDokument>> untergeordneteUrnenwahlbezirkeNachGebiet, Map<Gebiet, List<WahlterminGebietDokument>> untergeordneteBriefwahlbezirkeNachGebiet,
      Map<Gebiet, List<WahlterminGebietDokument>> untergeordneteSonderstimmbezirkeNachGebiet) {
    if (uebergeordnetesGebiet == null) {
      return;
    }

    if (Boolean.TRUE.equals(gebiet.getSonderstimmbezirk())) {
      if (untergeordneteSonderstimmbezirkeNachGebiet.containsKey(uebergeordnetesGebiet)) {
        untergeordneteSonderstimmbezirkeNachGebiet.get(uebergeordnetesGebiet).add(wahlterminGebietDokumentFuerGebiet);
      }
    } else {
      if (untergeordneteUrnenwahlbezirkeNachGebiet.containsKey(uebergeordnetesGebiet) && gebiet.isUrnenwahlgebiet()) {
        untergeordneteUrnenwahlbezirkeNachGebiet.get(uebergeordnetesGebiet).add(wahlterminGebietDokumentFuerGebiet);
      }

      if (untergeordneteBriefwahlbezirkeNachGebiet.containsKey(uebergeordnetesGebiet) && gebiet.isBriefwahlgebiet()) {
        untergeordneteBriefwahlbezirkeNachGebiet.get(uebergeordnetesGebiet).add(wahlterminGebietDokumentFuerGebiet);
      }
    }

    addUebergeordnet(gebiet, uebergeordnetesGebiet.getUebergeordnetesGebiet(), wahlterminGebietDokumentFuerGebiet, untergeordneteUrnenwahlbezirkeNachGebiet, untergeordneteBriefwahlbezirkeNachGebiet,
        untergeordneteSonderstimmbezirkeNachGebiet);
  }
}
