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

import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.ANREDE;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_ANSCHRIFT_ART;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_HAUSNUMMER;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_HAUSNUMMERZUSATZ;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_ORT;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_ORTSTEIL;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_PLZ;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_STAAT;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_STRASSE;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_WOHNUNGSNUMMER;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.ANSCHRIFT_ZUSTELLANWEISUNG;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.FAMILIENNAME;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.FAMILIENNAME_BESTANDTEIL;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.GEBURTSDATUM;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.GEBURTSORT;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.GESCHLECHT;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.KUENSTLERNAME;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.ORDENSNAME;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.ORDNUNGSMERKMAL;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.STAATSANGEHOERIGKEITEN;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.STIMMBEZIRK_SCHLUESSEL;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.TITEL;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.VORNAME;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.VORNAMEN;
import static de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcherAttribute.WAHLRECHTSAUSSCHLUSS;
import static de.ivu.elect.jee.entity.EntityUtil.normalizeDate;
import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNoneBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;

import de.ivu.elect.business.anschrift.entity.Anschrift;
import de.ivu.elect.business.anschrift.entity.AnschriftArt;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.personenregister.entity.PersonenregisterAnschrift;
import de.ivu.elect.business.personenregister.entity.PersonenregisterPersonendaten;
import de.ivu.elect.business.staat.entity.Staat;

public class PersonendatenPersonenregisterPersonendatenMatcher extends AbstractPersonendatenPersonenregisterPersonendatenMatcher implements PersonenregisterPersonendatenMatcher {

  private static final long serialVersionUID = 1L;

  public PersonendatenPersonenregisterPersonendatenMatcher() {
    super();
  }

  @Override // NOTE ELECT-1269, definiert die zu testenden Attribute.
  public boolean isAutomatischUebernehmbar(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    requireNonNull(personendaten, "Personendaten may NOT be null");
    requireNonNull(personenregisterPersonendaten, "Personenregister-Personendaten may NOT be null");

    return isAutomatischUebernehmbarInternal(personendaten, personenregisterPersonendaten);
  }

  protected boolean isAutomatischUebernehmbarInternal(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    return isPersonendatenAutomatischUebernehmbar(personendaten, personenregisterPersonendaten) && isAnschriftdatenAutomatischUebernehmbar(personendaten, personenregisterPersonendaten);
  }

  private boolean isPersonendatenAutomatischUebernehmbar(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    boolean result = true;

    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, ORDNUNGSMERKMAL) || matchOrdnungsmerkmal(personendaten, personenregisterPersonendaten).isUebernehmbar();

    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, ANREDE) || matchAnrede(personendaten, personenregisterPersonendaten).isUebernehmbar();
    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, TITEL) || matchTitel(personendaten, personenregisterPersonendaten).isUebernehmbar();

    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, FAMILIENNAME) || matchFamilienname(personendaten, personenregisterPersonendaten).isUebernehmbar();
    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, FAMILIENNAME_BESTANDTEIL) || matchFamiliennameBestandteil(personendaten, personenregisterPersonendaten).isUebernehmbar();
    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, VORNAME) || matchVorname(personendaten, personenregisterPersonendaten).isUebernehmbar();
    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, VORNAMEN) || matchVornamen(personendaten, personenregisterPersonendaten).isUebernehmbar();

    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, KUENSTLERNAME) || matchKuenstlername(personendaten, personenregisterPersonendaten).isUebernehmbar();
    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, ORDENSNAME) || matchOrdensname(personendaten, personenregisterPersonendaten).isUebernehmbar();

    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, GEBURTSDATUM) || matchGeburtsdatum(personendaten, personenregisterPersonendaten).isUebernehmbar();
    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, GEBURTSORT) || matchGeburtsort(personendaten, personenregisterPersonendaten).isUebernehmbar();
    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, GESCHLECHT) || matchGeschlecht(personendaten, personenregisterPersonendaten).isUebernehmbar();

    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, STAATSANGEHOERIGKEITEN) || matchStaatsangehoerigkeiten(personendaten, personenregisterPersonendaten).isUebernehmbar();

    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, WAHLRECHTSAUSSCHLUSS) || matchWahlrechtsausschluss(personendaten, personenregisterPersonendaten).isUebernehmbar();

    result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, STIMMBEZIRK_SCHLUESSEL) || matchStimmbezirkSchluessel(personendaten, personenregisterPersonendaten).isUebernehmbar();

    return result;
  }

  protected Collection<PersonenregisterPersonendatenMatcherAttribute> collectPersonendatenAttributesToMatchAutomatischUebernehmbar(Personendaten personendaten) {
    return EnumSet.of( //
        ANREDE, //
        TITEL, //

        FAMILIENNAME, //
        VORNAME, //

        GEBURTSDATUM, //
        GESCHLECHT, //

        STAATSANGEHOERIGKEITEN //
    );
  }

  private boolean isAnschriftdatenAutomatischUebernehmbar(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    boolean result = true;

    Anschrift anschrift = personendaten.getAnschrift();
    AnschriftArt anschriftArt = anschrift.getAnschriftArt();

    if (AnschriftArt.UNBEKANNT.equals(anschriftArt)) {
      anschriftArt = AnschriftArt.HAUPTWOHNUNG;
    }

    result &= personenregisterPersonendaten.hasAnschrift(anschriftArt);

    if (result) { // ELECT-11438
      PersonenregisterAnschrift personenregisterAnschrift = personenregisterPersonendaten.getAnschrift(anschriftArt);

      result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_ANSCHRIFT_ART) || matchAnschriftArt(personendaten, anschrift, personenregisterAnschrift).isUebernehmbar();
      result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_ORT) || matchOrt(personendaten, anschrift, personenregisterAnschrift).isUebernehmbar();
      result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_ORTSTEIL) || matchOrtsteil(personendaten, anschrift, personenregisterAnschrift).isUebernehmbar();
      result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_PLZ) || matchPlz(personendaten, anschrift, personenregisterAnschrift).isUebernehmbar();
      result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_STRASSE) || matchStrasse(personendaten, anschrift, personenregisterAnschrift).isUebernehmbar();
      result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_HAUSNUMMER) || matchHausnummer(personendaten, anschrift, personenregisterAnschrift).isUebernehmbar();
      result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_HAUSNUMMERZUSATZ) || matchHausnummerzusatz(personendaten, anschrift, personenregisterAnschrift).isUebernehmbar();
      result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_WOHNUNGSNUMMER) || matchWohnungsnummer(personendaten, anschrift, personenregisterAnschrift).isUebernehmbar();
      result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_ZUSTELLANWEISUNG) || matchZustellanweisung(personendaten, anschrift, personenregisterAnschrift).isUebernehmbar();
      result &= !isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_STAAT) || matchStaat(personendaten, anschrift, personenregisterAnschrift).isUebernehmbar();
    }

    return result;
  }

  protected Collection<PersonenregisterPersonendatenMatcherAttribute> collectAnschriftdatenAttributesToMatchAutomatischUebernehmbar(Personendaten personendaten) {
    return EnumSet.of( //
        ANSCHRIFT_ANSCHRIFT_ART, //
        ANSCHRIFT_ORT, //
        ANSCHRIFT_ORTSTEIL, //
        ANSCHRIFT_PLZ, //
        ANSCHRIFT_STRASSE, //
        ANSCHRIFT_HAUSNUMMER, //
        ANSCHRIFT_HAUSNUMMERZUSATZ, //
        ANSCHRIFT_WOHNUNGSNUMMER, //
        ANSCHRIFT_ZUSTELLANWEISUNG //
    );
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchOrdnungsmerkmal(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    PersonenregisterPersonendatenMatcherResult matcherResult = matchUebernehmbarIfOneMatchesAnother(isAttributeToMatchAutomatischUebernehmbar(personendaten, ORDNUNGSMERKMAL),
        personendaten.getOrdnungsmerkmal(), personenregisterPersonendaten.getOrdnungsmerkmal());

    // NOTE, dass ein Ordnungsmerkmal schon identisch sein sollte, um übernehmbar zu sein.
    return new PersonenregisterPersonendatenMatcherResult(matcherResult.isAttributeToMatchAutomatischUebernehmbar(), matcherResult.getMatchArt(), matcherResult.isValueIdentisch());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchAnrede(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    // NOTE ELECT-1688, dass der Wert IMMER übernehmbar ist, falls einer der Werte NICHT gesetzt ist. Das impliziert,
    // dass BEI DER ÜBERNAHME IM MAPPER ÜBERPRÜFT WERDEN MUSS, ob denn tatsächlich eine Anrede im Personenregister vorhanden ist!
    return matchUebernehmbarIfOneMatchesAnotherOrAnyIsUndefined(isAttributeToMatchAutomatischUebernehmbar(personendaten, ANREDE), personendaten.getAnrede(), personenregisterPersonendaten.getAnrede());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchTitel(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    // NOTE ELECTMUC-71, dass der Wert IMMER übernehmbar ist, falls in der DB kein Wert existiert!
    return matchUebernehmbarIfOneMatchesAnotherOrOneIsUndefined(isAttributeToMatchAutomatischUebernehmbar(personendaten, TITEL), personendaten.getTitel(), personenregisterPersonendaten.getTitel());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchFamiliennameBestandteil(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    return matchUebernehmbarIfOneMatchesAnother(isAttributeToMatchAutomatischUebernehmbar(personendaten, FAMILIENNAME_BESTANDTEIL), personendaten.getFamiliennameBestandteil(),
        personenregisterPersonendaten.getFamiliennameBestandteil());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchFamilienname(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    return matchUebernehmbarIfOneMatchesAnother(isAttributeToMatchAutomatischUebernehmbar(personendaten, FAMILIENNAME), personendaten.getFamilienname(),
        personenregisterPersonendaten.getFamilienname());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchVorname(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    PersonenregisterPersonendatenMatcherAttribute attribute = VORNAME;

    String one = personendaten.getRufname();
    String another = personenregisterPersonendaten.getRufname();

    if (isNotBlank(another)) {
      // Vergleiche Rufname mit Rufname
      return matchRufname(personendaten, personenregisterPersonendaten);
    }

    PersonenregisterPersonendatenMatchArt matchArt;

    // Vergleiche Rufname mit Vornamen
    another = personenregisterPersonendaten.getVornamen();

    if (isBlank(one) && isBlank(another)) {
      matchArt = PersonenregisterPersonendatenMatchArt.IDENTISCH;
    } else {
      matchArt = getMatchArt(one, another);

      // NOTE, dass das Ergebnis bei diesem Vergleich nur 'ähnlich' oder 'verschieden' sein kann!
      if (matchArt.isIdentisch()) {
        matchArt = PersonenregisterPersonendatenMatchArt.AEHNLICH;
      } else if (matchArt.isVerschieden()) {
        String oneNormalized = normalize(one);

        if (oneNormalized.length() > 1) {
          String[] anotherPartsNormalized = normalize(another).split("\\s+");

          if (anotherPartsNormalized.length > 0 && matches(oneNormalized, anotherPartsNormalized[0])) {
            matchArt = PersonenregisterPersonendatenMatchArt.AEHNLICH;
          }
        }
      }
    }

    boolean uebernehmbar = !matchArt.isVerschieden();

    return new PersonenregisterPersonendatenMatcherResult(isAttributeToMatchAutomatischUebernehmbar(personendaten, attribute), matchArt, uebernehmbar);
  }

  protected PersonenregisterPersonendatenMatcherResult matchRufname(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    return matchUebernehmbarIfOneMatchesAnother(isAttributeToMatchAutomatischUebernehmbar(personendaten, VORNAME), personendaten.getRufname(), personenregisterPersonendaten.getRufname());
  }

  protected PersonenregisterPersonendatenMatcherResult matchVornamen(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    return matchUebernehmbarIfOneMatchesAnother(isAttributeToMatchAutomatischUebernehmbar(personendaten, VORNAMEN), personendaten.getVornamen(), personenregisterPersonendaten.getVornamen());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchKuenstlername(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    return matchUebernehmbarIfOneMatchesAnother(isAttributeToMatchAutomatischUebernehmbar(personendaten, KUENSTLERNAME), personendaten.getKuenstlername(),
        personenregisterPersonendaten.getKuenstlername());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchOrdensname(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    return matchUebernehmbarIfOneMatchesAnother(isAttributeToMatchAutomatischUebernehmbar(personendaten, ORDENSNAME), personendaten.getOrdensname(), personenregisterPersonendaten.getOrdensname());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchGeburtsdatum(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    return matchUebernehmbarIfOneMatchesAnother(isAttributeToMatchAutomatischUebernehmbar(personendaten, GEBURTSDATUM), normalizeDate(personendaten.getGeburtsdatum()),
        normalizeDate(personenregisterPersonendaten.getGeburtsdatum()));
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchGeburtsort(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    String one = personendaten.getGeburtsort();
    String another = personenregisterPersonendaten.getGeburtsort();

    PersonenregisterPersonendatenMatcherResult matcherResult = matchUebernehmbarIfOneMatchesAnotherOrOneIsUndefined(isAttributeToMatchAutomatischUebernehmbar(personendaten, GEBURTSORT), one, another);
    boolean uebernehmbar = !matcherResult.isValueVerschieden() || isBlank(one);

    return new PersonenregisterPersonendatenMatcherResult(matcherResult.isAttributeToMatchAutomatischUebernehmbar(), matcherResult.getMatchArt(), uebernehmbar);
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchGeschlecht(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    return matchUebernehmbarIfOneMatchesAnother(isAttributeToMatchAutomatischUebernehmbar(personendaten, GESCHLECHT), personendaten.getGeschlecht(), personenregisterPersonendaten.getGeschlecht());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchStaatsangehoerigkeiten(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    PersonenregisterPersonendatenMatchArt matchArt = PersonenregisterPersonendatenMatchArt.VERSCHIEDEN;

    List<Staat> oneStaatsangehoerigkeiten = personendaten.getStaatsangehoerigkeitenSorted();
    Collection<String> anotherValues = personenregisterPersonendaten.getStaatsangehoerigkeiten();

    if (anotherValues.size() == oneStaatsangehoerigkeiten.size()) {
      Collection<String> oneValuesIso3166Alpha2 = new ArrayList<>();
      Collection<String> oneValuesCustomCodesStaatsangehoerigkeit = new ArrayList<>();

      for (Staat staatsangehoerigkeit : oneStaatsangehoerigkeiten) {
        oneValuesIso3166Alpha2.add(staatsangehoerigkeit.getIso3166Alpha2());
        oneValuesCustomCodesStaatsangehoerigkeit.add(staatsangehoerigkeit.getCustomCodeStaatsangehoerigkeit());
      }

      if (oneValuesIso3166Alpha2.containsAll(anotherValues) || oneValuesCustomCodesStaatsangehoerigkeit.containsAll(anotherValues)) {
        matchArt = PersonenregisterPersonendatenMatchArt.IDENTISCH;
      }
    }

    // NOTE ELECTMUC-733, dass die Werte IMMER übernehmbar sind, falls in der DB kein Wert existiert!
    boolean uebernehmbar = !matchArt.isVerschieden() || !personendaten.hasStaatsangehoerigkeit();

    return new PersonenregisterPersonendatenMatcherResult(isAttributeToMatchAutomatischUebernehmbar(personendaten, STAATSANGEHOERIGKEITEN), matchArt, uebernehmbar);
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchWahlrechtsausschluss(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    return matchUebernehmbarIfOneMatchesAnother(isAttributeToMatchAutomatischUebernehmbar(personendaten, WAHLRECHTSAUSSCHLUSS), personendaten.getWahlrechtsausschluss(),
        personenregisterPersonendaten.getWahlrechtsausschluss());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchStimmbezirkSchluessel(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    PersonenregisterPersonendatenMatcherResult matcherResult = matchUebernehmbarIfOneMatchesAnother(isAttributeToMatchAutomatischUebernehmbar(personendaten, STIMMBEZIRK_SCHLUESSEL),
        personendaten.getStimmbezirkSchluessel(), personenregisterPersonendaten.getStimmbezirkSchluessel());

    // NOTE, dass ein Stimmbezirkschlüssel schon identisch sein sollte, um übernehmbar zu sein.
    return new PersonenregisterPersonendatenMatcherResult(matcherResult.isAttributeToMatchAutomatischUebernehmbar(), matcherResult.getMatchArt(), matcherResult.isValueIdentisch());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchAnschriftArt(Personendaten personendaten, Anschrift anschrift, PersonenregisterAnschrift personenregisterAnschrift) {
    AnschriftArt anschriftArt = anschrift.getAnschriftArt();

    PersonenregisterPersonendatenMatchArt matchArt = getMatchArt(anschriftArt, personenregisterAnschrift.getAnschriftArt());

    // NOTE ELECTMUC-733, dass der Wert IMMER übernehmbar ist, falls der Wert in der DB unbekannt ist!
    boolean uebernehmbar = !matchArt.isVerschieden() || AnschriftArt.UNBEKANNT.equals(anschriftArt);

    return new PersonenregisterPersonendatenMatcherResult(isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_ANSCHRIFT_ART), matchArt, uebernehmbar);
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchOrt(Personendaten personendaten, Anschrift anschrift, PersonenregisterAnschrift personenregisterAnschrift) {
    String one = anschrift.getOrt();
    String another = personenregisterAnschrift.getOrt();

    PersonenregisterPersonendatenMatcherResult matcherResult = matchUebernehmbarIfOneMatchesAnother(isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_ORT), one, another);

    if (!matcherResult.isUebernehmbar() && isNoneBlank(one, another) && (startsWithNormalized(one, another) || startsWithNormalized(another, one))) {
      return new PersonenregisterPersonendatenMatcherResult(matcherResult.isAttributeToMatchAutomatischUebernehmbar(), PersonenregisterPersonendatenMatchArt.AEHNLICH, true);
    }

    return matcherResult;
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchOrtsteil(Personendaten personendaten, Anschrift anschrift, PersonenregisterAnschrift personenregisterAnschrift) {
    // NOTE, dass der Wert IMMER übernehmbar ist, falls einer der Werte NICHT gesetzt ist. Das impliziert, dass BEI DER
    // ÜBERNAHME IM MAPPER ÜBERPRÜFT WERDEN MUSS, ob denn tatsächlich ein Ortsteil im Personenregister vorhanden ist!
    return matchUebernehmbarIfOneMatchesAnotherOrAnyIsUndefined(isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_ORTSTEIL), anschrift.getOrtsteil(),
        personenregisterAnschrift.getOrtsteil());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchPlz(Personendaten personendaten, Anschrift anschrift, PersonenregisterAnschrift personenregisterAnschrift) {
    return matchUebernehmbarIfOneMatchesAnother(isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_PLZ), anschrift.getPlz(), personenregisterAnschrift.getPlz());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchStrasse(Personendaten personendaten, Anschrift anschrift, PersonenregisterAnschrift personenregisterAnschrift) {
    PersonenregisterPersonendatenMatchArt matchArt = PersonenregisterPersonendatenMatchArt.VERSCHIEDEN;

    String one = anschrift.getStrasse();
    String another = personenregisterAnschrift.getStrasse();

    if (matches(one, another)) {
      matchArt = PersonenregisterPersonendatenMatchArt.IDENTISCH;
    } else {
      String oneNormalized = normalize(one);
      String anotherNormalized = normalize(another);

      if (matches(oneNormalized, anotherNormalized) || matches(normalizeStrasse(oneNormalized), normalizeStrasse(anotherNormalized))) {
        matchArt = PersonenregisterPersonendatenMatchArt.AEHNLICH;
      }
    }

    return new PersonenregisterPersonendatenMatcherResult(isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_STRASSE), matchArt, !matchArt.isVerschieden());
  }

  // NOTE ELECT-2092, dass Abkürzungen möglichst automatisch erkannt und ignoriert werden.
  private static String normalizeStrasse(String value) {
    String result = value.replaceAll("[\\p{Punct}]+", "");

    String aliasesRegExFormat = "(?i)(%s)\\b";

    String strasseStandardized = "STRASSE";
    String strasseAliases = "Strasse|Straße|Str";

    result = result.replaceAll(String.format(aliasesRegExFormat, strasseAliases), strasseStandardized);

    String platzStandardized = "PLATZ";
    String platzAliases = "Platz|Pl";

    result = result.replaceAll(String.format(aliasesRegExFormat, platzAliases), platzStandardized);

    String ringStandardized = "RING";
    String ringAliases = "Ring|Rg";

    result = result.replaceAll(String.format(aliasesRegExFormat, ringAliases), ringStandardized);

    return result.replaceAll("\\s*", "");
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchHausnummer(Personendaten personendaten, Anschrift anschrift, PersonenregisterAnschrift personenregisterAnschrift) {
    PersonenregisterPersonendatenMatchArt matchArt = PersonenregisterPersonendatenMatchArt.VERSCHIEDEN;

    String one = anschrift.getHausnummer();
    String another = personenregisterAnschrift.getHausnummer();

    if (matches(one, another)) {
      matchArt = PersonenregisterPersonendatenMatchArt.IDENTISCH;
    } else if (matches(normalizeHausnummer(one), normalizeHausnummer(another))) {
      matchArt = PersonenregisterPersonendatenMatchArt.AEHNLICH;
    }

    return new PersonenregisterPersonendatenMatcherResult(isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_HAUSNUMMER), matchArt, !matchArt.isVerschieden());
  }

  protected static String normalizeHausnummer(String value) {
    String valueNormalized = normalize(value);

    // NOTE ELECT-2088, dass führende Nullen zu ignorieren sind!
    valueNormalized = valueNormalized.replaceFirst("^0+(?!$)", "");

    // NOTE ELECTMUC-733, dass Leerzeichen zwischen Buchstaben und Zahlen zu ignorieren sind!
    valueNormalized = valueNormalized.replaceAll("(\\d)\\s+(\\D)", "$1$2");
    valueNormalized = valueNormalized.replaceAll("(\\D)\\s+(\\d)", "$1$2");

    return valueNormalized;
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchHausnummerzusatz(Personendaten personendaten, Anschrift anschrift, PersonenregisterAnschrift personenregisterAnschrift) {
    // NOTE ELECTMUC-733, dass der Wert IMMER übernehmbar ist, falls in der DB kein Wert existiert!
    return matchUebernehmbarIfOneMatchesAnotherOrOneIsUndefined(isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_HAUSNUMMERZUSATZ), anschrift.getHausnummerzusatz(),
        personenregisterAnschrift.getHausnummerzusatz());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchWohnungsnummer(Personendaten personendaten, Anschrift anschrift, PersonenregisterAnschrift personenregisterAnschrift) {
    // NOTE, dass der Wert IMMER übernehmbar ist, falls einer der Werte NICHT gesetzt ist. Das impliziert, dass BEI DER
    // ÜBERNAHME IM MAPPER ÜBERPRÜFT WERDEN MUSS, ob denn tatsächlich eine Wohnungsnummer im Personenregister vorhanden ist!
    return matchUebernehmbarIfOneMatchesAnotherOrAnyIsUndefined(isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_WOHNUNGSNUMMER), anschrift.getWohnungsnummer(),
        personenregisterAnschrift.getWohnungsnummer());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchZustellanweisung(Personendaten personendaten, Anschrift anschrift, PersonenregisterAnschrift personenregisterAnschrift) {
    // NOTE ELECTMUC-733, dass der Wert IMMER übernehmbar ist, falls in der DB kein Wert existiert!
    return matchUebernehmbarIfOneMatchesAnotherOrOneIsUndefined(isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_ZUSTELLANWEISUNG), anschrift.getZustellanweisung(),
        personenregisterAnschrift.getZustellanweisung());
  }

  @Override
  public PersonenregisterPersonendatenMatcherResult matchStaat(Personendaten personendaten, Anschrift anschrift, PersonenregisterAnschrift personenregisterAnschrift) {
    return matchUebernehmbarIfOneMatchesAnotherOrOneIsUndefined(isAttributeToMatchAutomatischUebernehmbar(personendaten, ANSCHRIFT_STAAT), anschrift.getStaat(), personenregisterAnschrift.getStaat());
  }

  protected boolean isAttributeToMatchAutomatischUebernehmbar(Personendaten personendaten, PersonenregisterPersonendatenMatcherAttribute attribute) {
    return collectPersonendatenAttributesToMatchAutomatischUebernehmbar(personendaten).contains(attribute)
        || collectAnschriftdatenAttributesToMatchAutomatischUebernehmbar(personendaten).contains(attribute);
  }
}
