
package de.ivu.elect.dokumente.wahlvorschlagsperson;

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.joining;
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.Set;

import de.ivu.document.generator.template.annotations.DocumentComplexField;
import de.ivu.document.generator.template.annotations.DocumentMethod;
import de.ivu.document.generator.template.annotations.DocumentParam;
import de.ivu.document.generator.template.annotations.DocumentRepeat;
import de.ivu.document.generator.template.annotations.DocumentSimpleField;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahlvorschlag.control.WahlvorschlagByGebietComparator;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlagsperson.entity.WahlvorschlagsPerson;
import de.ivu.elect.business.wahlvorschlagsperson.entity.WahlvorschlagsPersonTyp;
import de.ivu.elect.dokumente.CoreElectContext;
import de.ivu.elect.dokumente.PersonDokument;
import de.ivu.elect.dokumente.wahlvorschlag.CoreWahlvorschlagsdatenDocument;

public class WahlvorschlagsPersonDokument extends PersonDokument {

  private static final long serialVersionUID = 1L;

  public static final WahlvorschlagsPersonDokument EMPTY_OBJECT = new WahlvorschlagsPersonDokument();

  @DocumentSimpleField("LaufendeNummer")
  public final Long laufendeNummer;

  @DocumentSimpleField("Eingangssequenznummer")
  public final Long eingangssequenznummer;

  @DocumentComplexField("Wahlvorschlag")
  public final CoreWahlvorschlagsdatenDocument wahlvorschlagDaten;

  @DocumentSimpleField("WahlvorschlagspersonArt")
  public final String wahlvorschlagspersonArt;

  @DocumentRepeat(value = "Wahlvorschlaege", elementClass = CoreWahlvorschlagsdatenDocument.class, itemName = "wahlvorschlag")
  public final List<CoreWahlvorschlagsdatenDocument> wahlvorschlaegeDaten;

  private final Map<CoreWahlvorschlagsdatenDocument, String> wahlvorschlagspersonArtenFormattedPerWahlvorschlagDaten;

  public WahlvorschlagsPersonDokument() {
    super();

    this.laufendeNummer = 0L;
    this.eingangssequenznummer = 0L;
    this.wahlvorschlagDaten = CoreWahlvorschlagsdatenDocument.EMPTY_OBJECT;
    this.wahlvorschlagspersonArt = "";

    this.wahlvorschlaegeDaten = emptyList();
    this.wahlvorschlagspersonArtenFormattedPerWahlvorschlagDaten = new HashMap<>();
  }

  public WahlvorschlagsPersonDokument(CoreElectContext coreElectContext, EntityAufStimmzettelDaten entityAufStimmzettelDaten, Long laufendeNummer, WahlvorschlagsPerson wahlvorschlagsPerson) {
    super(coreElectContext, wahlvorschlagsPerson);

    this.laufendeNummer = laufendeNummer;
    this.eingangssequenznummer = wahlvorschlagsPerson.getEingangssequenznummer();
    this.wahlvorschlagDaten = createCoreWahlvorschlagsdatenDocument(coreElectContext, entityAufStimmzettelDaten, wahlvorschlagsPerson.getWahlvorschlag());
    this.wahlvorschlagspersonArt = formatWahlvorschlagspersonArten(coreElectContext, wahlvorschlagsPerson, singletonList(wahlvorschlagsPerson.getWahlvorschlagsPersonTyp()));

    this.wahlvorschlaegeDaten = new ArrayList<>();
    this.wahlvorschlagspersonArtenFormattedPerWahlvorschlagDaten = new HashMap<>();

    Map<Wahlvorschlag, Set<WahlvorschlagsPersonTyp>> wahlvorschlagsPersonTypenPerWahlvorschlag = collectWahlvorschlagsPersonTypenPerWahlvorschlag(wahlvorschlagsPerson);

    wahlvorschlagsPersonTypenPerWahlvorschlag.entrySet().stream().sorted((one, another) -> new WahlvorschlagByGebietComparator().compare(one.getKey(), another.getKey())).forEach(entry -> {
      CoreWahlvorschlagsdatenDocument wahlvorschlagsdatenDocument = createCoreWahlvorschlagsdatenDocument(coreElectContext, entityAufStimmzettelDaten, entry.getKey());

      this.wahlvorschlaegeDaten.add(wahlvorschlagsdatenDocument);
      this.wahlvorschlagspersonArtenFormattedPerWahlvorschlagDaten.put(wahlvorschlagsdatenDocument, formatWahlvorschlagspersonArten(coreElectContext, wahlvorschlagsPerson, entry.getValue()));
    });
  }

  @DocumentMethod // ELECT-12053
  public String getWahlvorschlagsPersonTypFuerWahlvorschlag(@DocumentParam("wahlvorschlag") Object wahlvorschlagReference) {
    return wahlvorschlagspersonArtenFormattedPerWahlvorschlagDaten.getOrDefault(wahlvorschlagReference, "");
  }

  private static Map<Wahlvorschlag, Set<WahlvorschlagsPersonTyp>> collectWahlvorschlagsPersonTypenPerWahlvorschlag(WahlvorschlagsPerson wahlvorschlagsPerson) {
    Map<Wahlvorschlag, Set<WahlvorschlagsPersonTyp>> result = new HashMap<>();

    addWahlvorschlagsPersonTypenPerWahlvorschlag(result, wahlvorschlagsPerson);

    Collection<WahlvorschlagsPerson> wahlvorschlagsPersonenIdentischVerknuepft = wahlvorschlagsPerson.getIdentischVerknuepftePersonen().stream() //
        .filter(personendaten -> personendaten.isInstanceOf(WahlvorschlagsPerson.class)) //
        .map(personendaten -> castTo(personendaten, WahlvorschlagsPerson.class)) //
        .collect(toSet());

    for (WahlvorschlagsPerson another : wahlvorschlagsPersonenIdentischVerknuepft) {
      addWahlvorschlagsPersonTypenPerWahlvorschlag(result, another);
    }

    return result;
  }

  private static void addWahlvorschlagsPersonTypenPerWahlvorschlag(Map<Wahlvorschlag, Set<WahlvorschlagsPersonTyp>> result, WahlvorschlagsPerson wahlvorschlagsPerson) {
    Wahlvorschlag wahlvorschlag = wahlvorschlagsPerson.getWahlvorschlag();

    result.putIfAbsent(wahlvorschlag, new HashSet<>());
    result.get(wahlvorschlag).add(wahlvorschlagsPerson.getWahlvorschlagsPersonTyp());
  }

  private static CoreWahlvorschlagsdatenDocument createCoreWahlvorschlagsdatenDocument(CoreElectContext coreElectContext, EntityAufStimmzettelDaten entityAufStimmzettelDaten,
      Wahlvorschlag wahlvorschlag) {
    return new CoreWahlvorschlagsdatenDocument(coreElectContext, entityAufStimmzettelDaten, wahlvorschlag, 0L);
  }

  private static String formatWahlvorschlagspersonArten(CoreElectContext coreElectContext, Personendaten personendaten, Collection<WahlvorschlagsPersonTyp> wahlvorschlagsPersonTypen) {
    return wahlvorschlagsPersonTypen.stream().sorted() //
        .map(wahlvorschlagsPersonTyp -> coreElectContext.getMessageGendered(wahlvorschlagsPersonTyp, personendaten.getGeschlecht())) //
        .collect(joining(", "));
  }
}
