
package de.ivu.elect.business.kandidatur.control;

import static java.util.Arrays.asList;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.endsWith;
import static org.apache.commons.lang3.StringUtils.isNoneBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.replaceIgnoreCase;
import static org.apache.commons.lang3.StringUtils.splitByWholeSeparator;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;

import java.io.Serializable;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;

import de.ivu.elect.business.anschrift.entity.Anschrift;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.wahl.entity.AbstractWahlParameterLoader;
import de.ivu.elect.business.wahl.entity.CommonWahlart;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlParameterWerte;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.util.StringJoiner;

public class StimmzettelFormatter implements Serializable {

  private static final long serialVersionUID = 1L;

  private static final String DOKUMENT_PLATZHALTER_STIMMZETTEL_FORMAT_PART_SEPARATOR = "|";

  @Inject
  private transient Messages messages;

  public StimmzettelFormatter() {
    super();
  }

  public StimmzettelFormatter(Messages messages) {
    super();

    this.messages = messages;
  }

  public String formatKandidaturNamenUndPseudonyme(AbstractWahlParameterLoader wahlParameterLoader, Kandidatur kandidatur) {
    if (kandidatur != null) {
      return formatKandidaturNamenUndPseudonyme(resolveDokumentPlatzhalterFormat(wahlParameterLoader, kandidatur, WahlParameterWerte.DOKUMENT_PLATZHALTER_STIMMZETTEL_KANDIDATUR_NAMEN_FORMAT),
          kandidatur);
    }

    return formatKandidaturNamenUndPseudonyme(kandidatur);
  }

  public String formatKandidaturNamenUndPseudonyme(String format, Kandidatur kandidatur) {
    return formatDokumentPlatzhalterFormat(format, kandidatur, this::formatKandidaturNamenUndPseudonyme);
  }

  public String formatKandidaturNamenUndPseudonyme(Kandidatur kandidatur) {
    if (kandidatur == null) {
      return "";
    }

    if (CommonWahlart.BUNDESTAGSWAHL.equalsWahlart(kandidatur.getWahl()) || CommonWahlart.EUROPAWAHL.equalsWahlart(kandidatur.getWahl())) {
      // Für BTW und EUW vorgegeben durch Destatis (WASDE-1827 und WASDE-2062)
      return getKandidaturNamePraesentation(kandidatur);
    }

    String separator = getKandidaturNamenSeparator(kandidatur);

    String kandidaturNamen = formatKandidaturNamen(kandidatur);
    String kandidaturPseudonyme = formatKandidaturPseudonyme(kandidatur);

    if (isNoneBlank(kandidaturNamen, kandidaturPseudonyme)) {
      if (!endsWith(kandidaturNamen, separator)) {
        kandidaturNamen += separator;
      }

      kandidaturNamen += kandidaturPseudonyme;
    }

    if (endsWith(kandidaturNamen, separator)) {
      kandidaturNamen = kandidaturNamen.substring(0, kandidaturNamen.lastIndexOf(separator));
    }

    return kandidaturNamen;
  }

  public String formatKandidaturNamen(AbstractWahlParameterLoader wahlParameterLoader, Kandidatur kandidatur) {
    if (kandidatur != null) {
      return formatKandidaturNamen(resolveDokumentPlatzhalterFormat(wahlParameterLoader, kandidatur, WahlParameterWerte.DOKUMENT_PLATZHALTER_STIMMZETTEL_KANDIDATUR_NAMEN_FORMAT), kandidatur);
    }

    return formatKandidaturNamen(kandidatur);
  }

  public String formatKandidaturNamen(String format, Kandidatur kandidatur) {
    return formatDokumentPlatzhalterFormat(format, kandidatur, this::formatKandidaturNamen);
  }

  public String formatKandidaturNamen(Kandidatur kandidatur) {
    String result = getKandidaturNamePraesentation(kandidatur);

    String separator = getKandidaturNamenSeparator(kandidatur);

    if (endsWith(result, separator)) {
      result = result.substring(0, result.lastIndexOf(separator));
    }

    return result;
  }

  public String formatKandidaturVornamen(Kandidatur kandidatur) {
    if (kandidatur == null) {
      return "";
    }

    return trimToEmpty(defaultString(kandidatur.getVornamen(), kandidatur.getRufname()));
  }

  public String formatKandidaturRufnamen(Kandidatur kandidatur) {
    if (kandidatur == null) {
      return "";
    }

    return trimToEmpty(defaultString(kandidatur.getRufname(), kandidatur.getVornamen()));
  }

  public String formatKandidaturNachname(Kandidatur kandidatur) {
    if (kandidatur == null) {
      return "";
    }

    return joinNonBlankValues(" ", // ELECT-8844
        kandidatur.getTitel(), //
        kandidatur.getFamiliennameBestandteil(), //
        kandidatur.getFamilienname() //
    );
  }

  public String formatKandidaturPseudonyme(Kandidatur kandidatur) {
    if (kandidatur == null) {
      return "";
    }

    if (CommonWahlart.BUNDESTAGSWAHL.equalsWahlart(kandidatur.getWahl()) || CommonWahlart.EUROPAWAHL.equalsWahlart(kandidatur.getWahl())) {
      // Für BTW und EUW vorgegeben durch Destatis (WASDE-1827 und WASDE-2062)
      String pseudonyme = StringJoiner.joinWithComma(kandidatur.getKuenstlername(), kandidatur.getOrdensname()).trim();

      if (isNotBlank(pseudonyme)) {
        return "(" + pseudonyme + ")";
      }

      return "";
    }

    String result = getKandidaturPseudonyme(kandidatur);

    String separator = getKandidaturNamenSeparator(kandidatur);

    if (endsWith(result, separator)) {
      result = result.substring(0, result.lastIndexOf(separator));
    }

    return result;
  }

  public String formatKandidaturInfotext(AbstractWahlParameterLoader wahlParameterLoader, Kandidatur kandidatur) {
    if (kandidatur != null) {
      return formatKandidaturInfotext(resolveDokumentPlatzhalterFormat(wahlParameterLoader, kandidatur, WahlParameterWerte.DOKUMENT_PLATZHALTER_STIMMZETTEL_KANDIDATUR_INFOTEXT_FORMAT), kandidatur);
    }

    return formatKandidaturInfotext(kandidatur);
  }

  public String formatKandidaturInfotext(String format, Kandidatur kandidatur) {
    return formatDokumentPlatzhalterFormat(format, kandidatur, this::formatKandidaturInfotext);
  }

  public String formatKandidaturInfotext(Kandidatur kandidatur) {
    return getKandidaturInfotext(kandidatur);
  }

  private String resolveDokumentPlatzhalterFormat(AbstractWahlParameterLoader wahlParameterLoader, Kandidatur kandidatur, WahlParameterWerte wahlParameterWerte) {
    if (wahlParameterLoader != null && kandidatur != null && kandidatur.getWahl() != null) {
      return wahlParameterLoader.resolveStringValue(kandidatur.getWahl(), wahlParameterWerte);
    }

    return null;
  }

  private String formatDokumentPlatzhalterFormat(String format, Kandidatur kandidatur, Function<Kandidatur, String> defaultFormatter) {
    String[] formatParts = splitByWholeSeparator(format, DOKUMENT_PLATZHALTER_STIMMZETTEL_FORMAT_PART_SEPARATOR);

    if (kandidatur != null && formatParts != null && formatParts.length > 0) {
      String result = "";

      Map<String, String> mapping = new HashMap<>();
      mapping.put("$titel", kandidatur.getTitel());
      mapping.put("$namensbestandteile", kandidatur.getFamiliennameBestandteil());
      mapping.put("$familienname", kandidatur.getFamilienname());
      mapping.put("$rufname", kandidatur.getRufname());
      mapping.put("$vornamen", kandidatur.getVornamen());
      mapping.put("$kuenstlername", kandidatur.getKuenstlername());
      mapping.put("$ordensname", kandidatur.getOrdensname());
      mapping.put("$akademischeGrade", getKandidaturAkademischeGrade(kandidatur));
      mapping.put("$berufe", getKandidaturBerufe(kandidatur));
      mapping.put("$ehrenaemter", getKandidaturEhrenaemter(kandidatur));
      mapping.put("$wohnort", getKandidaturOrt(kandidatur));
      mapping.put("$ortsteil", getKandidaturOrtsteil(kandidatur));
      mapping.put("$strasse", getKandidaturStrasse(kandidatur));
      mapping.put("$hausnummer", getKandidaturHausnummer(kandidatur));
      mapping.put("$hausnummerzusatz", getKandidaturHausnummerzusatz(kandidatur));

      for (String part : formatParts) {
        String partFormatted = part;

        boolean partFormattedPlaceholderNotFound = true;
        boolean partFormattedPlaceholderReplaced = false;

        for (Entry<String, String> entry : mapping.entrySet()) {
          String placeholder = entry.getKey();
          String value = entry.getValue();

          if (partFormatted.contains(placeholder)) {
            partFormattedPlaceholderNotFound = false;

            if (isNotBlank(value)) {
              partFormattedPlaceholderReplaced = true;
              partFormatted = replaceIgnoreCase(partFormatted, placeholder, trimToEmpty(value));
            }
          }
        }

        if (partFormattedPlaceholderReplaced || partFormattedPlaceholderNotFound) {
          result += partFormatted;
        }
      }

      return result;
    }

    return defaultFormatter.apply(kandidatur);
  }

  public String getKandidaturNamenSeparator(Kandidatur kandidatur) {
    if (kandidatur == null) {
      return "";
    }

    return getMessageOrDefault(kandidatur, "StimmzettelFormatter.KandidaturNamen.separator");
  }

  /**
   * @see StimmzettelFormatter#formatKandidaturNamen
   * @see ELECTMUC-508, ELECTMUC-520
   * @deprecated
   */
  @Deprecated
  public String getKandidaturNamePraesentation(Kandidatur kandidatur) {
    if (kandidatur == null) {
      return "";
    }

    Wahl wahl = kandidatur.getWahl();

    if (CommonWahlart.BUNDESTAGSWAHL.equalsWahlart(wahl) || CommonWahlart.EUROPAWAHL.equalsWahlart(wahl)) {
      // Für BTW und EUW vorgegeben durch Destatis (WASDE-1827 und WASDE-2062)
      return getKandidaturNamePraesentationBtwEuw(kandidatur);
    }

    String result = formatKandidaturNachname(kandidatur);

    String vornamen = getKandidaturNamePraesentationVornamen(kandidatur);

    if (isNotBlank(vornamen)) {
      if (isNotBlank(result)) {
        result += getMessageOrDefault(kandidatur, "StimmzettelFormatter.KandidaturVornameNachname.separator");
      }

      result += vornamen;
    }

    return trimToEmpty(result);
  }

  public String getKandidaturNamePraesentationVornamen(Kandidatur kandidatur) {
    if (kandidatur != null) {
      Wahl wahl = kandidatur.getWahl();

      if (EnumSet.of( //
          CommonWahlart.UNBEKANNT, CommonWahlart.KREISTAGSWAHL, CommonWahlart.GEMEINDERATSWAHL, // ELECT-16259
          CommonWahlart.BUERGERMEISTERWAHL // ELECT-13166
      ).stream().anyMatch(value -> value.equalsWahlart(wahl))) {
        return formatKandidaturRufnamen(kandidatur);
      }
    }

    return formatKandidaturVornamen(kandidatur);
  }

  /**
   * Für BTW und EUW vorgegeben durch Destatis (WASDE-1827 und WASDE-2062)
   */
  private String getKandidaturNamePraesentationBtwEuw(Kandidatur kandidatur) {
    String result;

    if (kandidatur.isListenkandidatur() || kandidatur.isErsatzkandidatur()) {
      result = joinNonBlankValues(" ", // ELECT-8844
          kandidatur.getTitel(), //
          formatKandidaturRufnamen(kandidatur), // WASDE-2062
          kandidatur.getFamiliennameBestandteil(), //
          kandidatur.getFamilienname() //
      );
    } else {
      result = formatKandidaturNachname(kandidatur);

      String vornamen = formatKandidaturRufnamen(kandidatur); //

      if (isNotBlank(vornamen)) {
        if (isNotBlank(result)) {
          result += getMessageOrDefault(kandidatur, "StimmzettelFormatter.KandidaturVornameNachname.separator");
        }

        result += vornamen;
      }
    }

    String pseudonyme = formatKandidaturPseudonyme(kandidatur);

    if (isNotBlank(pseudonyme)) {
      result += " " + pseudonyme;
    }

    return trimToEmpty(result);
  }

  /**
   * @see ELECTMUC-520
   * @see StimmzettelFormatter#formatKandidaturPseudonyme
   * @deprecated
   */
  @Deprecated
  public String getKandidaturPseudonyme(Kandidatur kandidatur) {
    if (kandidatur == null) {
      return "";
    }

    if (CommonWahlart.BUNDESTAGSWAHL.equalsWahlart(kandidatur.getWahl()) || CommonWahlart.EUROPAWAHL.equalsWahlart(kandidatur.getWahl())) {
      // Für BTW und EUW vorgegeben durch Destatis (WASDE-1827 und WASDE-2062)
      return formatKandidaturPseudonyme(kandidatur);
    }

    StringBuilder result = new StringBuilder();

    if (isNotBlank(kandidatur.getKuenstlername())) {
      result.append(getKuenstlernamePrefix(kandidatur));
      result.append(kandidatur.getKuenstlername());
      result.append(getKandidaturNamenSeparator(kandidatur));
    }

    if (isNotBlank(kandidatur.getOrdensname())) {
      result.append(getOrdensnamePrefix(kandidatur));
      result.append(kandidatur.getOrdensname());
      result.append(getKandidaturNamenSeparator(kandidatur));
    }

    return result.toString();
  }

  private String getKuenstlernamePrefix(Kandidatur kandidatur) {
    return getMessageOrDefault(kandidatur, "StimmzettelFormatter.KandidaturKuenstlername.prefix");
  }

  private String getOrdensnamePrefix(Kandidatur kandidatur) {
    return getMessageOrDefault(kandidatur, "StimmzettelFormatter.KandidaturOrdensname.prefix");
  }

  public String getKandidaturInfotextSeparator(Kandidatur kandidatur) {
    return getKandidaturNamenSeparator(kandidatur);
  }

  /**
   * @see StimmzettelFormatter#formatKandidaturInfotext
   * @see ELECTMUC-508, ELECTMUC-520
   * @deprecated
   */
  @Deprecated
  public String getKandidaturInfotext(Kandidatur kandidatur) {
    return getKandidaturInfotext(kandidatur, true, false);
  }

  public String getKandidaturInfotext(Kandidatur kandidatur, boolean includesOrt, boolean includesOrtsteil) {
    if (kandidatur == null) {
      return "";
    }

    String akademischeGrade = getKandidaturAkademischeGrade(kandidatur);
    String berufe = getKandidaturBerufe(kandidatur);
    String ehrenaemter = getKandidaturEhrenaemter(kandidatur);
    String ort = includesOrt ? getKandidaturOrt(kandidatur) : ""; // ELECTKOM-709
    String ortsteil = includesOrtsteil ? getKandidaturOrtsteil(kandidatur) : ""; // ELECTKOM-709

    return joinNonBlankValues(", ", getKandidaturInfotextSorted(kandidatur.getWahl(), akademischeGrade, berufe, ehrenaemter, ort, ortsteil));
  }

  private List<String> getKandidaturInfotextSorted(Wahl wahl, String akademischeGrade, String berufe, String ehrenaemter, String ort, String ortsteil) {
    return asList(akademischeGrade, berufe, ehrenaemter, ort, ortsteil);
  }

  public String getKandidaturAkademischeGrade(Kandidatur kandidatur) {
    if (kandidatur == null) {
      return "";
    }

    return getValuesFormatted("StimmzettelFormatter.KandidaturAkademischeGrade.separator", kandidatur, kandidatur.getAkademischerGradAsList());
  }

  public String getKandidaturEhrenaemter(Kandidatur kandidatur) {
    if (kandidatur == null) {
      return "";
    }

    return getValuesFormatted("StimmzettelFormatter.KandidaturEhrenaemter.separator", kandidatur, kandidatur.getEhrenamtAsList());
  }

  public String getKandidaturBerufe(Kandidatur kandidatur) {
    if (kandidatur == null) {
      return "";
    }

    return getValuesFormatted("StimmzettelFormatter.KandidaturBerufe.separator", kandidatur, kandidatur.getBerufAsList());
  }

  public String getKandidaturPlz(Kandidatur kandidatur) {
    Anschrift anschrift = getKandidaturAnschrift(kandidatur);

    if (anschrift == null) {
      return "";
    }

    return trimToEmpty(anschrift.getPlz());
  }

  public String getKandidaturOrt(Kandidatur kandidatur) {
    Anschrift anschrift = getKandidaturAnschrift(kandidatur);

    if (anschrift == null) {
      return "";
    }

    return trimToEmpty(anschrift.getOrt());
  }

  public String getKandidaturOrtsteil(Kandidatur kandidatur) {
    Anschrift anschrift = getKandidaturAnschrift(kandidatur);

    if (anschrift == null) {
      return "";
    }

    return trimToEmpty(anschrift.getOrtsteil());
  }

  public String getKandidaturStrasse(Kandidatur kandidatur) {
    Anschrift anschrift = getKandidaturAnschrift(kandidatur);

    if (anschrift == null) {
      return "";
    }

    return trimToEmpty(anschrift.getStrasse());
  }

  public String getKandidaturHausnummer(Kandidatur kandidatur) {
    Anschrift anschrift = getKandidaturAnschrift(kandidatur);

    if (anschrift == null) {
      return "";
    }

    return trimToEmpty(anschrift.getHausnummer());
  }

  public String getKandidaturHausnummerzusatz(Kandidatur kandidatur) {
    Anschrift anschrift = getKandidaturAnschrift(kandidatur);

    if (anschrift == null) {
      return "";
    }

    return trimToEmpty(anschrift.getHausnummerzusatz());
  }

  @Nullable
  public Anschrift getKandidaturAnschrift(Kandidatur kandidatur) {
    if (kandidatur == null) {
      return null;
    }

    return kandidatur.getAnschrift();
  }

  private String getValuesFormatted(String defaultDelimiterMsgKey, Kandidatur kandidatur, List<String> values) {
    if (!values.isEmpty()) {
      return joinNonBlankValues(getMessageOrDefault(kandidatur, defaultDelimiterMsgKey), values);
    }

    return "";
  }

  protected String getMessageOrDefault(Kandidatur kandidatur, String defaultMsgKey) {
    String result = getMessage(defaultMsgKey);

    Wahl wahl = kandidatur.getWahl();

    if (wahl != null) {
      String wahlartMsgKeyPrefix = wahl.getWahlart().getWahlartNameNormalized();

      result = getMessageOrDefault(wahlartMsgKeyPrefix + "." + defaultMsgKey, result);
    }

    return result;
  }

  protected String getMessageOrDefault(String msgKey, String defaultValue) {
    if (getMessages().isMessageKeyVorhanden(msgKey)) {
      return getMessage(msgKey);
    }

    return defaultValue;
  }

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

  public Messages getMessages() {
    return messages;
  }

  protected static String joinNonBlankValues(String delimiter, String... values) {
    return joinNonBlankValues(delimiter, asList(values));
  }

  protected static String joinNonBlankValues(String delimiter, List<String> values) {
    return trimToEmpty(values.stream().filter(StringUtils::isNotBlank).collect(Collectors.joining(delimiter)));
  }
}
