
package de.ivu.elect.dokumente.ergebnis;

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.stream.Collectors.toMap;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.document.generator.template.BaseDocument;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietPK;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.GruppePK;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.kandidatur.entity.Direktkandidatur;
import de.ivu.elect.business.kandidatur.entity.Ersatzkandidatur;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.person.entity.PersonendatenPK;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Listenverbindung;
import de.ivu.elect.business.wahlvorschlag.entity.ListenverbindungPK;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagPK;
import de.ivu.elect.dokumente.PersonBasisDokument;
import de.ivu.elect.dokumente.WasElectContext;
import de.ivu.elect.dokumente.gebiet.BasicGebietDokument;
import de.ivu.elect.dokumente.wahlvorschlag.AbstractGruppeDokument;
import de.ivu.elect.dokumente.wahlvorschlag.CoreListenverbindungDokument;
import de.ivu.elect.dokumente.wahlvorschlag.CoreWahlvorschlagsdatenDocument;

public class ErgebnisDatenCustomDataDocument extends BaseDocument {

  private static final long serialVersionUID = 1L;

  private final Logger log;

  private final WasElectContext wasElectContext;

  private final ErgebnisDaten ergebnisDaten;

  private final Stimmabgabeart stimmabgabeart;

  private final Map<Long, Gruppe> gruppePerId;

  private final Map<Long, Wahldurchfuehrungsgebiet> gebietPerId;

  private final Map<Long, Listenverbindung> listenverbindungPerId;

  private final Map<Long, Wahlvorschlag> wahlvorschlagPerId;

  private final Map<Long, Kandidatur> kandidaturPerId;

  public ErgebnisDatenCustomDataDocument(WasElectContext wasElectContext, ErgebnisDaten ergebnisDaten, List<Wahldurchfuehrungsgebiet> auswertungsgebiete, Stimmabgabeart stimmabgabeart) {
    super();

    this.log = LoggerFactory.getLogger(getClass());

    this.wasElectContext = wasElectContext;

    this.ergebnisDaten = ergebnisDaten;

    this.stimmabgabeart = stimmabgabeart;

    this.gruppePerId = ergebnisDaten.getGruppen().stream().collect(toMap(Gruppe::getId, Function.identity()));
    this.gebietPerId = ergebnisDaten.getGebiete().stream().collect(toMap(Gebiet::getId, Function.identity()));

    this.listenverbindungPerId = ergebnisDaten.getListenverbindungen().stream().collect(toMap(Listenverbindung::getId, Function.identity()));

    this.wahlvorschlagPerId = ergebnisDaten.getWahlvorschlaege().stream().collect(toMap(Wahlvorschlag::getId, Function.identity()));

    this.kandidaturPerId = ergebnisDaten.getKandidaturen().stream().collect(toMap(Kandidatur::getId, Function.identity()));
  }

  protected WasElectContext getWasElectContext() {
    return wasElectContext;
  }

  protected ErgebnisDaten getErgebnisDaten() {
    return ergebnisDaten;
  }

  protected Stimmabgabeart getStimmabgabeart() {
    return stimmabgabeart;
  }

  public Optional<Wahldurchfuehrungsgebiet> findGebiet(Object gebietReference) {
    Optional<Wahldurchfuehrungsgebiet> result = Optional.empty();

    if (gebietReference instanceof BasicGebietDokument) {
      result = findGebietById(((BasicGebietDokument) gebietReference).id);
    } else if (gebietReference instanceof GebietPK) {
      result = findGebietById(((GebietPK) gebietReference).getId());
    } else if (gebietReference instanceof Number) {
      result = findGebietById(((Number) gebietReference).longValue());
    } else if (gebietReference instanceof Wahldurchfuehrungsgebiet) {
      result = Optional.of((Wahldurchfuehrungsgebiet) gebietReference);
    }

    if (!result.isPresent()) {
      warn("KEIN Gebiet zur Gebiet-Referenz gefunden [gebietReference = {}]", gebietReference);
    }

    return result;
  }

  private Optional<Wahldurchfuehrungsgebiet> findGebietById(Long id) {
    return Optional.ofNullable(gebietPerId.get(id));
  }

  public Optional<PolitischeGruppe> findPolitischeGruppe(Object gruppeReference) {
    if (gruppeReference instanceof PolitischeGruppe) {
      return Optional.of((PolitischeGruppe) gruppeReference);
    }

    return findGruppe(gruppeReference).filter(Gruppe::isPolitischeGruppe).map(gruppe -> castTo(gruppe, PolitischeGruppe.class));
  }

  private Optional<Gruppe> findGruppe(Object gruppeReference) {
    Optional<Gruppe> result = Optional.empty();

    if (gruppeReference instanceof AbstractGruppeDokument) {
      result = findGruppeById(((AbstractGruppeDokument) gruppeReference).id);
    } else if (gruppeReference instanceof GruppePK) {
      result = findGruppeById(((GruppePK) gruppeReference).getId());
    } else if (gruppeReference instanceof Number) {
      result = findGruppeById(((Number) gruppeReference).longValue());
    } else if (gruppeReference instanceof Gruppe) {
      result = Optional.of((Gruppe) gruppeReference);
    }

    if (!result.isPresent()) {
      warn("KEINE Gruppe zur Gruppe-Referenz gefunden [gruppeReference = {}]", gruppeReference);
    }

    return result;
  }

  private Optional<Gruppe> findGruppeById(Long id) {
    return Optional.ofNullable(gruppePerId.get(id));
  }

  public Optional<Listenverbindung> findListenverbindung(Object listenverbindungReference) {
    Optional<Listenverbindung> result = Optional.empty();

    if (listenverbindungReference instanceof CoreListenverbindungDokument) {
      result = findListenverbindungById(((CoreListenverbindungDokument) listenverbindungReference).id);
    } else if (listenverbindungReference instanceof ListenverbindungPK) {
      result = findListenverbindungById(((WahlvorschlagPK) listenverbindungReference).getId());
    } else if (listenverbindungReference instanceof Number) {
      result = findListenverbindungById(((Number) listenverbindungReference).longValue());
    } else if (listenverbindungReference instanceof Listenverbindung) {
      return Optional.of((Listenverbindung) listenverbindungReference);
    }

    if (!result.isPresent()) {
      warn("KEINE Listenverbindung zur Listenverbindung-Referenz gefunden [listenverbindungReference = {}]", listenverbindungReference);
    }

    return result;
  }

  private Optional<Listenverbindung> findListenverbindungById(Long id) {
    return Optional.ofNullable(listenverbindungPerId.get(id));
  }

  public Optional<Liste> findListe(Object wahlvorschlagReference) {
    if (wahlvorschlagReference instanceof Liste) {
      return Optional.of((Liste) wahlvorschlagReference);
    }

    return findWahlvorschlag(wahlvorschlagReference).filter(Wahlvorschlag::isListe).map(wahlvorschlag -> castTo(wahlvorschlag, Liste.class));
  }

  public Optional<Direktkandidat> findDirektkandidat(Object wahlvorschlagReference) {
    if (wahlvorschlagReference instanceof Direktkandidat) {
      return Optional.of((Direktkandidat) wahlvorschlagReference);
    }

    return findWahlvorschlag(wahlvorschlagReference).filter(Wahlvorschlag::isDirektkandidat).map(wahlvorschlag -> castTo(wahlvorschlag, Direktkandidat.class));
  }

  private Optional<Wahlvorschlag> findWahlvorschlag(Object wahlvorschlagReference) {
    Optional<Wahlvorschlag> result = Optional.empty();

    if (wahlvorschlagReference instanceof CoreWahlvorschlagsdatenDocument) {
      result = findWahlvorschlagById(((CoreWahlvorschlagsdatenDocument) wahlvorschlagReference).id);
    } else if (wahlvorschlagReference instanceof WahlvorschlagPK) {
      result = findWahlvorschlagById(((WahlvorschlagPK) wahlvorschlagReference).getId());
    } else if (wahlvorschlagReference instanceof Number) {
      result = findWahlvorschlagById(((Number) wahlvorschlagReference).longValue());
    } else if (wahlvorschlagReference instanceof Wahlvorschlag) {
      return Optional.of((Wahlvorschlag) wahlvorschlagReference);
    }

    if (!result.isPresent()) {
      warn("KEINEN Wahlvorschlag zur Wahlvorschlag-Referenz gefunden [wahlvorschlagReference = {}]", wahlvorschlagReference);
    }

    return result;
  }

  private Optional<Wahlvorschlag> findWahlvorschlagById(Long id) {
    return Optional.ofNullable(wahlvorschlagPerId.get(id));
  }

  public Optional<Listenkandidatur> findListenkandidatur(Object kandidaturReference) {
    if (kandidaturReference instanceof Listenkandidatur) {
      return Optional.of((Listenkandidatur) kandidaturReference);
    }

    return findKandidatur(kandidaturReference).filter(Kandidatur::isListenkandidatur).map(kandidatur -> castTo(kandidatur, Listenkandidatur.class));
  }

  public Optional<Direktkandidatur> findDirektkandidatur(Object kandidaturReference) {
    if (kandidaturReference instanceof Direktkandidatur) {
      return Optional.of((Direktkandidatur) kandidaturReference);
    }

    return findKandidatur(kandidaturReference).filter(Kandidatur::isDirektkandidatur).map(kandidatur -> castTo(kandidatur, Direktkandidatur.class));
  }

  public Optional<Ersatzkandidatur> findErsatzkandidatur(Object kandidaturReference) {
    if (kandidaturReference instanceof Ersatzkandidatur) {
      return Optional.of((Ersatzkandidatur) kandidaturReference);
    }

    return findKandidatur(kandidaturReference).filter(Kandidatur::isErsatzkandidatur).map(kandidatur -> castTo(kandidatur, Ersatzkandidatur.class));
  }

  public Optional<Kandidatur> findKandidatur(Object kandidaturReference) {
    Optional<Kandidatur> result = Optional.empty();

    if (kandidaturReference instanceof PersonBasisDokument) { // Inklusive KandidaturDokument, etc...
      result = findKandidaturById(((PersonBasisDokument) kandidaturReference).id);
    } else if (kandidaturReference instanceof PersonendatenPK) {
      result = findKandidaturById(((PersonendatenPK) kandidaturReference).getId());
    } else if (kandidaturReference instanceof Number) {
      result = findKandidaturById(((Number) kandidaturReference).longValue());
    } else if (kandidaturReference instanceof Kandidatur) {
      return Optional.of((Kandidatur) kandidaturReference);
    }

    if (!result.isPresent()) {
      warn("KEINE Kandidatur zur Kandidatur-Referenz gefunden [kandidaturReference = {}]", kandidaturReference);
    }

    return result;
  }

  private Optional<Kandidatur> findKandidaturById(Long id) {
    return Optional.ofNullable(kandidaturPerId.get(id));
  }

  protected String getMessage(String msgKey, Object... params) {
    return wasElectContext.getMessage(msgKey, params);
  }

  protected String getMessage(Enum< ? > enumValue) {
    return wasElectContext.getMessage(enumValue);
  }

  protected void info(String format, Object... params) {
    log.info(format, params);
  }

  protected void warn(String format, Object... params) {
    log.warn(format, params);
  }
}
