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

import static org.apache.commons.lang3.StringUtils.isBlank;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.inject.Inject;

import de.ivu.elect.business.ergebnis.boundary.GewaehltExtendedDAO;
import de.ivu.elect.business.ergebnis.control.AbstractKumulierteStimmenSearch;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
import de.ivu.elect.business.ergebnis.entity.GewaehltStatus;
import de.ivu.elect.business.ergebnis.entity.GewaehltStatusVergabe;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.kandidatur.control.KandidaturGewaehltByGewaehltAndStimmenAnzahlComparator;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.KandidaturGewaehlt;
import de.ivu.elect.business.wahl.entity.Wahl;

public class KandidaturGewaehltSearch extends AbstractKumulierteStimmenSearch<KandidaturGewaehltSearchParameters, KandidaturGewaehlt> {

  private static final Comparator<KandidaturGewaehlt> DEFAULT_COMPARATOR = new KandidaturGewaehltByGewaehltAndStimmenAnzahlComparator();

  @Inject
  private transient KandidaturExtendedDAO kandidaturExtendedDAO;

  @Inject
  private transient GewaehltExtendedDAO gewaehltExtendedDAO;

  public boolean isBerechneStimmen() {
    // siehe auch ELECT-3460
    return true;
  }

  @Override
  protected List<KandidaturGewaehlt> suche(Wahl wahl, Gebiet gebietSelected, Ergebnis ergebnisSelected, KandidaturGewaehltSearchParameters searchParameters) {
    if (gebietSelected == null || ergebnisSelected == null) {
      return new ArrayList<>();
    }

    List<Stimmen> stimmen = new ArrayList<>();
    if (isBerechneStimmen()) {
      stimmen = findAlleStimmen(ergebnisSelected);
    }

    return berechneStimmen(
        filterBySearchParameters(searchParameters,
            getKandidaturenGewaehltInUntergeordneterGebietshierarchie(gebietSelected, findKandidaturen(gebietSelected.getWahl()), stimmen,
                findGewaehlt(ergebnisSelected)))
    );
  }

  private List<KandidaturGewaehlt> berechneStimmen(List<KandidaturGewaehlt> kandidaturGewaehlts) {
    for (KandidaturGewaehlt kandidaturGewaehlt : kandidaturGewaehlts) {
      kandidaturGewaehlt.getStimmenAnzahl(); // Stimmnenberechnung auslösen (Lazy-Loading...)
    }
    return kandidaturGewaehlts;
  }

  // ELECTMUC-731
  private List<KandidaturGewaehlt> filterBySearchParameters(KandidaturGewaehltSearchParameters searchParameters, List<KandidaturGewaehlt> ungefiltert) {
    List<KandidaturGewaehlt> result = new ArrayList<>(ungefiltert.size());

    for (KandidaturGewaehlt kandidaturGewaehlt : ungefiltert) {
      if (isGewaehltStatusPasst(kandidaturGewaehlt, searchParameters.getGewaehltStatus())) {
        if (isGewaehltStatusVergebenPasst(kandidaturGewaehlt, searchParameters.getGewaehltStatusVergabe())) {
          if (isGruppePasst(kandidaturGewaehlt, searchParameters.getGruppe())) {
            if (isFamiliennamePasst(kandidaturGewaehlt, searchParameters.getKandidatur().getFamilienname())) {
              result.add(kandidaturGewaehlt);
            }
          }
        }
      }
    }

    return result;
  }

  private boolean isFamiliennamePasst(KandidaturGewaehlt kandidaturGewaehlt, String familienname) {
    if (isBlank(familienname)) {
      return true;
    }

    if (isBlank(kandidaturGewaehlt.getKandidatur().getFamilienname())) {
      return false;
    }

    return kandidaturGewaehlt.getKandidatur().getFamilienname().toLowerCase().startsWith(familienname.toLowerCase());
  }

  private static boolean isGruppePasst(KandidaturGewaehlt kandidaturGewaehlt, PolitischeGruppe gruppeAnzuzeigen) {
    return gruppeAnzuzeigen == null || gruppeAnzuzeigen.equals(kandidaturGewaehlt.getKandidatur().getGruppe());
  }

  private static boolean isGewaehltStatusPasst(KandidaturGewaehlt kandidaturGewaehlt, Collection<GewaehltStatus> gewaehltStatusAnzuzeigen) {
    return gewaehltStatusAnzuzeigen == null || gewaehltStatusAnzuzeigen.isEmpty() || gewaehltStatusAnzuzeigen.contains(
        kandidaturGewaehlt.getGewaehlt().getGewaehltStatus());
  }

  private static boolean isGewaehltStatusVergebenPasst(KandidaturGewaehlt kandidaturGewaehlt, Collection<GewaehltStatusVergabe> gewaehltStatusAnzuzeigen) {
    return gewaehltStatusAnzuzeigen == null || gewaehltStatusAnzuzeigen.isEmpty() || gewaehltStatusAnzuzeigen.contains(
        kandidaturGewaehlt.getGewaehlt().getGewaehltStatusVergabe());
  }

  protected List<KandidaturGewaehlt> getKandidaturenGewaehltInUntergeordneterGebietshierarchie(Gebiet gebietSelected,
                                                                                               Collection<Kandidatur> kandidaturenByWahl, Collection<Stimmen> stimmenByErgebnis, Collection<Gewaehlt> gewaehltByErgebnis) {
    Map<Kandidatur, Gewaehlt> gewaehltByErgebnisPerKandidatur = new HashMap<>(gewaehltByErgebnis.size());

    for (Gewaehlt gewaehlt : gewaehltByErgebnis) {
      gewaehltByErgebnisPerKandidatur.put(gewaehlt.getKandidatur(), gewaehlt);
    }

    Map<Gruppe, Collection<Stimmen>> stimmenByErgebnisPerGruppe = new HashMap<>(stimmenByErgebnis.size());

    for (Stimmen stimmen : stimmenByErgebnis) {
      Gruppe gruppe = stimmen.getGruppe();

      if (!stimmenByErgebnisPerGruppe.containsKey(gruppe)) {
        stimmenByErgebnisPerGruppe.put(gruppe, new ArrayList<>());
      }

      stimmenByErgebnisPerGruppe.get(gruppe).add(stimmen);
    }

    return getKandidaturenGewaehlt(gebietSelected, kandidaturenByWahl, stimmenByErgebnisPerGruppe, gewaehltByErgebnisPerKandidatur);
  }

  private List<KandidaturGewaehlt> getKandidaturenGewaehlt(Gebiet gebietSelected, Collection<Kandidatur> kandidaturenByWahl,
                                                           Map<Gruppe, Collection<Stimmen>> stimmenByErgebnisPerGruppe, Map<Kandidatur, Gewaehlt> gewaehltByErgebnisPerKandidatur) {
    List<KandidaturGewaehlt> result = new ArrayList<>();

    for (Kandidatur kandidatur : kandidaturenByWahl) {
      Gebiet gebietKandidaturAngetreten = kandidatur.getGebiet();

      if (gebietKandidaturAngetreten.isInGebietshierarchieVon(gebietSelected)) {
        Collection<Stimmen> stimmen = getStimmen(gebietSelected, kandidatur.getGruppe(), stimmenByErgebnisPerGruppe);

        Gewaehlt gewaehlt = gewaehltByErgebnisPerKandidatur.get(kandidatur);

        if (gewaehlt != null) {
          result.add(new KandidaturGewaehlt(getEntityAufStimmzettelDaten(), kandidatur, gewaehlt,
              stimmen)); // elect unabhängig von istGewählt aufnehmen, in der GUI wird angezeigt ob der Kandidat gewählt ist oder nicht
        }
      }
    }

    return result;
  }

  private Collection<Stimmen> getStimmen(Gebiet gebietSelected, Gruppe gruppe, Map<Gruppe, Collection<Stimmen>> stimmenByErgebnisPerGruppe) {
    if (stimmenByErgebnisPerGruppe.containsKey(gruppe)) {
      Collection<Stimmen> stimmenDerGruppe = stimmenByErgebnisPerGruppe.get(gruppe);
      if (gebietSelected.isWurzelgebiet()) {
        return new ArrayList<>(stimmenDerGruppe);
      } else {
        return stimmenDerGruppe.stream()
            .filter(stimmen -> stimmen.getGebiet().isInGebietshierarchieUntergeordnetesGebietVon(gebietSelected))
            .collect(Collectors.toCollection(() -> new ArrayList<>(stimmenDerGruppe.size())));
      }
    }
    return new ArrayList<>();
  }

  private Collection<Kandidatur> findKandidaturen(Wahl wahl) {
    return kandidaturExtendedDAO.findByWahl(wahl)/* ELECTKOM-117 */.stream().filter(k -> k.getWahlvorschlag() != null).collect(Collectors.toList());
  }

  private Collection<Gewaehlt> findGewaehlt(Ergebnis ergebnis) {
    return gewaehltExtendedDAO.findByErgebnis(ergebnis);
  }

  @Override
  protected Comparator<KandidaturGewaehlt> getDefaultComparator() {
    return DEFAULT_COMPARATOR;
  }
}
