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

import static de.ivu.elect.jee.entity.EntityUtil.castTo;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import javax.annotation.Nonnull;

import org.apache.commons.lang3.StringUtils;

import de.ivu.elect.business.gruppe.entity.NlPolitischeGruppe;
import de.ivu.elect.business.person.entity.NlPersonendaten;
import de.ivu.elect.business.person.entity.SichtpruefungStatus;
import de.ivu.elect.business.pruefung.boundary.ZulassungPruefer;
import de.ivu.elect.business.wahlrechtspruefung.boundary.AbstractWahlrechtspruefungDAO;
import de.ivu.elect.business.wahlrechtspruefung.entity.Wahlrechtspruefung;
import de.ivu.elect.business.wahlvorschlag.entity.NlListe;
import de.ivu.elect.business.wahlvorschlag.entity.NlListeStatus;
import de.ivu.elect.util.CaseAndDiacriticIgnoringNormalizer;

public class NlKandidatur extends NlPersonendaten {

  public static final String ZULASSUNG_ABGELEHNT_BEGRUENDUNG_SEPARATOR = NlPolitischeGruppe.ZULASSUNG_ABGELEHNT_BEGRUENDUNG_SEPARATOR;

  private final Kandidatur kandidatur;

  public NlKandidatur(Kandidatur kandidatur) {
    super(kandidatur);

    this.kandidatur = kandidatur;
  }

  public NlPolitischeGruppe getPolitischeGruppe() {
    return new NlPolitischeGruppe(kandidatur.getGruppe());
  }

  public boolean isZugelassen() {
    return kandidatur.isZugelassen();
  }

  public boolean isVerstorben() {
    return kandidatur.isZulassungNachtraeglichWeggefallen();
  }

  // ELECT-11451
  public boolean isPublishGender() {
    if (kandidatur.isInstanceOf(Listenkandidatur.class)) {
      return NlListe.isPublishGender(castTo(kandidatur, Listenkandidatur.class).getListe());
    }

    return true;
  }

  /**
   * ELECT-10814
   */
  public boolean isAndereStaatsbuergerschaft() {
    if (kandidatur.getBewertung() == null) {
      return false;
    }
    return (kandidatur.getBewertung() & 1) == 1;
  }

  /**
   * ELECT-10814
   */
  public void setAndereStaatsbuergerschaft(boolean andereStaatsbuergerschaft) {
    Long bisher = kandidatur.getBewertung();
    if (bisher == null) {
      bisher = 0L;
    }

    if (andereStaatsbuergerschaft) {
      kandidatur.setBewertung(bisher | 1);
    } else {
      if (isAndereStaatsbuergerschaft()) {
        kandidatur.setBewertung(bisher ^ 1);
      }
    }
  }

  /**
   * ELECT-10831
   */
  public boolean isUmzugBeabsichtig() {
    if (kandidatur.getBewertung() == null) {
      return false;
    }
    return (kandidatur.getBewertung() & 2) == 2;
  }

  /**
   * ELECT-10831
   */
  public void setUmzugBeabsichtig(boolean umzugBeabsichtig) {
    Long bisher = kandidatur.getBewertung();
    if (bisher == null) {
      bisher = 0L;
    }

    if (umzugBeabsichtig) {
      kandidatur.setBewertung(bisher | 2);
    } else {
      if (isUmzugBeabsichtig()) {
        kandidatur.setBewertung(bisher ^ 2);
      }
    }
  }

  public KandidaturStatus getKandidaturStatus() {
    return kandidatur.getKandidaturStatus();
  }

  public void setKandidaturStatus(KandidaturStatus kandidaturStatus) {
    kandidatur.setKandidaturStatus(kandidaturStatus);
  }

  public List<String> getZulassungAbgelehntBegruendung() {
    return getPolitischeGruppe().getZulassungAbgelehntBegruendung(kandidatur);
  }

  public void setZulassungAbgelehntBegruendung(String begruendung) {
    getPolitischeGruppe().setZulassungAbgelehntBegruendung(kandidatur, begruendung);
  }

  public List<String> formatPruefungenZuBehebenLangtexteExcludingZulassungPrueferExtraInformation(ZulassungPruefer zulassungPruefer) {
    return getPolitischeGruppe().formatPruefungenZuBehebenLangtexteExcludingZulassungPrueferExtraInformation(zulassungPruefer, kandidatur);
  }

  public List<String> formatPruefungenZuBehebenLangtexteIncludingZulassungPrueferExtraInformation(ZulassungPruefer zulassungPruefer) {
    return getPolitischeGruppe().formatPruefungenZuBehebenLangtexteIncludingZulassungPrueferExtraInformation(zulassungPruefer, kandidatur);
  }

  public List<String> formatPruefungenZuBehebenKurztexteIncludingZulassungPrueferExtraInformation(ZulassungPruefer zulassungPruefer) {
    return getPolitischeGruppe().formatPruefungenZuBehebenKurztexteIncludingZulassungPrueferExtraInformation(zulassungPruefer, kandidatur);
  }

  public List<String> formatPruefungenBehobenLangtexte(ZulassungPruefer zulassungPruefer) {
    return getPolitischeGruppe().formatPruefungenBehobenLangtexte(zulassungPruefer, kandidatur);
  }

  public List<String> formatPruefungenBehobenKurztexte(ZulassungPruefer zulassungPruefer) {
    return getPolitischeGruppe().formatPruefungenBehobenKurztexte(zulassungPruefer, kandidatur);
  }

  public SichtpruefungStatus getSichtpruefungStatus() {
    return kandidatur.getSichtpruefungStatus();
  }

  public void setSichtpruefungStatus(SichtpruefungStatus sichtpruefungStatus) {
    kandidatur.setSichtpruefungStatus(sichtpruefungStatus);
  }

  public Ersatzkandidatur getBevollmaechtigter() {
    return kandidatur.getZugeordneteErsatzkandidatur();
  }

  public void setBevollmaechtigter(Ersatzkandidatur bevollmaechtigter) {
    kandidatur.setZugeordneteErsatzkandidatur(bevollmaechtigter);
  }

  @Override
  public NlKandidaturProperties getProperties() {
    return new NlKandidaturProperties(kandidatur);
  }

  public boolean isDubletteNachElect10799(NlKandidatur other) {
    if (this.kandidatur.getId() != null && other.kandidatur.getId() != null && Objects.equals(this.kandidatur.getId(), other.kandidatur.getId()) || isIdentischVerknuepft(other)) {
      return false;
    }

    if (this.kandidatur.isErsatzkandidatur() || other.kandidatur.isErsatzkandidatur()) {
      return false;
    }

    boolean geburtsdatumIdentisch = isAttributNichtBeidseitigGesetztOderIdentisch(this.getGeburtsdatumErfasst(), other.getGeburtsdatumErfasst())
        || this.getGeburtsdatum() != null && other.getGeburtsdatum() != null && isAttributNichtBeidseitigGesetztOderIdentisch(this.getGeburtsdatum(), other.getGeburtsdatum());

    return geburtsdatumIdentisch
        && isAttributNichtBeidseitigGesetztOderIdentisch(this.getGeschlecht(), other.getGeschlecht())
        && isAttributNichtBeidseitigGesetztOderIdentisch(this.getWohnanschrift().getOrt(), other.getWohnanschrift().getOrt())
        // ELECT-11653 && isAttributNichtBeidseitigGesetztOderIdentisch(this.getErreichbarkeitsanschrift().getOrt(), other.getErreichbarkeitsanschrift().getOrt())
        // ELECT-11653 && isAttributNichtBeidseitigGesetztOderIdentisch(this.getErreichbarkeitsanschrift().getPlz(), other.getErreichbarkeitsanschrift().getPlz())
        // ELECT-11653 && isAttributNichtBeidseitigGesetztOderIdentisch(this.getInitialen(), other.getInitialen())
        && isAttributNichtBeidseitigGesetztOderIdentisch(this.getFamilienname(), other.getFamilienname());
    // ELECT-11653 && isAttributNichtBeidseitigGesetztOderIdentisch(this.getVorname(), other.getVorname())
    // ELECT-11653 && isAttributNichtBeidseitigGesetztOderIdentisch(this.getFamiliennameBestandteil(), other.getFamiliennameBestandteil());
    // ELECT-11653 && isAttributNichtBeidseitigGesetztOderIdentisch(this.getErreichbarkeitsanschrift().getStrasse(), other.getErreichbarkeitsanschrift().getStrasse());

  }

  private boolean isIdentischVerknuepft(NlKandidatur other) {
    // ELECT-11466
    if (StringUtils.isNotBlank(this.kandidatur.getExterneId())) {
      return this.kandidatur.getExterneId().equals(other.getKandidatur().getExterneId()) &&
      /* ELECT-11615 Gruppe muss identisch sein */ this.kandidatur.getGruppe().equals(other.getKandidatur().getGruppe());
    }

    // vor ELECT-11466
    return this.kandidatur.getIdentischVerknuepfteKandidaturen().contains(other.kandidatur);
  }

  protected boolean isAttributNichtBeidseitigGesetztOderIdentisch(String one, String another) {
    return one == null || another == null || Objects.equals(one, another) || normalize(one).equals(normalize(another));
  }

  private boolean isAttributNichtBeidseitigGesetztOderIdentisch(Object one, Object another) {
    return one == null || another == null || Objects.equals(one, another);
  }

  private boolean isAttributNichtBeidseitigGesetztOderIdentisch(Date one, Date another) {
    return one == null || another == null || one.getTime() == another.getTime();
  }

  private static final Map<String, String> NORMALIZE_CACHE = new ConcurrentHashMap<>(10000);

  @Nonnull
  public static String normalize(String value) {
    if (value == null) {
      return "";
    }
    return NORMALIZE_CACHE.computeIfAbsent(value, x -> calculateNormalization(value));
  }

  @Nonnull
  private static String calculateNormalization(String value) {
    return StringUtils.trimToEmpty(StringUtils.normalizeSpace(CaseAndDiacriticIgnoringNormalizer.DEFAULT_INSTANCE.getStringNormalized(value)));
  }

  public Kandidatur getKandidatur() {
    return kandidatur;
  }

  public String getEmlLocalityName() {
    return getWohnanschrift().getOrt();
  }

  /**
   * @see NlListe#getNlListeStatus
   */
  public static NlListeStatus getNlListeStatus(Kandidatur kandidatur, AbstractWahlrechtspruefungDAO wahlrechtspruefungDAO) {
    return getNlListeStatus(kandidatur, wahlrechtspruefungDAO::findByKandidatur);
  }

  /**
   * @see NlListe#getNlListeStatus
   */
  public static NlListeStatus getNlListeStatus(Kandidatur kandidatur, Function<Kandidatur, Collection<Wahlrechtspruefung>> wahlrechtspruefungFct) {
    if (kandidatur.getKandidaturStatus().isZugelassen()) {
      return NlListeStatus.ZUGELASSEN;
    }

    if (kandidatur.getKandidaturStatus().isNichtZugelassenImSinneAbgelehnt()) {
      return NlListeStatus.ABGELEHNT;
    }

    for (Wahlrechtspruefung wahlrechtspruefung : wahlrechtspruefungFct.apply(kandidatur)) {
      if (wahlrechtspruefung.getWahlrechtspruefStatus().isNichtErfuellt()) {
        return NlListeStatus.ABLEHNUNGGRUENDE_VORHANDEN;
      }
    }

    return NlListeStatus.OFFEN;
  }
}
