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

import static java.util.Collections.emptyList;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;

import java.text.MessageFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.annotation.Nonnull;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiErgebniseingangsart;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiFehlerart;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiKonfiguration;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiStatus;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.jee.faces.Messages;

public abstract class AbstractStimmeingabePlausi implements StimmeingabePlausi {

  protected static final StimmeingabePlausiFiktiveWerteFehlerBeheber KEIN_FIKTIVE_WERTE_FEHLER_BEHEBER = StimmeingabePlausiResult.KEIN_FIKTIVE_WERTE_FEHLER_BEHEBER;

  protected static final Logger LOG = LoggerFactory.getLogger(AbstractStimmeingabePlausi.class);

  private final StimmeingabePlausiKonfiguration stimmeingabePlausiKonfiguration;

  private final Messages messages;

  protected AbstractStimmeingabePlausi(StimmeingabePlausiKonfiguration stimmeingabePlausiKonfiguration, Messages messages) {
    super();

    this.stimmeingabePlausiKonfiguration = stimmeingabePlausiKonfiguration;

    this.messages = messages;
  }

  @Override
  public StimmeingabePlausiResult plausibilisieren(Stimmeingabe stimmeingabe) {
    if (stimmeingabe != null) {
      return calculatePlausiResult(stimmeingabe);
    }

    throw new IllegalArgumentException("Plausibilisieren einer NULL-Stimmeingabe? [plausi=" + getClass().getSimpleName() + ", stimmeingabe=" + stimmeingabe + "]");
  }

  protected abstract boolean isPlausiResultCalculatedAutomatischKorrigierbar();

  protected abstract StimmeingabePlausiResult calculatePlausiResult(@Nonnull Stimmeingabe stimmeingabe);

  protected StimmeingabePlausiErgebniseingangsart getErgebniseingangsart() {
    return stimmeingabePlausiKonfiguration.getErgebniseingangsart();
  }

  protected StimmeingabePlausiTyp getTyp() {
    return stimmeingabePlausiKonfiguration.getTyp();
  }

  protected StimmeingabePlausiFehlerart getFehlerart() {
    return stimmeingabePlausiKonfiguration.getFehlerart();
  }

  protected StimmeingabePlausiStatus getStatus() {
    return stimmeingabePlausiKonfiguration.getStatus();
  }

  protected Wahl getWahl() {
    return stimmeingabePlausiKonfiguration.getWahl();
  }

  protected StimmeingabePlausiKonfiguration getStimmeingabePlausiKonfiguration() {
    return stimmeingabePlausiKonfiguration;
  }

  protected StimmeingabePlausiResult createPlausiResultOhneFiktiveWerteFehlerBeheber(Stimmeingabe stimmeingabe) {
    return createPlausiResult(stimmeingabe, null, null, KEIN_FIKTIVE_WERTE_FEHLER_BEHEBER);
  }

  protected StimmeingabePlausiResult createPlausiResult(Stimmeingabe stimmeingabe, Number anzahlSoll, Number anzahlSollDifferenz,
      StimmeingabePlausiFiktiveWerteFehlerBeheber fiktiveWerteFehlerBeheber) {
    String anzahlSollEingabe = anzahlSoll != null ? anzahlSoll.toString() : null;

    return createPlausiResult(stimmeingabe, anzahlSollEingabe, anzahlSoll, anzahlSollDifferenz, fiktiveWerteFehlerBeheber);
  }

  protected StimmeingabePlausiResult createPlausiResult(Stimmeingabe stimmeingabe, String anzahlSollEingabe, Number anzahlSoll, Number anzahlSollDifferenz,
      StimmeingabePlausiFiktiveWerteFehlerBeheber fiktiveWerteFehlerBeheber) {
    return new StimmeingabePlausiResult(stimmeingabe, getTyp(), getFehlerart(), getStatus(), getStimmeingabefehlerFehlerschluessel(stimmeingabe),
        getStimmeingabefehlerBeschreibung(stimmeingabe, anzahlSoll, anzahlSollDifferenz), anzahlSollEingabe, anzahlSoll, isPlausiResultCalculatedAutomatischKorrigierbar(), fiktiveWerteFehlerBeheber);
  }

  public String getStimmeingabefehlerFehlerschluessel(Stimmeingabe stimmeingabe) {
    return stimmeingabePlausiKonfiguration.getFehlerschluessel();
  }

  protected String getStimmeingabefehlerBeschreibung(Stimmeingabe stimmeingabe, Number anzahlSoll, Number anzahlSollDifferenz) {
    return getStimmeingabefehlerBeschreibung(stimmeingabe, anzahlSoll, anzahlSollDifferenz, null);
  }

  protected String getStimmeingabefehlerBeschreibung(Stimmeingabe stimmeingabe, Number anzahlSoll, Number anzahlSollDifferenz, Number anzahlSollAnteil) {
    String anzahlSollAnteilFormatted = "";

    if (anzahlSollAnteil != null) {
      anzahlSollAnteilFormatted = NumberFormat.getPercentInstance().format(anzahlSollAnteil);
    }

    return getStimmeingabefehlerBeschreibung(stimmeingabe, anzahlSoll, anzahlSollDifferenz, anzahlSollAnteil, anzahlSollAnteilFormatted);
  }

  protected String getStimmeingabefehlerBeschreibung(Stimmeingabe stimmeingabe, Number anzahlSoll, Number anzahlSollDifferenz, Number anzahlSollAnteil, String anzahlSollAnteilFormatted) {
    List<Object> params = new ArrayList<>();
    params.add(getStimmeingabefehlerBeschreibungAnzahl(stimmeingabe));
    params.add(fixNullToEmpty(anzahlSoll));
    params.add(fixNullToEmpty(anzahlSollDifferenz));
    params.add(fixNullToEmpty(anzahlSollAnteil));
    params.add(anzahlSollAnteilFormatted);
    params.addAll(getStimmeingabefehlerBeschreibungParameters(stimmeingabe));

    String format = getStimmeingabefehlerBeschreibungMessageFormat();

    try {
      return MessageFormat.format(format, params.toArray(new Object[params.size()]));
    } catch (Exception ex) {
      LOG.error("FAILED to format message [plausi = " + getClass().getSimpleName() + ", format = " + format + ", params = " + params + "]", ex);
    }

    return format;
  }

  protected String getStimmeingabefehlerBeschreibungMessageFormat() {
    return stimmeingabePlausiKonfiguration.getFehlermeldungFormat();
  }

  protected Object getStimmeingabefehlerBeschreibungAnzahl(Stimmeingabe stimmeingabe) {
    String result = stimmeingabe.getAnzahlEingabe();

    if (!stimmeingabe.isAnzahlEingabeZuKonvertieren()) {
      return result;
    }

    return defaultIfBlank(result, "0"); // ELECT-10547
  }

  protected List<Object> getStimmeingabefehlerBeschreibungParameters(Stimmeingabe stimmeingabe) {
    return emptyList();
  }

  protected String getMessage(String msgKey, Object... params) {
    return getMessages().getMessageForMessageKey(msgKey, params);
  }

  protected Messages getMessages() {
    return messages;
  }

  protected long fixEmptyToZero(Collection<Stimmeingabe> stimmeingaben) {
    if (stimmeingaben != null && !stimmeingaben.isEmpty()) {
      return stimmeingaben.stream().mapToLong(this::fixNullToZero).sum();
    }

    return 0L;
  }

  protected long fixNullToZero(Stimmeingabe stimmeingabe) {
    if (stimmeingabe != null) {
      return fixNullToZero(stimmeingabe.getAnzahl());
    }

    return 0L;
  }

  protected long fixNullToZero(Long value) {
    if (value != null) {
      return value;
    }

    return 0L;
  }

  private Object fixNullToEmpty(Object value) {
    if (value == null) {
      return "";
    }

    return value;
  }
}
