
package de.ivu.elect.presentation.wahlvorschlag;

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

import java.io.Serializable;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.boundary.PolitischeGruppeExtendedDAO;
import de.ivu.elect.business.gruppe.control.GruppeByKurznameComparator;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.GruppeArt;
import de.ivu.elect.business.gruppe.entity.GruppePK;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebietUK;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlvorschlag.boundary.WahlvorschlagExtendedDAO;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagUK;
import de.ivu.elect.presentation.wahlvorschlag.form.WahlvorschlagGruppeFinderProperty;
import de.ivu.elect.presentation.wahlvorschlag.form.WahlvorschlagGruppeFinderSelectOneMenuProperty;

@RequestScoped
public class WahlvorschlagGruppeFinder implements Serializable {

  private static final Comparator<Gruppe> DEFAULT_COMPARATOR = GruppeByKurznameComparator.ASCENDING;

  private static final long serialVersionUID = 1L;

  @Inject
  protected transient PolitischeGruppeExtendedDAO politischeGruppeDao;

  @Inject
  protected transient WahlvorschlagExtendedDAO wahlvorschlagExtendedDAO;

  private final Map<WahlvorschlagUK, Boolean> wahlvorschlagVorhandenCache = new HashMap<>();

  private WahlvorschlagGruppeFinderProperty gruppeFinderProperty;

  public WahlvorschlagGruppeFinder() {
    super();
  }

  public WahlvorschlagGruppeFinderProperty getGruppeProperty(Wahlvorschlag wahlvorschlag) {
    if (gruppeFinderProperty == null) {
      gruppeFinderProperty = createWahlvorschlagGruppeFinderProperty(wahlvorschlag);
    }

    return gruppeFinderProperty;
  }

  protected WahlvorschlagGruppeFinderProperty createWahlvorschlagGruppeFinderProperty(Wahlvorschlag wahlvorschlag) {
    return new WahlvorschlagGruppeFinderSelectOneMenuProperty(this, wahlvorschlag);
  }

  public PolitischeGruppe findByPrimaryKey(GruppePK gruppePK) {
    return politischeGruppeDao.findByPrimaryKey(gruppePK);
  }

  public Collection<PolitischeGruppe> findSupportedPolitischeGruppen(Gebiet gebiet, Wahlvorschlag wahlvorschlag) {
    if (gebiet != null && wahlvorschlag != null) {
      if (wahlvorschlag.isDirektkandidat()) {
        return findSupportedPolitischeGruppen(gebiet, (Direktkandidat) wahlvorschlag);
      } else if (wahlvorschlag.isListe()) {
        return findSupportedPolitischeGruppen(gebiet, (Liste) wahlvorschlag);
      }
    }

    throw new IllegalArgumentException(
        "Zum uebergebenen Gebiet und Wahlvorschlag [gebiet = <" + gebiet + ">, wahlvorschlag = <" + wahlvorschlag + ">] koennen keine politische Gruppen bestimmt werden");
  }

  protected Collection<PolitischeGruppe> findSupportedPolitischeGruppen(Gebiet gebiet, Direktkandidat wahlvorschlag) {
    return filterGruppenMitBereitsVorhandenemWahlvorschlag(findPolitischeGruppen(gebiet), gebiet, wahlvorschlag);
  }

  private Collection<PolitischeGruppe> filterGruppenMitBereitsVorhandenemWahlvorschlag(Collection<PolitischeGruppe> politischeGruppen, Gebiet gebiet, Wahlvorschlag wahlvorschlag) {
    return politischeGruppen.stream().filter(p -> !isWahlvorschlagBereitsVorhanden(p, gebiet, wahlvorschlag)).collect(toList());

  }

  private boolean isWahlvorschlagBereitsVorhanden(PolitischeGruppe p, Gebiet gebiet, Wahlvorschlag wahlvorschlag) {
    WahlvorschlagUK wahlvorschlagUK = new WahlvorschlagUK(wahlvorschlag.getDiscriminator(), new GruppeImGebietUK(gebiet.getGebietUK(), p.getPolitischeGruppeUK()));

    return wahlvorschlagVorhandenCache.computeIfAbsent(wahlvorschlagUK, x -> isWahlvorschlagUkVorhanden(wahlvorschlagUK));
  }

  private boolean isWahlvorschlagUkVorhanden(WahlvorschlagUK wahlvorschlagUK) {
    try {
      return wahlvorschlagExtendedDAO.findByUK(wahlvorschlagUK) != null;
    } catch (NonUniqueResultException | NoResultException expected) {
    }

    return false;
  }

  protected Collection<PolitischeGruppe> findSupportedPolitischeGruppen(Gebiet gebiet, Liste wahlvorschlag) {
    return filterGruppenMitBereitsVorhandenemWahlvorschlag(findPolitischeGruppen(gebiet), gebiet, wahlvorschlag);
  }

  private Collection<PolitischeGruppe> findPolitischeGruppen(Gebiet gebiet) {
    return findPolitischeGruppen(gebiet.getWahl(), getRelevantGruppeArten());
  }

  protected Collection<GruppeArt> getRelevantGruppeArten() {
    return GruppeArt.getGruppeArtenMitListen();
  }

  private Collection<PolitischeGruppe> findPolitischeGruppen(Wahl wahl, Collection<GruppeArt> gruppeArten) {
    List<PolitischeGruppe> result = politischeGruppeDao.findByWahlAndGruppeArten(wahl, gruppeArten);

    result.sort(getDefaultPolitischeGruppeComparator());

    return result;
  }

  protected Comparator<Gruppe> getDefaultPolitischeGruppeComparator() {
    return DEFAULT_COMPARATOR;
  }
}
