
package de.ivu.elect.presentation.gruppe;

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

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

import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.enterprise.event.Observes;
import javax.enterprise.event.Reception;
import javax.inject.Inject;

import org.apache.deltaspike.core.api.scope.ConversationGroup;
import org.primefaces.model.menu.MenuModel;

import de.ivu.elect.business.gebietsbaum.boundary.GebietSelectChangeEvent;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Svg;
import de.ivu.elect.business.gruppe.boundary.GruppeExtendedDAO;
import de.ivu.elect.business.gruppe.boundary.GruppeNachErfassungsgebietBarChartBuilder;
import de.ivu.elect.business.gruppe.boundary.GruppeNachErfassungsgebietStimmenSearch;
import de.ivu.elect.business.gruppe.boundary.GruppeNachErfassungsgebietStimmenSearchParameters;
import de.ivu.elect.business.gruppe.boundary.GruppeNachErfassungsgebietSvgBuilder;
import de.ivu.elect.business.gruppe.control.GruppeByPositionComparator;
import de.ivu.elect.business.gruppe.entity.AbstractGruppe;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.GruppeKumulierteStimmen;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietExtendedDAO;
import de.ivu.elect.business.search.boundary.BenutzerSuchparameterExtendedDAO;
import de.ivu.elect.business.search.boundary.BenutzerSuchparameterSearch;
import de.ivu.elect.business.search.entity.BenutzerSuchparameter;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlvorschlag.boundary.WahlvorschlagExtendedDAO;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.jee.faces.FacesContextUtil;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.presentation.ViewHavingSuchparameterPanel;
import de.ivu.elect.presentation.ViewHavingSuchparameterSpeicherbar;
import de.ivu.elect.presentation.charts.BarChart;
import de.ivu.elect.presentation.gruppe.table.GruppeNachErfassungsgebietStimmenColumnBuilder;
import de.ivu.elect.presentation.search.menu.BenutzerSuchparameterOptionenMenu;

public abstract class AbstractGruppeNachErfassungsgebietStimmenSearchView extends AbstractGruppeKumulierteStimmenSearchView<GruppeNachErfassungsgebietStimmenSearchParameters>
    implements
      ViewHavingSuchparameterSpeicherbar {

  private static final long serialVersionUID = 1L;

  @Inject
  protected transient GruppeExtendedDAO gruppeExtendedDAO;

  @Inject
  protected transient GruppeImGebietExtendedDAO gruppeImGebietExtendedDAO;

  @Inject
  protected transient WahlvorschlagExtendedDAO wahlvorschlagExtendedDAO;

  @Inject
  protected transient BenutzerSuchparameterSearch benutzerSuchparameterSearch;

  @Inject
  protected transient BenutzerSuchparameterExtendedDAO benutzerSuchparameterExtendedDAO;

  @Inject
  protected transient BenutzerSuchparameterOptionenMenu benutzerSuchparameterOptionenMenu;

  @Inject
  protected transient GruppeNachErfassungsgebietStimmenSearch gruppeNachErfassungsgebietStimmenSearch;

  @Inject
  @ConversationGroup(GruppeNachErfassungsgebietStimmenSearchParameters.class)
  protected transient GruppeNachErfassungsgebietStimmenSearchParameters gruppeNachErfassungsgebietStimmenSearchParameters;

  @Inject
  protected transient GruppeNachErfassungsgebietStimmenColumnBuilder gruppeNachErfassungsgebietStimmenColumnBuilder;

  @Inject
  protected transient GruppeNachErfassungsgebietSvgBuilder gruppeNachErfassungsgebietSvgBuilder;

  @Inject
  protected transient GruppeNachErfassungsgebietBarChartBuilder gruppeNachErfassungsgebietBarChartBuilder;

  protected BenutzerSuchparameter benutzerSuchparameterInitialisiert;

  protected BarChart barChart;

  protected Svg svg;

  public AbstractGruppeNachErfassungsgebietStimmenSearchView() {
    super();
  }

  @Override
  @PostConstruct
  public void init() {
    resetSearchParametersGruppenBySelectedGebiet(); // WASDE-583

    super.init();
  }

  @Override
  protected void initSearchParametersByPrincipal() {
    super.initSearchParametersByPrincipal();

    if (isSuchparameterStateAlsDefaultSupported() && isWahlSelected()) {
      Wahl wahl = getSelectedWahl();

      findBenutzerSuchparameterAlsDefault(wahl)
          .ifPresent(benutzerSuchparameter -> presetSuchparameterState(benutzerSuchparameter, "benutzer.suchvorlage.auswaehlen.als.default.action.success.message"));
    }
  }

  @Override
  public void onGebietSelectChangeEvent(@Observes(notifyObserver = Reception.IF_EXISTS) GebietSelectChangeEvent event) {
    super.onGebietSelectChangeEvent(event);

    if (getViewId().equals(FacesContextUtil.getCurrentViewId())) {
      resetSearchParametersGruppenBySelectedGebiet(); // WASDE-583

      ajaxHelper.update(ViewHavingSuchparameterPanel.SUCHPARAMETER_FORM_ID);
    }
  }

  private void resetSearchParametersGruppenBySelectedGebiet() {
    resetPolitischeGruppen();

    resetSearchParametersModel(); // Gruppen ändern sich womöglich => Such-Formular zurücksetzen

    getSearchParameters().resetGruppenProperty();

    if (!getSearchParameters().getGruppen().isEmpty()) {
      Collection<Gruppe> gruppenVerfuegbar = new HashSet<>(getPolitischeGruppenMitSystemgruppen());
      List<Gruppe> gruppenRelevant = getSearchParameters().getGruppen().stream().filter(gruppenVerfuegbar::contains).collect(toList());

      getSearchParameters().setGruppen(gruppenRelevant);
    }
  }

  @Override
  protected GruppeNachErfassungsgebietStimmenSearchParameters getSearchParametersInternal() {
    return gruppeNachErfassungsgebietStimmenSearchParameters;
  }

  @Override
  protected GruppeNachErfassungsgebietStimmenSearch getSearch() {
    return gruppeNachErfassungsgebietStimmenSearch;
  }

  @Override
  protected GruppeNachErfassungsgebietStimmenColumnBuilder getColumnBuilder() {
    return gruppeNachErfassungsgebietStimmenColumnBuilder;
  }

  protected void addGebietGruppeProperties(FormBuilder formBuilder) {
    formBuilder.addProperty(getSearchParameters().getGebietsauswahlProperty());
    formBuilder.addProperty(getSearchParameters().getStimmabgabeartProperty());
    formBuilder.addProperty(getSearchParameters().getErgebnisartFuerDifferenzProperty(getErgebnisartenFuerDifferenz()));
    formBuilder.addProperty(getSearchParameters().getGruppenProperty(new ArrayList<>(getPolitischeGruppenMitSystemgruppen())));
  }

  // WASDE-586
  private List<Gruppe> getPolitischeGruppenMitSystemgruppen() {
    Wahl wahl = getSelectedWahl();

    if (wahl == null) {
      return emptyList();
    }

    List<Gruppe> result = new ArrayList<>();

    List<Gruppe> gruppenInWahl = gruppeExtendedDAO.findByWahl(wahl);
    result.add(AbstractGruppe.findeSystemGruppe(gruppenInWahl, SystemGruppe.GUELTIGE, getSystemGruppeEigenschaften()));
    result.add(AbstractGruppe.findeSystemGruppe(gruppenInWahl, SystemGruppe.UNGUELTIGE, getSystemGruppeEigenschaften()));
    result.add(AbstractGruppe.findeSystemGruppe(gruppenInWahl, SystemGruppe.WAHLBERECHTIGTE, getSystemGruppeEigenschaften()));
    result.add(AbstractGruppe.findeSystemGruppe(gruppenInWahl, SystemGruppe.WAEHLER, getSystemGruppeEigenschaften()));

    result.addAll(getPolitischeGruppen());

    return result;
  }

  @Override
  protected List<PolitischeGruppe> findPolitischeGruppen(Gebiet gebiet) {
    Comparator<Gruppe> comparator = new GruppeByPositionComparator(gruppeImGebietExtendedDAO.findByGebiet(gebiet));

    Map<PolitischeGruppe, List<Wahlvorschlag>> wahlvorschlaegePerPolitischeGruppe = wahlvorschlagExtendedDAO.findByWahl(gebiet.getWahl()).stream()
        .collect(Collectors.groupingBy(Wahlvorschlag::getGruppe));

    return super.findPolitischeGruppen(gebiet).stream().filter(politischeGruppe -> {
      List<Wahlvorschlag> wahlvorschlaege = wahlvorschlaegePerPolitischeGruppe.getOrDefault(politischeGruppe, emptyList());

      for (Wahlvorschlag wahlvorschlag : wahlvorschlaege) {
        if (isWahlvorschlagFuerGebietRelevant(gebiet, wahlvorschlag, wahlvorschlaegePerPolitischeGruppe)) {
          return true;
        }
      }

      return false;
    }).sorted(comparator).collect(toList());
  }

  protected boolean isWahlvorschlagFuerGebietRelevant(Gebiet gebiet, Wahlvorschlag wahlvorschlag, Map<PolitischeGruppe, List<Wahlvorschlag>> wahlvorschlaegePerPolitischeGruppe) {
    return wahlvorschlag.getGebiet().isInGebietshierarchieVon(gebiet);
  }

  @Override
  public void resetSearchResult() {
    super.resetSearchResult();

    barChart = null;
    svg = null;
  }

  public boolean getTabelleAnzeigen() {
    return getSearchParameters().getSuchergebnisDarstellung() != null && getSearchParameters().getSuchergebnisDarstellung().isTabelle();
  }

  public boolean getBarChartAnzeigen() {
    return getSearchParameters().getSuchergebnisDarstellung() != null && getSearchParameters().getSuchergebnisDarstellung().isBarChart();
  }

  public boolean getSvgAnzeigen() {
    return getSearchParameters().getSuchergebnisDarstellung() != null && getSearchParameters().getSuchergebnisDarstellung().isSvg();
  }

  public BarChart getBarChart() {
    if (barChart == null) {
      barChart = gruppeNachErfassungsgebietBarChartBuilder.build(getSearchParameters(), getSearchResult(), gruppeNachErfassungsgebietStimmenColumnBuilder);
    }

    return barChart;
  }

  @Override
  protected List<GruppeKumulierteStimmen> callSearch() {
    barChart = null;
    svg = null;

    gruppeNachErfassungsgebietStimmenColumnBuilder.forceInit();

    return super.callSearch();
  }

  @Override
  public boolean isOptionMenuRendered() {
    return getOptionMenu() != null;
  }

  @Nullable
  @Override
  public MenuModel getOptionMenu() {
    return benutzerSuchparameterOptionenMenu.getMenuModel();
  }

  @Override
  public boolean isSuchparameterStateAlsDefaultSupported() {
    return false; // TODO ELECT-7630
  }

  @Override
  public void initSuchparameterState(BenutzerSuchparameter benutzerSuchparameter) {
    if (benutzerSuchparameter != null) {
      resetSearchResult();
      reset();

      presetSuchparameterState(benutzerSuchparameter, "benutzer.suchvorlage.auswaehlen.action.success.message");
    }
  }

  protected void presetSuchparameterState(BenutzerSuchparameter template, String msgKey) {
    BenutzerSuchparameter benutzerSuchparameter = benutzerSuchparameterExtendedDAO.attach(template);

    if (benutzerSuchparameter != null) {
      benutzerSuchparameterInitialisiert = benutzerSuchparameter;

      setSuchparameterState(benutzerSuchparameterInitialisiert.getSuchparameter());

      addInfoMessage(msgKey, benutzerSuchparameterInitialisiert.getName());
    }
  }

  @Override
  public void setSuchparameterState(String suchparameterState) {
    getSearchParameters().setSearchParameterState(suchparameterState);
  }

  @Override
  public String getSuchparameterState() {
    return getSearchParameters().getSearchParameterState();
  }

  @Override
  protected void setSearchNotReady() {
    // Graphen sofort anzeigen
    if (getSearchParameters().getSuchergebnisDarstellung() != null && getSearchParameters().getSuchergebnisDarstellung().isTabelle()) {
      super.setSearchNotReady();
    }
  }

  public Svg getSvg() {
    if (svg == null) {
      svg = gruppeNachErfassungsgebietSvgBuilder.build(getSearchParameters(), getSearchResult(), gruppeNachErfassungsgebietStimmenColumnBuilder);
    }

    return svg;
  }

  private Optional<BenutzerSuchparameter> findBenutzerSuchparameterAlsDefault(Wahl wahl) {
    return benutzerSuchparameterSearch.findBenutzerSuchparameterAlsDefault(null, wahl, getSelectedBenutzerKennung(), getViewId());
  }
}
