
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.Objects;
import java.util.function.Function;

import javax.inject.Inject;

import de.ivu.elect.business.dokumente.control.DokumentExtraFileOutput;
import de.ivu.elect.business.dokumente.control.ProgressMonitor;
import de.ivu.elect.business.dokumente.entity.DokumentGeneratorArt;
import de.ivu.elect.business.dokumente.entity.DokumentKonverterResult;
import de.ivu.elect.business.dokumente.entity.DokumentVorlageArt;
import de.ivu.elect.business.gebietsbaum.boundary.GebietDAO;
import de.ivu.elect.business.gebietsbaum.control.GebietByBreadthFirstComparator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.wahl.boundary.WahlDAO;
import de.ivu.elect.business.wahl.boundary.WahlterminDAO;
import de.ivu.elect.business.wahl.control.WahlDokumentKonverter;
import de.ivu.elect.business.wahl.control.WahlterminDokumentContext;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlOderWahltermin;
import de.ivu.elect.business.wahl.entity.Wahltermin;
import de.ivu.elect.dokumente.BaseElectContext;
import de.ivu.elect.dokumente.konverter.AbstractDokumentKonverter;

public class WahlterminDokumentKonverter extends AbstractDokumentKonverter<WahlterminDokumentContext> implements WahlDokumentKonverter {

  @Inject
  private transient WahlterminDAO wahlterminDAO;

  @Inject
  private transient WahlDAO wahlDAO;

  @Inject
  private transient GebietDAO gebietDAO;

  public WahlterminDokumentKonverter() {
    super();
  }

  @Override
  public DokumentVorlageArt getDokumentVorlageArt() {
    return DokumentVorlageArt.WAHLTERMIN;
  }

  @Override
  public DokumentKonverterResult konvertiereInDokumentEingabe(List<Wahltermin> wahltermine, WahlterminDokumentContext context, DokumentExtraFileOutput dokumentExtraFileOutput,
      ProgressMonitor monitor) {
    Function<WahlOderWahltermin, List<Gebiet>> gebieteLoader = wahlOderWahltermin -> {
      List<Gebiet> result = new ArrayList<>();

      Wahl wahl = wahlOderWahltermin.getWahlIfWahl();

      if (wahl != null) {
        result = gebietDAO.findByWahl(wahl);
      } else {
        Wahltermin wahltermin = wahlOderWahltermin.getWahlterminIfWahltermin();

        if (wahltermin != null) {
          result = gebietDAO.findByWahltermin(wahltermin);
        }
      }

      result.sort(new GebietByBreadthFirstComparator());

      return result;
    };

    List<Wahltermin> wahltermineZuKonvertieren = new ArrayList<>(wahlterminDAO.attachAll(wahltermine));
    List<WahlterminDokument> wahlterminDokumente = new ArrayList<>();

    Wahltermin wahlterminAusgewaehlt = wahlterminDAO.attach(context.getWahltermin());

    if (wahltermineZuKonvertieren.isEmpty() && wahlterminAusgewaehlt != null) {
      wahltermineZuKonvertieren.add(wahlterminAusgewaehlt);
    }

    for (Wahltermin wahltermin : wahltermineZuKonvertieren) {
      BaseElectContext baseElectContextNurWahltermin = createBaseElectContext(context, dokumentExtraFileOutput, wahltermin, null); // ELECT-15081, KEINE Wahl im Context vorbelegen...

      boolean aktuellAusgewaehlt = wahlterminAusgewaehlt != null && Objects.equals(wahlterminAusgewaehlt, wahltermin);
      Wahl wahlInWahlterminAusgewaehlt = null;

      if (aktuellAusgewaehlt) {
        wahlInWahlterminAusgewaehlt = wahlDAO.attach(context.getWahl()); // Aktuell beim Aufruf der Dokumenterzeugung (im View/Context) ausgewählte Wahl IMMER PASSEND zum ausgewählten Wahltermin...
      }

      Function<Gebiet, PlatzhalterWahlhelfereinsatzortDokument> wahlhelfereinsatzortBuilder = createWahlhelfereinsatzortBuilder(baseElectContextNurWahltermin);
      Map<Gebiet, WahlterminGebietDokument> wahlterminGebietDokumentPerGebiet = collectWahlterminGebietDokumentPerGebiet(context, gebieteLoader.apply(wahltermin), dokumentExtraFileOutput,
          wahlhelfereinsatzortBuilder);

      wahlterminDokumente.add(new WahlterminDokument(baseElectContextNurWahltermin, aktuellAusgewaehlt, wahlInWahlterminAusgewaehlt, gebieteLoader, wahlterminGebietDokumentPerGebiet));
    }

    return new DokumentKonverterResult(new ArrayList<>(wahlterminDokumente), DokumentGeneratorArt.OPEN_OFFICE_GENERATOR);
  }

  protected Function<Gebiet, PlatzhalterWahlhelfereinsatzortDokument> createWahlhelfereinsatzortBuilder(BaseElectContext baseElectContextNurWahltermin) {
    return ignored -> new PlatzhalterWahlhelfereinsatzortDokument();
  }

  private Map<Gebiet, WahlterminGebietDokument> collectWahlterminGebietDokumentPerGebiet(WahlterminDokumentContext context, List<Gebiet> gebiete, DokumentExtraFileOutput dokumentExtraFileOutput,
      Function<Gebiet, PlatzhalterWahlhelfereinsatzortDokument> wahlhelfereinsatzortBuilder) {
    Map<Gebiet, WahlterminGebietDokument> wahlterminGebietDokumentPerGebiet = new HashMap<>();

    Map<Gebiet, List<WahlterminGebietDokument>> untergeordneteUrnenwahlbezirkePerGebiet = new HashMap<>();
    Map<Gebiet, List<WahlterminGebietDokument>> untergeordneteBriefwahlbezirkePerGebiet = new HashMap<>();
    Map<Gebiet, List<WahlterminGebietDokument>> untergeordneteSonderstimmbezirkePerGebiet = new HashMap<>();

    for (Gebiet gebiet : gebiete) {
      BaseElectContext baseElectContext = createBaseElectContext(context, dokumentExtraFileOutput, gebiet.getWahltermin(), gebiet.getWahl());

      List<WahlterminGebietDokument> untergeordneteUrnenwahlbezirke = untergeordneteUrnenwahlbezirkePerGebiet.computeIfAbsent(gebiet, ignored -> new ArrayList<>());
      List<WahlterminGebietDokument> untergeordneteBriefwahlbezirke = untergeordneteBriefwahlbezirkePerGebiet.computeIfAbsent(gebiet, ignored -> new ArrayList<>());
      List<WahlterminGebietDokument> untergeordneteSonderstimmbezirke = untergeordneteSonderstimmbezirkePerGebiet.computeIfAbsent(gebiet, ignored -> new ArrayList<>());

      wahlterminGebietDokumentPerGebiet.put(gebiet, new WahlterminGebietDokument(baseElectContext, gebiet, wahlhelfereinsatzortBuilder.apply(gebiet), untergeordneteUrnenwahlbezirke,
          untergeordneteBriefwahlbezirke, untergeordneteSonderstimmbezirke));
    }

    for (Gebiet gebiet : gebiete) {
      if (gebiet.isUrnenwahlgebiet() || gebiet.isBriefwahlgebiet()) {
        addUebergeordnet(gebiet, gebiet.getUebergeordnetesGebiet(), wahlterminGebietDokumentPerGebiet.get(gebiet), untergeordneteUrnenwahlbezirkePerGebiet, untergeordneteBriefwahlbezirkePerGebiet,
            untergeordneteSonderstimmbezirkePerGebiet);
      }
    }

    return wahlterminGebietDokumentPerGebiet;
  }

  private void addUebergeordnet(Gebiet gebiet, Gebiet uebergeordnetesGebiet, WahlterminGebietDokument wahlterminGebietDokumentFuerGebiet,
      Map<Gebiet, List<WahlterminGebietDokument>> untergeordneteUrnenwahlbezirkePerGebiet, Map<Gebiet, List<WahlterminGebietDokument>> untergeordneteBriefwahlbezirkePerGebiet,
      Map<Gebiet, List<WahlterminGebietDokument>> untergeordneteSonderstimmbezirkePerGebiet) {
    if (uebergeordnetesGebiet == null) {
      return;
    }

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

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

    addUebergeordnet(gebiet, uebergeordnetesGebiet.getUebergeordnetesGebiet(), wahlterminGebietDokumentFuerGebiet, untergeordneteUrnenwahlbezirkePerGebiet, untergeordneteBriefwahlbezirkePerGebiet,
        untergeordneteSonderstimmbezirkePerGebiet);
  }
}
