
package de.ivu.elect.business.entschaedigung.boundary;

import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;

import java.util.Collection;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

import javax.ejb.Stateless;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;

import de.ivu.elect.business.entschaedigung.control.EntschaedigungAggregator;
import de.ivu.elect.business.entschaedigung.control.PersonendatenEntschaedigungCalculator;
import de.ivu.elect.business.entschaedigung.entity.Entschaedigung;
import de.ivu.elect.business.entschaedigung.entity.Entschaedigungsart;
import de.ivu.elect.business.entschaedigung.entity.EntschaedigungsartKriterium;
import de.ivu.elect.business.entschaedigung.entity.EntschaedigungsartKriteriumEinsatzfunktion;
import de.ivu.elect.business.entschaedigung.entity.EntschaedigungsartKriteriumEinsatzort;
import de.ivu.elect.business.entschaedigung.entity.EntschaedigungsartTemplate;
import de.ivu.elect.business.person.boundary.KontoverbindungFilterKriterium;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.person.entity.PersonendatenArt;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahltermin;

@Stateless
public class EntschaedigungCalculator {

  @Inject
  private transient EntschaedigungDAO entschaedigungDAO;

  @Inject
  private transient Instance<PersonendatenEntschaedigungCalculator> calculators;

  public EntschaedigungCalculator() {
    super();
  }

  public <T extends PersonendatenArt & EntschaedigungsartKriterium> Collection<T> getSupportedPersonendatenArten() {
    Set<T> result = new HashSet<>();

    for (PersonendatenEntschaedigungCalculator calculator : getCalculators()) {
      result.add(calculator.getSupportedPersonendatenArt());
    }

    return result;
  }

  public Collection<EntschaedigungsartKriteriumEinsatzort> getSupportedEinsatzorte() {
    Set<EntschaedigungsartKriteriumEinsatzort> result = new HashSet<>();

    for (PersonendatenEntschaedigungCalculator calculator : getCalculators()) {
      result.addAll(calculator.getSupportedEinsatzorte());
    }

    return result;
  }

  public Collection<EntschaedigungsartKriteriumEinsatzfunktion> getSupportedEinsatzfunktionen() {
    Set<EntschaedigungsartKriteriumEinsatzfunktion> result = new HashSet<>();

    for (PersonendatenEntschaedigungCalculator calculator : getCalculators()) {
      result.addAll(calculator.getSupportedEinsatzfunktionen());
    }

    return result;
  }

  public Optional<Entschaedigungsart> getSupportedEntschaedigungsart(Wahl wahl, Wahltermin wahltermin, PersonendatenArt personendatenArt, EntschaedigungsartTemplate template) {
    return getSupportedEntschaedigungsarten(wahl, wahltermin, personendatenArt).stream() //
        .filter(entschaedigungsart -> entschaedigungsart.usesKriteriumPersonendatenArt(personendatenArt) && entschaedigungsart.usesTemplate(template)) //
        .findFirst();
  }

  public Collection<Entschaedigungsart> getSupportedEntschaedigungsarten(Wahl wahl, Wahltermin wahltermin, PersonendatenArt personendatenArt) {
    return getSupportedEntschaedigungsarten(wahl, wahltermin).stream() //
        .filter(entschaedigungsart -> entschaedigungsart.usesKriteriumPersonendatenArt(personendatenArt)) //
        .collect(toList());
  }

  public Collection<Entschaedigungsart> getSupportedEntschaedigungsarten(Wahl wahl, Wahltermin wahltermin) {
    Set<Entschaedigungsart> result = new HashSet<>();

    for (PersonendatenEntschaedigungCalculator calculator : getCalculators()) {
      result.addAll(calculator.getSupportedEntschaedigungsarten(wahl, wahltermin));
    }

    return result;
  }

  public Optional<Entschaedigung> calculateEntschaedigungVorgesehenGesamtbetragZeroIncluded(PersonendatenArt personendatenArt, Personendaten personendaten, EntschaedigungsartTemplate template) {
    return calculateEntschaedigung(calculateEntschaedigungenVorgesehenGesamtbetragZeroIncluded(personendatenArt, personendaten), template);
  }

  public Collection<Entschaedigung> calculateEntschaedigungenVorgesehenGesamtbetragZeroIncluded(PersonendatenArt personendatenArt, Personendaten personendaten) {
    return calculateEntschaedigungenVorgesehen(personendatenArt, personendaten, EntschaedigungKriteriumGesamtbetrag.ALLE);
  }

  public Optional<Entschaedigung> calculateEntschaedigungVorgesehenGesamtbetragZeroExcluded(PersonendatenArt personendatenArt, Personendaten personendaten, EntschaedigungsartTemplate template) {
    return calculateEntschaedigung(calculateEntschaedigungenVorgesehenGesamtbetragZeroExcluded(personendatenArt, personendaten), template);
  }

  public Collection<Entschaedigung> calculateEntschaedigungenVorgesehenGesamtbetragZeroExcluded(PersonendatenArt personendatenArt, Personendaten personendaten) {
    return calculateEntschaedigungenVorgesehen(personendatenArt, personendaten, EntschaedigungKriteriumGesamtbetrag.NUR_GROESSER_NULL);
  }

  private Collection<Entschaedigung> calculateEntschaedigungenVorgesehen(PersonendatenArt personendatenArt, Personendaten personendaten,
      EntschaedigungKriteriumGesamtbetrag entschaedigungKriteriumGesamtbetrag) {
    return calculateEntschaedigungen(personendatenArt, personendaten, EntschaedigungKriteriumEinsatzStatus.AUSZAHLBAR_ODER_BERECHENBAR, entschaedigungKriteriumGesamtbetrag);
  }

  public Optional<Entschaedigung> calculateEntschaedigungAuszahlbarGesamtbetragZeroIncluded(PersonendatenArt personendatenArt, Personendaten personendaten, EntschaedigungsartTemplate template) {
    return calculateEntschaedigung(calculateEntschaedigungenAuszahlbarGesamtbetragZeroIncluded(personendatenArt, personendaten), template);
  }

  public Collection<Entschaedigung> calculateEntschaedigungenAuszahlbarGesamtbetragZeroIncluded(PersonendatenArt personendatenArt, Personendaten personendaten) {
    return calculateEntschaedigungenAuszahlbar(personendatenArt, personendaten, EntschaedigungKriteriumGesamtbetrag.ALLE);
  }

  public Optional<Entschaedigung> calculateEntschaedigungAuszahlbarGesamtbetragZeroExcluded(PersonendatenArt personendatenArt, Personendaten personendaten, EntschaedigungsartTemplate template) {
    return calculateEntschaedigung(calculateEntschaedigungenAuszahlbarGesamtbetragZeroExcluded(personendatenArt, personendaten), template);
  }

  public Collection<Entschaedigung> calculateEntschaedigungenAuszahlbarGesamtbetragZeroExcluded(PersonendatenArt personendatenArt, Personendaten personendaten) {
    return calculateEntschaedigungenAuszahlbar(personendatenArt, personendaten, EntschaedigungKriteriumGesamtbetrag.NUR_GROESSER_NULL);
  }

  private Collection<Entschaedigung> calculateEntschaedigungenAuszahlbar(PersonendatenArt personendatenArt, Personendaten personendaten,
      EntschaedigungKriteriumGesamtbetrag entschaedigungKriteriumGesamtbetrag) {
    return calculateEntschaedigungen(personendatenArt, personendaten, EntschaedigungKriteriumEinsatzStatus.AUSZAHLBAR, entschaedigungKriteriumGesamtbetrag);
  }

  private Optional<Entschaedigung> calculateEntschaedigung(Collection<Entschaedigung> entschaedigungen, EntschaedigungsartTemplate template) {
    return entschaedigungen.stream().filter(entschaedigung -> entschaedigung.usesEntschaedigungsartTemplate(template)).findFirst();
  }

  private Collection<Entschaedigung> calculateEntschaedigungen(PersonendatenArt personendatenArt, Personendaten personendaten,
      EntschaedigungKriteriumEinsatzStatus entschaedigungKriteriumEinsatzStatus, EntschaedigungKriteriumGesamtbetrag entschaedigungKriteriumGesamtbetrag) {
    if (personendaten != null) {
      Wahl wahl = personendaten.getWahl();
      Wahltermin wahltermin = personendaten.getWahltermin();

      Collection<Entschaedigungsart> entschaedigungsarten = getSupportedEntschaedigungsarten(wahl, wahltermin, personendatenArt);

      EntschaedigungToCalculateParameters entschaedigungToCalculateParameters = new EntschaedigungToCalculateParameters(wahl, wahltermin, entschaedigungsarten);
      entschaedigungToCalculateParameters.setKontoverbindungFilterKriterium(KontoverbindungFilterKriterium.ALLE);
      entschaedigungToCalculateParameters.setEntschaedigungKriteriumEinsatzStatus(entschaedigungKriteriumEinsatzStatus);
      entschaedigungToCalculateParameters.setEntschaedigungKriteriumGesamtbetrag(entschaedigungKriteriumGesamtbetrag);

      return calculateEntschaedigungen(personendaten, entschaedigungToCalculateParameters);
    }

    return emptyList();
  }

  public Collection<Entschaedigung> calculateEntschaedigungen(EntschaedigungToCalculateParameters entschaedigungToCalculateParameters) {
    Set<Entschaedigung> result = new HashSet<>();

    EntschaedigungAggregator entschaedigungenFuerPersonendatenAggregator = EntschaedigungAggregator.entschaedigungenErstelltFuerBeliebigePersonendaten(entschaedigungDAO,
        entschaedigungToCalculateParameters.getWahl(), entschaedigungToCalculateParameters.getWahltermin());

    for (PersonendatenEntschaedigungCalculator calculator : getCalculators()) {
      result.addAll(calculator.calculate(entschaedigungenFuerPersonendatenAggregator, entschaedigungToCalculateParameters));
    }

    return result;
  }

  public Collection<Entschaedigung> calculateEntschaedigungen(Personendaten personendaten, EntschaedigungToCalculateParameters entschaedigungToCalculateParameters) {
    Set<Entschaedigung> result = new HashSet<>();

    EntschaedigungAggregator entschaedigungenFuerPersonendatenAggregator = EntschaedigungAggregator.entschaedigungenErstelltFuerKonkretePersonendaten(entschaedigungDAO, personendaten);

    for (PersonendatenEntschaedigungCalculator calculator : getCalculators()) {
      result.addAll(calculator.calculate(entschaedigungenFuerPersonendatenAggregator, personendaten, entschaedigungToCalculateParameters));
    }

    return result;
  }

  private Instance<PersonendatenEntschaedigungCalculator> getCalculators() {
    return calculators;
  }
}
