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

import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang3.StringUtils.deleteWhitespace;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.replace;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

import javax.persistence.Transient;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.entity.EntityMitGebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.entity.EntityMitGruppe;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppeEigenschaften;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.stimmeingabe.control.plausi.StimmeingabePlausi;
import de.ivu.elect.business.stimmeingabe.control.plausi.StimmeingabePlausiResult;
import de.ivu.elect.business.wahl.entity.Wahl;

public abstract class AbstractStimmeingabe implements StimmeingabeBase, EntityMitGruppe<Gruppe>, EntityMitGebiet {

  private static final long serialVersionUID = 1L;

  @Transient
  private transient final List<StimmeingabePlausi> stimmeingabePlausis = new ArrayList<>();

  public AbstractStimmeingabe() {
    super();
  }

  public boolean isErgebniseingangVorlaeufig() {
    return getErgebniseingangsart().isVorlaeufig();
  }

  public boolean isErgebniseingangEndgueltig() {
    return getErgebniseingangsart().isEndgueltig();
  }

  public Ergebniseingangsart getErgebniseingangsart() {
    requireNonNull(getErgebniseingang(), "FAILED to find expected Ergebniseingang");

    return getErgebniseingang().getErgebniseingangsart();
  }

  public Ergebniseingangsherkunft getErgebniseingangsherkunft() {
    requireNonNull(getErgebniseingang(), "FAILED to find expected Ergebniseingang");

    return getErgebniseingang().getErgebniseingangsherkunft();
  }

  public void addStimmeingabePlausi(StimmeingabePlausi stimmeingabePlausi) {
    stimmeingabePlausis.add(stimmeingabePlausi);
  }

  public void clearStimmeingabePlausis() {
    stimmeingabePlausis.clear();
  }

  public List<StimmeingabePlausiResult> plausibilisiere() {
    List<StimmeingabePlausiResult> result = new ArrayList<>();

    for (StimmeingabePlausi plausi : stimmeingabePlausis) {
      result.add(plausi.plausibilisieren((Stimmeingabe) this));
    }

    return result;
  }

  public List<StimmeingabePlausi> getStimmeingabePlausis() {
    return stimmeingabePlausis;
  }

  public boolean hasGruppeImGebiet() {
    return getGruppeImGebiet() != null;
  }

  public boolean isAnzahlEingabeZuKonvertieren() {
    return !isPruefkennzeichenEingabe();
  }

  public boolean hasAnzahlEingabe() {
    return isNotBlank(getAnzahlEingabe());
  }

  public long getAnzahl() {
    if (hasAnzahlGroesserGleichNull()) {
      return tryConvertAnzahlEingabeToLong();
    }

    return 0L;
  }

  public void setAnzahl(Long anzahl) {
    if (anzahl != null) {
      setAnzahlEingabe(Long.toString(anzahl));
    } else {
      throw new NullPointerException("null für stimmeingabe.setAnzahl() nicht erlaubt!");
    }
  }

  /**
   * @deprecated
   * @see AbstractStimmeingabe#hasAnzahlGroesserGleichNull
   *
   * @return Gibt true zurück, falls die Eingabe in eine POSITIVE ZAHL oder NULL konvertiert werden kann!
   */
  @Deprecated
  public boolean hasAnzahl() {
    return hasAnzahlGroesserGleichNull();
  }

  public boolean hasAnzahlGroesserGleichNull() {
    if (hasAnzahlEingabe()) {
      try {
        return tryConvertAnzahlEingabeToLong() >= 0;
      } catch (NumberFormatException ignored) {
        // Do nothing, but return false.
      }
    }

    return false;
  }

  public boolean hasAnzahlGroesserNull() {
    return hasAnzahlGroesserGleichNull() && getAnzahl() > 0L;
  }

  private Long tryConvertAnzahlEingabeToLong() {
    // ELECTMUC-420: Nur Punkte und Leerzeichen ignorieren
    String value = trimToEmpty(getAnzahlEingabe());
    value = deleteWhitespace(value);
    value = replace(value, ".", "");

    return Long.valueOf(value);
  }

  public boolean hasStimmeingabefehler() {
    return !getStimmeingabefehler().isEmpty();
  }

  /**
   * TODO: überdenken, ob diese Methode allgemeingültig ist, oder pro Wahlart angepasst werden muss.
   *
   * @return <code>true</code>, wenn diese Eingabe im Ergebnis landen soll, <code>false</code>, wenn es nur Zwischensummen oder sonstige Eingaben sind
   */
  public boolean isErgebnisrelevant() {
    // erstmal sind alles ergebnisrelevante Eingaben
    return true;
  }

  public Set<Stimmeingabefehler> getStimmeingabefehler() {
    return getStimmeingabefehlersByStimmeingabe();
  }

  public boolean isSystemGruppenStimmeingabe() {
    return getGruppeImGebiet() != null && getGruppeImGebiet().getGruppe().isSystemGruppe();
  }

  public boolean isWahlvorschlagStimmeingabe() {
    return hasPolitischeGruppe() && !hasListenkandidatur();
  }

  public Wahl getWahl() {
    return getGruppeImGebiet().getWahl();
  }

  @Override
  public boolean hasGruppe() {
    return hasGruppeImGebiet() && getGruppeImGebiet().getGruppe() != null;
  }

  @Override
  public Gruppe getGruppe() {
    return getGruppeImGebiet().getGruppe();
  }

  @Override
  public void setGruppe(Gruppe gruppe) {
    getGruppeImGebiet().setGruppe(gruppe);
  }

  public boolean hasPolitischeGruppe() {
    return hasGruppe() && getGruppe().isPolitischeGruppe();
  }

  public PolitischeGruppe getPolitischeGruppe() {
    if (hasPolitischeGruppe()) {
      return getGruppeImGebiet().getGruppe().castTo(PolitischeGruppe.class).getEntity();
    }

    throw new IllegalStateException("Die Stimmeingabe [" + toString() + "] ist keiner politischen Gruppe zugeordnet.");
  }

  public boolean hasListenkandidatur() {
    return getListenkandidatur() != null;
  }

  public boolean hasReferendumOption() {
    return getReferendumOption() != null;
  }

  public boolean isGruppeStimmeingabe() {
    return !isZwischensummeEingabe() && !hasListenkandidatur() && !hasReferendumOption();
  }

  public boolean isPruefkennzeichenEingabe() {
    return Stimmenart.PRUEFKENNZEICHEN.equals(getStimmenart());
  }

  public boolean isZwischensummeEingabe() {
    return false;
  }

  @Override
  public Gebiet getGebiet() {
    if (getGruppeImGebiet() != null) {
      return getGruppeImGebiet().getGebiet();
    }

    if (getErgebniseingang() != null) {
      return getErgebniseingang().getGebiet();
    }

    return null;
  }

  public static Function<Stimmeingabe, SystemGruppe> getStimmeingabeToSystemGruppeFunction(final SystemGruppeEigenschaften systemGruppeEigenschaften) {
    return s -> {
      if (!s.getGruppe().isSystemGruppe()) {
        return null;
      }
      return s.getGruppe().getSystemGruppe(systemGruppeEigenschaften);
    };
  }

  public static Function<Stimmeingabe, GruppeImGebiet> getStimmeingabeToGruppeImGebietFunction(final SystemGruppeEigenschaften systemGruppeEigenschaften) {
    return s -> {
      if (s.getGruppe().isSystemGruppe()) {
        return null;
      }
      return s.getGruppeImGebiet();
    };
  }
}
