
package de.ivu.elect.dokumente;

import static de.ivu.elect.business.wahl.entity.WahlOderWahltermin.from;
import static de.ivu.elect.business.wahl.entity.WahlParameterWerte.DOKUMENTENERZEUGUNG_KLEINE_DOKUMENTE_FUER_TEST_ERZEUGEN;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.Nullable;

import de.ivu.document.generator.template.BaseDocument;
import de.ivu.elect.business.benutzerverwaltung.entity.Benutzer;
import de.ivu.elect.business.dokumente.control.DokumentExtraFileOutput;
import de.ivu.elect.business.exchange.control.AbstractContextMitCustomParameter;
import de.ivu.elect.business.gebietsbaum.boundary.GebietsartTemplate;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.konfiguration.entity.AbstractKonfigurationLoader;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.person.entity.Geschlecht;
import de.ivu.elect.business.system.entity.SystemGebietsebene;
import de.ivu.elect.business.wahl.entity.AbstractWahlParameterLoader;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlOderWahltermin;
import de.ivu.elect.business.wahl.entity.WahlParameterWerte;
import de.ivu.elect.business.wahl.entity.Wahltermin;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.faces.UserLocale;
import de.ivu.elect.presentation.gebietsbaum.GebietNummerFormatter;
import de.ivu.elect.presentation.person.PersonendatenFormatter;
import de.ivu.elect.util.MoneyUtil;

public class BaseElectContext extends AbstractContextMitCustomParameter implements Serializable {

  private static final long serialVersionUID = 1L;

  private final Benutzer benutzer;

  private final Locale locale;

  private final TimeZone timeZone;

  private final Messages messages;

  private final Wahl wahl;

  private final Wahltermin wahltermin;

  private final DokumentExtraFileOutput dokumentExtraFileOutput;

  private final PersonendatenFormatter personendatenFormatter;

  private final Collection< ? extends GebietsartTemplate> gebietsartTemplates;

  private final BaseElectCustomDataDocumentBuilder baseElectCustomDataDocumentBuilder;

  private final GebietNummerFormatter gebietNummerFormatter;

  private final CustomParameterDokument customParameterDokument;

  /**
   * @deprecated
   * @see BaseElectContext#customParameterMap
   */
  @Deprecated
  private final Map<String, Object> cache = new HashMap<>();

  private final boolean generateSmallDocumentForTest;

  private final AbstractWahlParameterLoader wahlParameterLoader;

  private final AbstractKonfigurationLoader konfigurationLoader;

  private final Mandant mandant;

  private final SystemGebietsebene systemGebietsebene;

  protected BaseElectContext(BaseElectContext other) {
    this(other.getDokumentExtraFileOutput(), other.getAnwender(), other.getWahltermin(), other.getWahl(), other.getLocale(), other.getTimeZone(), other.getMessages(),
        other.getPersonendatenFormatter(), other.getGebietsartTemplates(), other.getCustomDataDocumentBuilder(), other.getGebietNummerFormatter(), other.getWahlParameterLoader(),
        other.isGenerateSmallDocumentForTest(), other.getCustomParameterDokument(), other.getKonfigurationLoader(), other.getMandant());

    this.contextParameters.putAll(other.getContextParameters());
  }

  public BaseElectContext(BaseElectContext other, Wahl wahl) {
    this(other.getDokumentExtraFileOutput(), other.getAnwender(), other.getWahltermin(), wahl, other.getLocale(), other.getTimeZone(), other.getMessages(), other.getPersonendatenFormatter(),
        other.getGebietsartTemplates(), other.getCustomDataDocumentBuilder(), other.getGebietNummerFormatter(), other.getWahlParameterLoader(), other.isGenerateSmallDocumentForTest(),
        other.getCustomParameterDokument(), other.getKonfigurationLoader(), other.getMandant());

    this.contextParameters.putAll(other.getContextParameters());
  }

  public BaseElectContext(DokumentExtraFileOutput dokumentExtraFileOutput, Benutzer benutzer, Wahltermin wahltermin, Wahl wahl, Locale locale, TimeZone timeZone, Messages messages,
      PersonendatenFormatter personendatenFormatter, Collection< ? extends GebietsartTemplate> gebietsartTemplates, BaseElectCustomDataDocumentBuilder baseElectCustomDataDocumentBuilder,
      GebietNummerFormatter gebietNummerFormatter, AbstractWahlParameterLoader wahlParameterLoader, AbstractKonfigurationLoader konfigurationLoader, Mandant mandant) {
    this(dokumentExtraFileOutput, benutzer, wahltermin, wahl, locale, timeZone, messages, personendatenFormatter, gebietsartTemplates, baseElectCustomDataDocumentBuilder, gebietNummerFormatter,
        wahlParameterLoader, bestimmeGenerateSmallDocumentForTest(wahltermin, wahl, wahlParameterLoader),
        new CustomParameterDokument(from(wahltermin, wahl), mandant, locale, konfigurationLoader, wahlParameterLoader), konfigurationLoader, mandant);
  }

  private BaseElectContext(DokumentExtraFileOutput dokumentExtraFileOutput, Benutzer benutzer, Wahltermin wahltermin, Wahl wahl, Locale locale, TimeZone timeZone, Messages messages,
      PersonendatenFormatter personendatenFormatter, Collection< ? extends GebietsartTemplate> gebietsartTemplates, BaseElectCustomDataDocumentBuilder baseElectCustomDataDocumentBuilder,
      GebietNummerFormatter gebietNummerFormatter, AbstractWahlParameterLoader wahlParameterLoader, boolean generateSmallDocumentForTest, CustomParameterDokument customParameterDokument,
      AbstractKonfigurationLoader konfigurationLoader, Mandant mandant) {
    super();

    this.benutzer = benutzer;

    this.locale = locale;
    this.timeZone = timeZone;
    this.messages = messages;

    this.wahltermin = wahltermin;
    this.wahl = wahl;

    this.dokumentExtraFileOutput = dokumentExtraFileOutput;
    this.personendatenFormatter = personendatenFormatter;
    this.gebietsartTemplates = gebietsartTemplates;
    this.baseElectCustomDataDocumentBuilder = baseElectCustomDataDocumentBuilder;
    this.gebietNummerFormatter = gebietNummerFormatter;
    this.generateSmallDocumentForTest = generateSmallDocumentForTest;
    this.wahlParameterLoader = wahlParameterLoader;
    this.customParameterDokument = customParameterDokument;
    this.konfigurationLoader = konfigurationLoader;
    this.mandant = mandant;
    this.systemGebietsebene = customParameterDokument.getSystemGebietsebene();
  }

  private static boolean bestimmeGenerateSmallDocumentForTest(Wahltermin wahltermin, Wahl wahl, AbstractWahlParameterLoader wahlParameterLoader) {
    return resolveBooleanValue(DOKUMENTENERZEUGUNG_KLEINE_DOKUMENTE_FUER_TEST_ERZEUGEN, wahltermin, wahl, wahlParameterLoader);
  }

  private final Map<WahlParameterWerte, Boolean> booleanLookupCache = new ConcurrentHashMap<>();

  public boolean resolveBooleanValue(WahlParameterWerte wahlParameterWerte) {
    if (wahlParameterWerte == null) {
      return false;
    }

    return booleanLookupCache.computeIfAbsent(wahlParameterWerte, x -> resolveBooleanValue(wahlParameterWerte, wahltermin, wahl, wahlParameterLoader));
  }

  private static boolean resolveBooleanValue(WahlParameterWerte wahlParameterWerte, Wahltermin wahltermin, Wahl wahl, AbstractWahlParameterLoader wahlParameterLoader) {
    WahlOderWahltermin wahlOderWahltermin = from(wahltermin, wahl);
    if (wahlParameterLoader != null && wahlOderWahltermin != null) {
      return wahlParameterLoader.resolveBooleanValue(wahlOderWahltermin, wahlParameterWerte);
    }
    return false;

  }

  public DokumentExtraFileOutput getDokumentExtraFileOutput() {
    return dokumentExtraFileOutput;
  }

  public Benutzer getAnwender() {
    return benutzer;
  }

  public Wahl getWahl() {
    return wahl;
  }

  public Locale getLocale() {
    return locale;
  }

  public TimeZone getTimeZone() {
    return timeZone;
  }

  public Messages getMessages() {
    return messages;
  }

  public String getMessage(Boolean value) {
    if (getMessages() == null) {
      return "";
    }

    return getMessages().lokalisiere(value);
  }

  public String getMessage(Enum< ? > enumValue) {
    if (getMessages() == null) {
      return "";
    }

    return getMessages().lokalisiere(enumValue);
  }

  public String getMessageGendered(Enum< ? > enumValue, Geschlecht geschlecht) {
    if (getMessages() == null) {
      return "";
    }

    return getMessages().lokalisiere(enumValue, geschlecht);
  }

  public String getMessage(String msgKey, Object... params) {
    if (getMessages() == null) {
      return "";
    }

    return getMessages().getMessageForMessageKey(msgKey, params);
  }

  public PersonendatenFormatter getPersonendatenFormatter() {
    return personendatenFormatter;
  }

  public Collection< ? extends GebietsartTemplate> getGebietsartTemplates() {
    return gebietsartTemplates;
  }

  public Wahltermin getWahltermin() {
    return wahltermin;
  }

  public WahlOderWahltermin getWahlOderWahltermin() {
    return from(getWahltermin(), getWahl());
  }

  public String formatDateTimeMedium(Date dateTime) {
    return UserLocale.formatDateTimeMedium(dateTime, locale);
  }

  public String formatDateShort(@Nullable Date dateTime) {
    return UserLocale.formatDateShort(dateTime, locale);
  }

  public String formatDateMedium(@Nullable Date dateTime) {
    return UserLocale.formatDateMedium(dateTime, locale);
  }

  public String formatDateLong(@Nullable Date dateTime) {
    return UserLocale.formatDateLong(dateTime, locale);
  }

  public String formatTimeHHmm(Date dateTime) {
    return UserLocale.formatTimeHHmm(dateTime, locale);
  }

  public String formatTimeHHmmss(Date dateTime) {
    return UserLocale.formatTimeHHmmss(dateTime, locale);
  }

  public String formatWaehrungskuerzel(String waehrungAlsIsoCode) {
    return MoneyUtil.formatWaehrungskuerzel(locale, waehrungAlsIsoCode);
  }

  public String formatBetragMitWaehrungskuerzel(BigDecimal betrag, String waehrungAlsIsoCode) {
    return MoneyUtil.formatBetragMitWaehrungskuerzel(locale, betrag, waehrungAlsIsoCode);
  }

  public String formatWaehrungssymbol(String waehrungAlsIsoCode) {
    return MoneyUtil.formatWaehrungssymbol(locale, waehrungAlsIsoCode);
  }

  public String formatBetragMitWaehrungssymbol(BigDecimal betrag, String waehrungAlsIsoCode) {
    return MoneyUtil.formatBetragMitWaehrungssymbol(locale, betrag, waehrungAlsIsoCode);
  }

  public BaseElectCustomDataDocumentBuilder getCustomDataDocumentBuilder() {
    return baseElectCustomDataDocumentBuilder;
  }

  public GebietNummerFormatter getGebietNummerFormatter() {
    return gebietNummerFormatter;
  }

  /**
   * Wenn dies true ist, können Dokumente, die ansonsten sehr groß sind und beim Testen Performance-Probleme verursachen, mit reduzierten Daten erzeugt werden, um das Tesetn zu vereinfachen.
   *
   * @return true, wenn im Test-Modus, false, wenn das Dokument vollständig erzeugt werden muss
   */
  public boolean isGenerateSmallDocumentForTest() {
    return generateSmallDocumentForTest;
  }

  private final Map<DocumentClassParameterKey, BaseDocument> documentCache = new WeakHashMap<>();

  public <D extends BaseDocument> void cacheDokument(Class<D> dokumentClass, ParameterKey paremeterKey, D document) {
    documentCache.put(new DocumentClassParameterKey(dokumentClass, paremeterKey), document);
  }

  public <D extends BaseDocument> D lookupCachedDokument(Class<D> dokumentClass, ParameterKey paremeterKey) {
    return (D) documentCache.get(new DocumentClassParameterKey(dokumentClass, paremeterKey));
  }

  public AbstractWahlParameterLoader getWahlParameterLoader() {
    return wahlParameterLoader;
  }

  public CustomParameterDokument getCustomParameterDokument() {
    return customParameterDokument;
  }

  /**
   * @deprecated
   * @see BaseElectContext#getCustomParameters
   */
  @Deprecated
  public Map<String, Object> getCache() {
    return cache;
  }

  public AbstractKonfigurationLoader getKonfigurationLoader() {
    return konfigurationLoader;
  }

  public Long getAnzahlStimmenProWaehlerMaximal(Gebiet gebiet) {
    return 0L;
  }

  public Mandant getMandant() {
    return mandant;
  }

  public SystemGebietsebene getSystemGebietsebene() {
    return systemGebietsebene;
  }
}
