
package de.ivu.elect.business.wahlrechtspruefung.entity;

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static de.ivu.elect.jee.entity.EntityUtil.normalizeCaseAndDiacritics;
import static org.apache.commons.lang3.RegExUtils.replaceAll;
import static org.apache.commons.lang3.StringUtils.containsAny;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.split;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;

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

import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.kandidatur.entity.NlKandidatur;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.NlListe;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.util.StringJoiner;

/**
 * @see ELECT-9984
 */
public class NlWahlrechtspruefung {

  public static final WahlrechtspruefStatus WAHLRECHTSPRUEFSTATUS_INITIAL_ERFUELLT = WahlrechtspruefStatus.ERFUELLT;

  public static final WahlrechtspruefStatus WAHLRECHTSPRUEFSTATUS_ZU_BEHEBEN = WahlrechtspruefStatus.NICHT_ERFUELLT;

  public static final WahlrechtspruefStatus WAHLRECHTSPRUEFSTATUS_BEHOBEN = WahlrechtspruefStatus.ERFUELLT;

  public static final WahlrechtspruefStatus WAHLRECHTSPRUEFSTATUS_INITIAL = WAHLRECHTSPRUEFSTATUS_INITIAL_ERFUELLT;

  public static final WahlrechtspruefStatus WAHLRECHTSPRUEFSTATUS_IRRELEVANT = WahlrechtspruefStatus.IRRELEVANT;

  private static final int MAX_ANZAHL_NAMEN_IN_KANDIDATENVERSAEUMNISSEN = 3; // ELECT-11131

  private static final Logger LOG = LoggerFactory.getLogger(NlWahlrechtspruefung.class);

  private static final Long PSEUDOFRIST = 0L;

  private static final String TEXT_FORMAT_SEPARATOR = "|";

  private final Wahlrechtspruefung wahlrechtspruefung;

  public static NlWahlrechtspruefung from(Wahlrechtspruefung wahlrechtspruefung) {
    return (NlWahlrechtspruefung) wahlrechtspruefung.getInstanceCache().computeIfAbsent("NlWahlrechtspruefung", x -> new NlWahlrechtspruefung(wahlrechtspruefung));
  }

  private NlWahlrechtspruefung(Wahlrechtspruefung wahlrechtspruefung) {
    super();

    this.wahlrechtspruefung = wahlrechtspruefung;
  }

  public WahlrechtspruefungPK getPk() {
    return wahlrechtspruefung.getPk();
  }

  public Pruefkriterium getPruefkriterium() {
    return wahlrechtspruefung.getPruefkriterium();
  }

  public String formatKurztext() {
    return formatText(getPruefkriterium().getKriteriumtext());
  }

  public String formatLangtext() {
    return defaultIfBlank(wahlrechtspruefung.getBemerkung(), formatText(getDefaultLangtextUnformatiert()));
  }

  public boolean isDefaultLangText() {
    return isBlank(wahlrechtspruefung.getBemerkung());
  }

  public String getDefaultLangtextUnformatiert() {
    return getPruefkriterium().getTextFuerProtokollNichtErfuellt();
  }

  private final Map<String, String> FORMAT_TEXT_CACHE = new HashMap<>();

  public String formatText(String input) {
    String format = trimToEmpty(input);
    if (wahlrechtspruefung.isAttributeChanged()) {
      return formatTextNotNull(format);
    }
    return FORMAT_TEXT_CACHE.computeIfAbsent(format, x -> formatTextNotNull(format));
  }

  @Nullable
  private String formatTextNotNull(String input) {
    String format = input;
    Object[] params = initParamsZumFormatierenVonText();

    Kandidatur kandidatur = wahlrechtspruefung.getKandidatur();

    if (containsAny(format, "{", "}")) {
      Liste liste = getListe();

      if (liste != null) {
        params[0] = liste.getKennwort();

        if (liste.isListe()) {
          params[0] = NlListe.getName(liste);
        }
      }

      if (kandidatur != null) {
        NlKandidatur nlKandidatur = new NlKandidatur(kandidatur);

        String familiennameBestandteil = trimToEmpty(nlKandidatur.getFamiliennameBestandteil());
        String familienname = trimToEmpty(nlKandidatur.getFamilienname());
        String initialen = trimToEmpty(nlKandidatur.getInitialen());

        params[1] = StringJoiner.joinWithComma(StringJoiner.joinWithBlanks(familiennameBestandteil, familienname), initialen);
      }
    }

    String[] formatSplitted = split(format, TEXT_FORMAT_SEPARATOR);

    if (formatSplitted != null && formatSplitted.length > 0) {
      format = trimToEmpty(formatSplitted[0]);

      if (kandidatur != null) {
        switch (kandidatur.getGeschlecht()) {
          case MAENNLICH :
            if (formatSplitted.length > 1) {
              format = trimToEmpty(formatSplitted[1]);
            }

            break;
          case DIVERS :
            if (formatSplitted.length > 2) {
              format = trimToEmpty(formatSplitted[2]);
            }

            break;
          case UNBEKANNT :
            if (formatSplitted.length > 3) {
              format = trimToEmpty(formatSplitted[3]);
            }

            break;

          default :
            break;
        }
      }
    }

    String result = format;

    try {
      result = MessageFormat.format(format, params);
    } catch (Exception ex) {
      LOG.error("Fehler beim Formatieren eines Kurz-/Langtextes [format = {}, params = {}]", format, params, ex);
    }

    return replaceAll(result, "[ \\t]{2,}", " ");
  }

  private Object[] initParamsZumFormatierenVonText() {
    Object[] params = new Object[] { // ELECT-10734
        "", // Name des Wahlvorschlags
        "" // Familienname mit vorangestellten Namensbestandteilen und den Initialen
    };
    return params;
  }

  public boolean isInitialErfuellt() {
    return isInitialErfuellt(wahlrechtspruefung.getWahlrechtspruefStatus(), wahlrechtspruefung.getFristInTagenBisZurWahl());
  }

  public static boolean isInitialErfuellt(WahlrechtspruefStatus wahlrechtspruefStatus, Long fristInTagenBisZurWahl) {
    return WAHLRECHTSPRUEFSTATUS_INITIAL_ERFUELLT.equals(wahlrechtspruefStatus) && fristInTagenBisZurWahl == null;
  }

  public void setInitialErfuellt() {
    wahlrechtspruefung.setWahlrechtspruefStatus(WAHLRECHTSPRUEFSTATUS_INITIAL_ERFUELLT);
    wahlrechtspruefung.setFristInTagenBisZurWahl(null); // ELECT-9984
    wahlrechtspruefung.setBemerkung("");
  }

  public boolean isZuBeheben() {
    return isZuBeheben(wahlrechtspruefung.getWahlrechtspruefStatus(), wahlrechtspruefung.getFristInTagenBisZurWahl());
  }

  public static boolean isZuBeheben(WahlrechtspruefStatus wahlrechtspruefStatus, Long fristInTagenBisZurWahl) {
    return WAHLRECHTSPRUEFSTATUS_ZU_BEHEBEN.equals(wahlrechtspruefStatus);
  }

  public void setZuBeheben(String wahlrechtspruefStatusBemerkung) {
    wahlrechtspruefung.setWahlrechtspruefStatus(WAHLRECHTSPRUEFSTATUS_ZU_BEHEBEN);
    wahlrechtspruefung.setFristInTagenBisZurWahl(getPruefkriterium().getFristInTagenBisZurWahl()); // ELECT-9984

    String bemerkung = wahlrechtspruefStatusBemerkung; // ELECT-10729

    if (isDefaultBemerkung(bemerkung)) {
      bemerkung = "";
    }

    wahlrechtspruefung.setBemerkung(bemerkung);
  }

  public boolean isDefaultBemerkung(String bemerkung) {
    return equalsIgnoreCase(normalizeCaseAndDiacritics(bemerkung), normalizeCaseAndDiacritics(formatText(getPruefkriterium().getTextFuerProtokollNichtErfuellt())));
  }

  public boolean isBearbeitet() { // ELECT-11689
    return isZuBeheben() || isBehoben() || isIrrelevant();
  }

  public boolean isBehoben() {
    return isBehoben(wahlrechtspruefung.getWahlrechtspruefStatus(), wahlrechtspruefung.getFristInTagenBisZurWahl());
  }

  public static boolean isBehoben(WahlrechtspruefStatus wahlrechtspruefStatus, Long fristInTagenBisZurWahl) {
    return WAHLRECHTSPRUEFSTATUS_BEHOBEN.equals(wahlrechtspruefStatus) && fristInTagenBisZurWahl != null;
  }

  public void setBehoben() {
    Long fristInTagenBisZurWahl = getPruefkriterium().getFristInTagenBisZurWahl();

    if (fristInTagenBisZurWahl == null) {
      fristInTagenBisZurWahl = PSEUDOFRIST;
    }

    wahlrechtspruefung.setWahlrechtspruefStatus(WAHLRECHTSPRUEFSTATUS_BEHOBEN);
    wahlrechtspruefung.setFristInTagenBisZurWahl(fristInTagenBisZurWahl); // ELECT-9984
  }

  public boolean isIrrelevant() {
    return WAHLRECHTSPRUEFSTATUS_IRRELEVANT.equals(wahlrechtspruefung.getWahlrechtspruefStatus());
  }

  public void setIrrelevant() {
    wahlrechtspruefung.setWahlrechtspruefStatus(WAHLRECHTSPRUEFSTATUS_IRRELEVANT);
    // NOTE: Frist und Bemerkung müssen bleiben, weil die Informationen beim zurücknehmen der Irrelevanz benötigt werden. Den Übergang auf irrelevant gibt es nur von nicht erfüllt.
    //    wahlrechtspruefung.setFristInTagenBisZurWahl(null);
    //    wahlrechtspruefung.setBemerkung("");
  }

  public boolean isAnzuzeigen() {
    return getKandidatur() != null || !(NlListe.isBlancoListe(getListe()) && wahlrechtspruefung.getPruefkriterium().getKannIrrelevantSein());
  }

  public Gruppe getGruppe() {
    return getListe().getGruppe();
  }

  public Liste getListe() {
    Wahlvorschlag wahlvorschlag = getWahlvorschlag();

    if (wahlvorschlag == null || !wahlvorschlag.isListe()) {
      throw new IllegalStateException("Wahlrechtspruefung mit unerwartetem Wahlvorschlag gefunden [wahlvorschlag = " + wahlvorschlag + "]");
    }

    return castTo(wahlvorschlag, Liste.class);
  }

  private Wahlvorschlag getWahlvorschlag() {
    Kandidatur kandidatur = wahlrechtspruefung.getKandidatur();

    if (kandidatur != null) {
      Wahlvorschlag wahlvorschlag = kandidatur.getWahlvorschlag();

      if (wahlvorschlag != null) {
        return wahlvorschlag;
      }
    }

    return wahlrechtspruefung.getWahlvorschlag();
  }

  public Kandidatur getKandidatur() {
    return wahlrechtspruefung.getKandidatur();
  }

  public String formatDefaultMehrzahlLangtext(List<Listenkandidatur> listenkandidaturen, Messages messages) {
    Object[] params = initParamsZumFormatierenVonText();
    params[1] = combineListenkandidaturen(listenkandidaturen, messages);
    return MessageFormat.format(getDefaultMehrzahlLangtextUnformatiert(), params);
  }

  private String getDefaultMehrzahlLangtextUnformatiert() {
    String mehrzahlLangtextUnformatiert = getPruefkriterium().getTextFuerProtokollFrist();
    if (isBlank(mehrzahlLangtextUnformatiert)) {
      String[] einzahlLangtexte = split(getDefaultLangtextUnformatiert(), TEXT_FORMAT_SEPARATOR);
      if (einzahlLangtexte.length > 0) {
        mehrzahlLangtextUnformatiert = einzahlLangtexte[0]; // ersten Einzahltext verwenden
      }
    }
    return mehrzahlLangtextUnformatiert;
  }

  /**
   * Diese Methode stammt aus OSV und wurde für elect angepasst (ELECT-11131): EmlExportOmissionsHelper::candidatesDescriptions()
   */
  private String combineListenkandidaturen(List<Listenkandidatur> listenkandidaturen, Messages messages) {
    String candidatesNames = "";
    if (listenkandidaturen.size() > 0) {
      candidatesNames += messages.getMessageForMessageKey("versaeumnisse.kandidaturen.nr", listenkandidaturen.size());
      candidatesNames += " " + listenkandidaturen.get(0).getEingereichtePosition();
      if (isListPositionSequence(listenkandidaturen)) {
        candidatesNames += " "
            + formatInitialenFamiliennameBestandteilUndFamilienname(listenkandidaturen.get(0));
        candidatesNames += " "
            + messages.getMessageForMessageKey("versaeumnisse.kandidaturen.bis")
            + " "
            + listenkandidaturen.get(listenkandidaturen.size() - 1).getEingereichtePosition()
            + " "
            + formatInitialenFamiliennameBestandteilUndFamilienname(listenkandidaturen.get(listenkandidaturen.size() - 1));
      } else {
        if (listenkandidaturen.size() <= MAX_ANZAHL_NAMEN_IN_KANDIDATENVERSAEUMNISSEN) {
          candidatesNames += " "
              + formatInitialenFamiliennameBestandteilUndFamilienname(listenkandidaturen.get(0));
        }
        for (int i = 1; i < listenkandidaturen.size(); i++) {
          if (i == listenkandidaturen.size() - 1) {
            candidatesNames += " " + messages.getMessageForMessageKey("versaeumnisse.kandidaturen.und") + " ";
          } else {
            candidatesNames += ", ";
          }
          candidatesNames += listenkandidaturen.get(i).getEingereichtePosition();
          if (listenkandidaturen.size() <= MAX_ANZAHL_NAMEN_IN_KANDIDATENVERSAEUMNISSEN) {
            candidatesNames += " "
                + formatInitialenFamiliennameBestandteilUndFamilienname(listenkandidaturen.get(i));
          }
        }
      }
    }
    return candidatesNames;
  }

  private boolean isListPositionSequence(final List<Listenkandidatur> listenkandidaturen) {
    if (listenkandidaturen.size() > 2) { // must be at least 3 listenkandidaturen
      final long firstPosition = listenkandidaturen.get(0).getEingereichtePosition();
      for (int i = 0; i < listenkandidaturen.size(); i++) {
        if (listenkandidaturen.get(i).getEingereichtePosition() != i + firstPosition) {
          return false;
        }
      }
      return true;
    }
    return false;
  }

  private String formatInitialenFamiliennameBestandteilUndFamilienname(Listenkandidatur listenkandidatur) {
    NlKandidatur nlKandidatur = new NlKandidatur(listenkandidatur);
    String initialen = trimToEmpty(nlKandidatur.getInitialen());
    String familiennameBestandteil = trimToEmpty(nlKandidatur.getFamiliennameBestandteil());
    String familienname = trimToEmpty(nlKandidatur.getFamilienname());
    return StringJoiner.joinWithBlanks(initialen, familiennameBestandteil, familienname);
  }
}
