
package de.ivu.elect.business.stimmeingabe.control.plausi;

import java.util.function.Predicate;

import javax.annotation.Nonnull;

import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiKonfiguration;
import de.ivu.elect.jee.faces.Messages;

public abstract class AbstractStimmeingabeAnzahlComparedToPlausi extends AbstractStimmeingabePlausi {

  private static final Double COMPARED_TO_ANTEIL_DEFAULT = 1D;

  private final StimmeingabeAnzahlComparedToOperator comparedToOperator;

  protected AbstractStimmeingabeAnzahlComparedToPlausi(AbstractStimmeingabeAnzahlComparedToPlausiDefinition stimmeingabeAnzahlComparedToPlausiDefinition,
      StimmeingabePlausiKonfiguration stimmeingabePlausiKonfiguration, Messages messages) {
    this(stimmeingabeAnzahlComparedToPlausiDefinition.getComparedToOperator(), stimmeingabePlausiKonfiguration, messages);
  }

  protected AbstractStimmeingabeAnzahlComparedToPlausi(StimmeingabeAnzahlComparedToOperator comparedToOperator, StimmeingabePlausiKonfiguration stimmeingabePlausiKonfiguration, Messages messages) {
    super(stimmeingabePlausiKonfiguration, messages);

    this.comparedToOperator = comparedToOperator;
  }

  @Override
  protected boolean isPlausiResultCalculatedAutomatischKorrigierbar() {
    return StimmeingabeAnzahlComparedToOperator.EQUAL_TO.equals(getComparedToOperator()) && !hasComparedToAnteil();
  }

  @Override
  protected StimmeingabePlausiResult calculatePlausiResult(@Nonnull Stimmeingabe stimmeingabe) {
    return calculatePlausiResult(stimmeingabe, calculateComparedToValue());
  }

  protected StimmeingabePlausiResult calculatePlausiResult(@Nonnull Stimmeingabe stimmeingabe, Double value) {
    if (!isPlausibilisierenZulaessig(stimmeingabe) || getComparedToOperator().matches(calculateStimmeingabeAnzahl(stimmeingabe), value)) {
      return StimmeingabePlausiResult.KEIN_FEHLER;
    }

    return createPlausiResult(stimmeingabe);
  }

  protected boolean isPlausibilisierenZulaessig(Stimmeingabe stimmeingabe) {
    return true; // To be overridden...
  }

  protected Double calculateStimmeingabeAnzahl(Stimmeingabe stimmeingabe) {
    return 1D * getStimmeingabeAnzahl(stimmeingabe);
  }

  protected Long getStimmeingabeAnzahl(Stimmeingabe stimmeingabe) {
    return stimmeingabe.getAnzahl();
  }

  protected Double calculateComparedToValue() {
    return Math.max(0D, getComparedToAnteil() * getComparedToValue());
  }

  protected boolean hasComparedToAnteil() {
    return COMPARED_TO_ANTEIL_DEFAULT.compareTo(getComparedToAnteil()) != 0;
  }

  protected Double getComparedToAnteil() {
    return COMPARED_TO_ANTEIL_DEFAULT;
  }

  protected abstract Long getComparedToValue();

  protected StimmeingabeAnzahlComparedToOperator getComparedToOperator() {
    return comparedToOperator;
  }

  protected StimmeingabePlausiResult createPlausiResult(Stimmeingabe stimmeingabe) {
    Double anzahlSoll = calculateComparedToValue();
    Double anzahlSollDifferenz = anzahlSoll - calculateStimmeingabeAnzahl(stimmeingabe);

    return createPlausiResult(stimmeingabe, anzahlSoll, anzahlSollDifferenz, getFiktiveWerteFehlerBeheber());
  }

  protected StimmeingabePlausiFiktiveWerteFehlerBeheber getFiktiveWerteFehlerBeheber() {
    if (this instanceof StimmeingabePlausiFiktiveWerteFehlerBeheber) {
      return (StimmeingabePlausiFiktiveWerteFehlerBeheber) this;
    }

    return KEIN_FIKTIVE_WERTE_FEHLER_BEHEBER;
  }

  @Override
  public String getStimmeingabefehlerBeschreibung(Stimmeingabe stimmeingabe, Number anzahlSoll, Number anzahlSollDifferenz) {
    return getStimmeingabefehlerBeschreibung(stimmeingabe, anzahlSoll, anzahlSollDifferenz, getComparedToAnteil());
  }

  @Override
  protected Object getStimmeingabefehlerBeschreibungAnzahl(Stimmeingabe stimmeingabe) {
    return getStimmeingabeAnzahl(stimmeingabe);
  }

  public enum StimmeingabeAnzahlComparedToOperator {

    EQUAL_TO {

      @Override
      protected Predicate<Integer> toPredicate() {
        return oneComparedToAnother -> oneComparedToAnother == 0;
      }
    },
    LESS_THAN {

      @Override
      protected Predicate<Integer> toPredicate() {
        return oneComparedToAnother -> oneComparedToAnother < 0;
      }
    },
    LESS_THAN_OR_EQUAL_TO {

      @Override
      protected Predicate<Integer> toPredicate() {
        return LESS_THAN.or(EQUAL_TO);
      }
    },
    LESS_THAN_OR_GREATER_THAN {

      @Override
      protected Predicate<Integer> toPredicate() {
        return LESS_THAN.or(GREATER_THAN);
      }
    },
    GREATER_THAN {

      @Override
      protected Predicate<Integer> toPredicate() {
        return oneComparedToAnother -> oneComparedToAnother > 0;
      }
    },
    GREATER_THAN_OR_EQUAL_TO {

      @Override
      protected Predicate<Integer> toPredicate() {
        return GREATER_THAN.or(EQUAL_TO);
      }
    };

    private StimmeingabeAnzahlComparedToOperator() {
    }

    public boolean matches(Double one, Double another) {
      return toPredicate().test(one.compareTo(another));
    }

    protected abstract Predicate<Integer> toPredicate();

    protected Predicate<Integer> or(StimmeingabeAnzahlComparedToOperator another) {
      return toPredicate().or(another.toPredicate());
    }
  }
}
