
package de.ivu.elect.business.gebietsbaum.boundary;

import static java.util.Arrays.stream;
import static org.apache.commons.lang3.StringUtils.deleteWhitespace;
import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.left;
import static org.apache.commons.lang3.StringUtils.leftPad;
import static org.apache.commons.lang3.StringUtils.length;
import static org.apache.commons.lang3.StringUtils.overlay;
import static org.apache.commons.lang3.StringUtils.rightPad;
import static org.apache.commons.lang3.StringUtils.startsWithIgnoreCase;
import static org.apache.commons.lang3.StringUtils.substring;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;

import java.util.Objects;

import javax.annotation.Nullable;

import de.ivu.elect.business.gebietsbaum.entity.BundeslandKennzahl;

/**
 * Bspw. AGS8 München (gesamt) = 09 1 62 000...
 * <ul>
 * <li>1-2 = Kennung Bundesland</li>
 * <li>3 = Kennung Regierungsbezirk</li>
 * <li>4-5 = Kennung Landkreis oder kreisfreie Stadt</li>
 * <li>6-8 = Kennung Gemeinde (000 bei kreisfreier Stadt)</li>
 * </ul>
 * ... die Stelle 3-5 werden bei München als Nummer der Gemeinde hinterlegt.
 *
 * @see https://www.xrepository.de/details/urn:de:bund:destatis:bevoelkerungsstatistik:schluessel:ags
 */
public class WahlverwaltungGemeindeAgs8 {

  public static final String KENNUNG_GEMEINDE_KREISFREIE_STADT = "000";

  private static final String KENNUNG_BUNDESLAND_FALLBACK = "  ";

  private final String nummerRaw;

  private final String nummerAgs8;

  /**
   * @deprecated IMMER das Bundesland mitgeben!!!
   */
  @Deprecated
  public WahlverwaltungGemeindeAgs8(@Nullable String nummer) {
    this(KENNUNG_BUNDESLAND_FALLBACK, nummer);
  }

  public WahlverwaltungGemeindeAgs8(BundeslandKennzahl bundeslandKennzahl, @Nullable String nummer) {
    this(bundeslandKennzahl.getKennzahlFormatted(), nummer);
  }

  public WahlverwaltungGemeindeAgs8(@Nullable String kennungBundesland, @Nullable String nummer) {
    super();

    this.nummerRaw = nummer;

    this.nummerAgs8 = formatNummerAgs8(kennungBundesland, nummer);
  }

  String getNummerRaw() {
    return nummerRaw;
  }

  public boolean hasKennungBundesland() {
    return !startsWithIgnoreCase(getGemeindeschluessel(), KENNUNG_BUNDESLAND_FALLBACK);
  }

  /**
   * @return Die Kennung des Bundeslandes, 2-stellig.
   */
  public String getKennungBundesland() {
    return substring(getGemeindeschluessel(), 0, 2);
  }

  /**
   * @return Die Kennung des Regierungsbezirkes, 1-stellig.
   */
  public String getKennungRegierungsbezirk() {
    return substring(getGemeindeschluessel(), 2, 3);
  }

  /**
   * @return Die Kennung des Landkreises oder der kreisfreien Stadt, 2-stellig.
   */
  public String getKennungKreis() {
    return substring(getGemeindeschluessel(), 3, 5);
  }

  /**
   * @return Die Kennung der Gemeinde, 3-stellig (000 bei kreisfreier Stadt).
   */
  public String getKennungGemeinde() {
    return substring(getGemeindeschluessel(), 5, 8);
  }

  /**
   * @return Der VOLLSTÄNDIGE Kreisschlüssel kodiert im AGS8, 5-stellig (== AGS8 ohne Kennung Gemeinde), also Kennung Bundesland (2) + Kennung Regierungsbezirk (1) + Kennung Landkreis oder kreisfreie
   *         Stadt (2).
   *
   * @see WahlverwaltungGemeindeAgs8#getKreisschluesselRegionalangabe
   */
  public String getKreisschluessel() {
    return substring(getGemeindeschluessel(), 0, 5);
  }

  /**
   * @return Der REGIONALE Kreisschlüssel kodiert im AGS8, 3-stellig (== AGS8 ohne Kennungen Bundesland und Gemeinde), also Kennung Regierungsbezirk (1) + Kennung Landkreis oder kreisfreie Stadt (2).
   *
   * @see WahlverwaltungGemeindeAgs8#getKreisschluessel
   */
  public String getKreisschluesselRegionalangabe() {
    return substring(getGemeindeschluesselRegionalangabe(), 0, 3);
  }

  /**
   * @return Der VOLLSTÄNDIGE Gemeindeschlüssel kodiert im AGS8, 8-stellig, also Kennung Bundesland (2) + Kennung Regierungsbezirk (1) + Kennung Landkreis oder kreisfreie Stadt (2) + Kennung Gemeinde
   *         (3).
   *
   * @see WahlverwaltungGemeindeAgs8#getGemeindeschluesselRegionalangabe
   */
  public String getGemeindeschluessel() {
    return nummerAgs8;
  }

  /**
   * @return Der REGIONALE Gemeindeschlüssel kodiert im AGS8, 6-stellig (== AGS8 ohne Kennung Bundesland), also Kennung Regierungsbezirk (1) + Kennung Landkreis oder kreisfreie Stadt (2) + Kennung
   *         Gemeinde (3).
   *
   * @see WahlverwaltungGemeindeAgs8#getGemeindeschluessel
   */
  public String getGemeindeschluesselRegionalangabe() {
    return substring(getGemeindeschluessel(), 2);
  }

  private static String formatNummerAgs8(@Nullable String kennungBundesland, @Nullable String nummer) {
    String result = "";

    String kennungBundeslandFormatted = formatKennungBundesland(kennungBundesland);

    String nummerNormalized = trimToEmpty(deleteWhitespace(nummer));
    String nummerSystematikCustomer = formatNummerAgs8SystematikCustomer(kennungBundeslandFormatted, nummerNormalized);

    if (isNotBlank(nummerSystematikCustomer) && !Objects.equals(nummerSystematikCustomer, nummerNormalized)) {
      result = nummerSystematikCustomer;
    } else {
      int length = length(nummerNormalized);

      switch (length) {
        case 8 : // Kennung Bundesland (2) + Kennung Regierungsbezirk (1) + Kennung Landkreis oder kreisfreie Stadt (2) + Kennung Gemeinde (3)
          result = nummerNormalized;
          break;
        case 7 : // Kennung Bundesland (1) + Kennung Regierungsbezirk (1) + Kennung Landkreis oder kreisfreie Stadt (2) + Kennung Gemeinde (3)
          result = formatKennungBundesland(left(nummerNormalized, 1)) + substring(nummerNormalized, 1);
          break;
        case 5 : // Kennung Bundesland (2) + Kennung Regierungsbezirk (1) + Kennung kreisfreie Stadt (2)
          result = nummerNormalized + KENNUNG_GEMEINDE_KREISFREIE_STADT;
          break;
        case 4 : // Kennung Bundesland (1) + Kennung Regierungsbezirk (1) + Kennung kreisfreie Stadt (2)
          result = formatKennungBundesland(left(nummerNormalized, 1)) + substring(nummerNormalized, 1) + KENNUNG_GEMEINDE_KREISFREIE_STADT;
          break;
        case 6 : // Kennung Regierungsbezirk (1) + Kennung Landkreis oder kreisfreie Stadt (2) + Kennung Gemeinde (3)
          result = kennungBundeslandFormatted + nummerNormalized;
          break;
        case 3 : // Kennung Regierungsbezirk (1) + Kennung kreisfreie Stadt (2)
          result = kennungBundeslandFormatted + nummerNormalized + KENNUNG_GEMEINDE_KREISFREIE_STADT;
          break;
        default :
          break;
      }
    }

    if (startsWithAnyIgnoreCase(result, KENNUNG_BUNDESLAND_FALLBACK, kennungBundeslandFormatted)) { // ELECT-13142, AGS8 MUSS mit dem Bundesland beginnen!!!
      return result;
    }

    return rightPad(kennungBundeslandFormatted, 8, "0"); // Nullen nach rechts auffüllen...
  }

  private static String formatNummerAgs8SystematikCustomer(@Nullable String kennungBundeslandFormatted, String nummerNormalized) {
    if (equalsIgnoreCase(kennungBundeslandFormatted, BundeslandKennzahl.BRANDENBURG.getKennzahlFormatted())) {
      return formatNummerAgs8SystematikBb(nummerNormalized);
    }

    if (equalsIgnoreCase(kennungBundeslandFormatted, BundeslandKennzahl.RHEINLAND_PFALZ.getKennzahlFormatted())) {
      return formatNummerAgs8SystematikRp(nummerNormalized);
    }

    return nummerNormalized;
  }

  private static String formatNummerAgs8SystematikBb(String nummerNormalized) {
    BundeslandKennzahl bundeslandKennzahl = BundeslandKennzahl.BRANDENBURG;

    String kennungBundeslandFormatted = bundeslandKennzahl.getKennzahlFormatted();

    String nummer = nummerNormalized;

    if (!startsWithIgnoreCase(nummer, kennungBundeslandFormatted)) {
      nummer = kennungBundeslandFormatted + nummer;
    }

    int length = length(nummer);
    String kreisschluessel = substring(nummer, 0, 5); // Kennung Bundesland (2) + Kennung Regierungsbezirk (1) + Kennung Landkreis oder kreisfreie Stadt (2)

    if (length == 9 || length == 13) { // Amt || Briefwahlbezirk in Amt
      return rightPad(kreisschluessel, 8, "0");
    }

    if (length == 12) { // Gemeinde
      return kreisschluessel + substring(nummer, -3);
    }

    if (length == 16) { // Urnenwahlbezirk
      return kreisschluessel + substring(nummer, 9, 12);
    }

    return nummerNormalized;
  }

  // ELECT-13035, cf. KomRpWahlverwaltungGemeindeGsrp!!!
  private static String formatNummerAgs8SystematikRp(String nummerNormalized) {
    BundeslandKennzahl bundeslandKennzahl = BundeslandKennzahl.RHEINLAND_PFALZ;

    String kennungBundesland = bundeslandKennzahl.getKennzahl();
    String kennungBundeslandFormatted = bundeslandKennzahl.getKennzahlFormatted();

    String nummer = nummerNormalized;

    if (startsWithAnyIgnoreCase(nummer, kennungBundesland, kennungBundeslandFormatted)) {
      int lengthNormalized = length(nummerNormalized);

      if (lengthNormalized == 9 || lengthNormalized == 10) {// Kennung Bundesland (1|2) + Kennung Regierungsbezirk (1) + Kennung Landkreis oder kreisfreie Stadt (2) + Kennung Verbandsgemeinde (2) + Kennung Gemeinde (3)
        nummer = substring(nummerNormalized, -8);
      }
    }

    if (!startsWithAnyIgnoreCase(nummer, kennungBundesland, kennungBundeslandFormatted)) { // Kennungen Regierungsbezirk momentan 1|2|3...
      int length = length(nummer);

      if (length == 8) { // Kennung Regierungsbezirk (1) + Kennung Landkreis oder kreisfreie Stadt (2) + Kennung Verbandsgemeinde (2) + Kennung Gemeinde (3)
        return kennungBundeslandFormatted + overlay(nummer, "", 3, 5);
      }
    }

    return nummerNormalized;
  }

  private static String formatKennungBundesland(@Nullable String nummer) {
    String nummerNormalized = trimToEmpty(deleteWhitespace(nummer));
    String padCharacter = " ";

    if (isNotBlank(nummerNormalized)) {
      padCharacter = "0";
    }

    return leftPad(nummerNormalized, 2, padCharacter);
  }

  private static boolean startsWithAnyIgnoreCase(String str, String... prefixes) {
    return prefixes != null && stream(prefixes).anyMatch(prefix -> startsWithIgnoreCase(str, prefix));
  }
}
