
package de.ivu.elect.presentation.person;

import static java.util.Arrays.asList;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.normalizeSpace;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.apache.commons.lang3.StringUtils.upperCase;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.regex.Pattern;

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

import de.ivu.elect.business.anschrift.entity.Anschrift;
import de.ivu.elect.business.person.entity.EntityMitPersonendaten;
import de.ivu.elect.business.person.entity.Geschlecht;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.util.StringJoiner;

@Named(PersonendatenFormatter.COMPONENT_NAME)
public class PersonendatenFormatter implements Serializable {

  private static final long serialVersionUID = 1L;

  public static final String COMPONENT_NAME = "personendatenFormatter";

  private static final List<String> ANREDEN_MAENNLICH_UPPER = asList("HERR");

  private static final List<String> ANREDEN_WEIBLICH_UPPER = asList("FRAU");

  private static final Map<Locale, Map<Geschlecht, String>> ANREDE_PER_GESCHLECHT = new ConcurrentHashMap<>();

  private static final Map<Locale, Map<Geschlecht, String>> ANREDEFORMEL_PER_GESCHLECHT = new ConcurrentHashMap<>();

  private static final Map<Locale, Map<Geschlecht, String>> ANREDE_ANSCHRIFT_PER_GESCHLECHT = new ConcurrentHashMap<>();

  private static final Map<Locale, Map<Geschlecht, Pattern>> ANREDE_ANSCHRIFT_PATTERN_PER_GESCHLECHT = new ConcurrentHashMap<>();

  private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s+");

  @Inject
  private transient Messages messages;

  public PersonendatenFormatter() {
    super();
  }

  public String getTitelFamilienname(Personendaten personendaten) {
    if (personendaten != null) {
      return bestimmeTitelUndNachname(personendaten.getTitel(), personendaten.getFamilienname());
    }

    return "";
  }

  public String bestimmeAkademischerGradTitelNamensbestandteileUndNachname(List<String> akademischeGrade, @Nullable String titel, @Nullable String familiennameBestandteil,
      @Nullable String familienname) {
    List<String> tokens = new ArrayList<>(akademischeGrade);

    tokens.add(titel);
    tokens.add(familiennameBestandteil);
    tokens.add(familienname);

    return joinNonBlankValues(tokens);
  }

  public String bestimmeTitelUndNachname(@Nullable String titel, @Nullable String familienname) {
    return joinNonBlankValues(titel, familienname);
  }

  public String bestimmeTitelNamensbestandteileUndNachname(@Nullable String titel, @Nullable String familiennameBestandteil, @Nullable String familienname) {
    return joinNonBlankValues(titel, familiennameBestandteil, familienname);
  }

  public String bestimmeTitelVornameNamensbestandteileUndNachname(@Nullable String titel, String rufname, @Nullable String familiennameBestandteil, @Nullable String familienname) {
    return joinNonBlankValues(titel, rufname, familiennameBestandteil, familienname);
  }

  public String getVornameAnzeige(EntityMitPersonendaten entityMitPersonendaten) {
    if (entityMitPersonendaten != null) {
      return getVornameAnzeige(entityMitPersonendaten.getRufname(), entityMitPersonendaten.getVornamen());
    }

    return "";
  }

  public String getVornameAnzeige(String rufname, String vornamen) {
    if (isNotBlank(rufname)) {
      return normalizeSpace(rufname);
    }

    if (isNotBlank(vornamen)) {
      String[] vornamenSplitted = PATTERN_WHITESPACE.split(normalizeSpace(vornamen));

      if (vornamenSplitted.length > 0) {
        return vornamenSplitted[0];
      }
    }

    return "";
  }

  public String getVornamenVollstaendigAnzeige(EntityMitPersonendaten entityMitPersonendaten) {
    if (entityMitPersonendaten != null) {
      return getVornamenVollstaendigAnzeige(entityMitPersonendaten.getRufname(), entityMitPersonendaten.getVornamen());
    }

    return "";
  }

  public String getVornamenVollstaendigAnzeige(String rufname, String vornamen) {
    if (isNotBlank(vornamen)) {
      return normalizeSpace(vornamen);
    }

    if (isNotBlank(rufname)) {
      return normalizeSpace(rufname);
    }

    return "";
  }

  public String getVornameUndFamiliennameFuerAnzeige(EntityMitPersonendaten entityMitPersonendaten) {
    if (entityMitPersonendaten != null) {
      return getVornameUndFamiliennameFuerAnzeige(entityMitPersonendaten.getRufname(), entityMitPersonendaten.getFamilienname());
    }

    return "";
  }

  public String getVornameUndFamiliennameFuerAnzeige(String rufname, String familienname) {
    if (isBlank(rufname)) {
      if (isBlank(familienname)) {
        return "";
      }

      return familienname;
    }

    if (isBlank(familienname)) {
      return rufname;
    }

    return rufname + " " + familienname;
  }

  public String getFamiliennameVornameKommasepariert(Personendaten personendaten) {
    return getFamiliennameVornameSepariert(personendaten, ",");
  }

  private String getFamiliennameVornameSepariert(Personendaten personendaten, String separator) {
    String result = "";

    if (personendaten != null) {
      result += trimToEmpty(personendaten.getFamilienname());

      String vorname = trimToEmpty(defaultIfBlank(personendaten.getRufname(), personendaten.getVornamen()));

      if (isNotBlank(vorname)) {
        result += separator + " ";
      }

      result += vorname;
    }

    return result;
  }

  public String getFamiliennameVornameGeburtsjahr(Personendaten personendaten) {
    String result = getFamiliennameVornameKommasepariert(personendaten);

    if (personendaten != null) {
      Long geburtsjahr = personendaten.getGeburtsjahr();

      if (geburtsjahr != null) {
        result += " (" + geburtsjahr.toString() + ")";
      }
    }

    return result;
  }

  public String bestimmeAnrede(Personendaten personendaten) {
    if (personendaten != null) {
      return defaultIfBlank(personendaten.getAnrede(), bestimmeAnrede(personendaten.getGeschlecht()));
    }

    return "";
  }

  public String bestimmeAnrede(Geschlecht geschlecht) {
    Geschlecht value = geschlecht;

    if (value == null) {
      value = Geschlecht.UNBEKANNT;
    }

    return computePerGeschlechtIfAbsent(ANREDE_PER_GESCHLECHT, value, key -> getMessage("anrede." + key.name()));
  }

  public String bestimmeAnredeAnschrift(Personendaten personendaten) {
    if (personendaten == null) {
      return bestimmeAnredeAnschrift(Geschlecht.UNBEKANNT);
    }

    return bestimmeAnredeAnschrift(bestimmeAnrede(personendaten));
  }

  public String bestimmeAnredeAnschrift(Geschlecht geschlecht) {
    Geschlecht value = geschlecht;

    if (value == null) {
      value = Geschlecht.UNBEKANNT;
    }

    if (value.isUnbekannt()) {
      return computePerGeschlechtIfAbsent(ANREDE_ANSCHRIFT_PER_GESCHLECHT, value, key -> getMessage("anrede.anschrift." + key.name()));
    }

    return bestimmeAnredeAnschrift(bestimmeAnrede(geschlecht));
  }

  protected String bestimmeAnredeAnschrift(@Nullable String anrede) {
    String result = trimToEmpty(anrede);

    // NOTE ELECT-9130, ist sicher nicht der Weisheit letzter Schluss ...
    for (Geschlecht value : Geschlecht.values()) {
      String anredeAnschrift = computePerGeschlechtIfAbsent(ANREDE_ANSCHRIFT_PER_GESCHLECHT, value, key -> getMessage("anrede.anschrift." + key.name()));

      if (isNotBlank(anredeAnschrift)) {
        Pattern anredeAnschriftPattern = computePerGeschlechtIfAbsent(ANREDE_ANSCHRIFT_PATTERN_PER_GESCHLECHT, value, key -> Pattern.compile("\\b" + Pattern.quote(getMessage("anrede." + key.name())) + "\\b"));

        result = anredeAnschriftPattern.matcher(result).replaceAll(anredeAnschrift);
      }
    }

    return result;
  }

  public String bestimmeAnredeFormel(Personendaten personendaten) {
    if (personendaten == null) {
      return bestimmeAnredeFormel(Geschlecht.UNBEKANNT);
    }

    Geschlecht geschlecht = anredeAsGeschlecht(personendaten.getAnrede());

    if (Geschlecht.UNBEKANNT.equals(geschlecht) && personendaten.getGeschlecht() != null) {
      geschlecht = personendaten.getGeschlecht();
    }

    return bestimmeAnredeFormel(geschlecht);
  }

  public String bestimmeAnredeFormel(Geschlecht geschlecht) {
    Geschlecht value = geschlecht;

    if (value == null) {
      value = Geschlecht.UNBEKANNT;
    }

    return computePerGeschlechtIfAbsent(ANREDEFORMEL_PER_GESCHLECHT, value, key -> getMessage("anredeformel." + key.name()));
  }

  private Geschlecht anredeAsGeschlecht(String anrede) {
    String anredeUpperTrimmed = trimToEmpty(upperCase(anrede));

    if (ANREDEN_MAENNLICH_UPPER.contains(anredeUpperTrimmed)) {
      return Geschlecht.MAENNLICH;
    }

    if (ANREDEN_WEIBLICH_UPPER.contains(anredeUpperTrimmed)) {
      return Geschlecht.WEIBLICH;
    }

    return Geschlecht.UNBEKANNT;
  }

  public String getStrasseMitHausnummerUndZusatz(@Nullable Anschrift anschrift) {
    if (anschrift == null) {
      return "";
    }

    return getStrasseMitHausnummerBuchstabeUndZusatz(anschrift.getStrasse(), anschrift.getHausnummer(), null, anschrift.getHausnummerzusatz());
  }

  public String getStrasseMitHausnummerBuchstabeUndZusatz(@Nullable String strasse, @Nullable String hausnummer, @Nullable String buchstabe, @Nullable String hausnummerzusatz) {
    StringBuilder stringBuilder = new StringBuilder();

    if (isNotBlank(strasse)) {
      stringBuilder.append(strasse);
    }

    if (isNotBlank(hausnummer)) {
      if (stringBuilder.length() > 0) {
        stringBuilder.append(" ");
      }

      stringBuilder.append(hausnummer);
    }

    if (isNotBlank(buchstabe)) {
      if (stringBuilder.length() > 0) {
        stringBuilder.append(" ");
      }

      stringBuilder.append(buchstabe);
    }

    if (isNotBlank(hausnummerzusatz)) {
      if (stringBuilder.length() > 0) {
        stringBuilder.append(" ");
      }

      stringBuilder.append(hausnummerzusatz);
    }

    return stringBuilder.toString();
  }

  public String bestimmeKuenstlernameUndOrdensname(Personendaten personendaten) {
    return StringJoiner.joinWithComma(personendaten.getKuenstlername(), personendaten.getOrdensname());
  }

  private static String joinNonBlankValues(String... values) {
    return joinNonBlankValues(asList(values));
  }

  private static String joinNonBlankValues(Iterable<String> values) {
    StringBuilder stringBuilder = new StringBuilder();

    for (String value : values) {
      if (isNotBlank(value)) {
        stringBuilder.append(value);
        stringBuilder.append(" ");
      }
    }

    return normalizeSpace(stringBuilder.toString());
  }

  private <T> T computePerGeschlechtIfAbsent(Map<Locale, Map<Geschlecht, T>> cache, Geschlecht value, Function<Geschlecht, T> mappingFunction) {
    return cache.computeIfAbsent(getLocale(), key -> new ConcurrentHashMap<>()).computeIfAbsent(value, mappingFunction);
  }

  private Locale getLocale() {
    Locale locale = getMessages().getResourceBundle().getLocale();

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

    return Locale.getDefault();
  }

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

  protected Messages getMessages() {
    return messages;
  }
}
