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

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

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

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

import de.ivu.elect.business.ergebnis.control.StimmenPoolDAOFactory;
import de.ivu.elect.business.gebietsbaum.boundary.GebietDAO;
import de.ivu.elect.business.gebietsbaum.control.GebietByBreadthFirstComparator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppeimgebiet.control.GruppeImGebietReihenfolgeVorsortierer;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.gruppeimgebiet.entity.ReihenfolgeBestaetigenOption;
import de.ivu.elect.business.stimmzettel.boundary.EntityAufStimmzettelDatenDAOFactory;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;

@Stateless
public class GruppeImGebietReihenfolgeBestaetiger implements Serializable {

  private static final Logger LOG = LoggerFactory.getLogger(GruppeImGebietReihenfolgeBestaetiger.class);

  private static final long serialVersionUID = 1L;

  @Inject
  private transient EntityAufStimmzettelDatenDAOFactory entityAufStimmzettelDatenDAOFactory;

  @Inject
  private transient StimmenPoolDAOFactory stimmenPoolDAOFactory;

  @Inject
  private transient GruppeImGebietReihenfolgeVorsortierer gruppeImGebietReihenfolgeVorsortierer;

  @Inject
  private transient GruppeImGebietDAO gruppeImGebietDAO;

  @Inject
  private transient GebietDAO gebietDAO;

  public GruppeImGebietReihenfolgeBestaetiger() {
    super();
  }

  public void bestaetigeVorsortierteReihenfolge(Gebiet gebietNotAttached, ReihenfolgeBestaetigenOption option) {
    if (gebietNotAttached == null) {
      return;
    }
    Gebiet gebiet = gebietDAO.attach(gebietNotAttached);

    LOG.info("Reihenfolge bestaetigen: gebiet=" + gebiet + ", option=" + option);

    Map<Gebiet, List<GruppeImGebiet>> gigNachGebiet = GruppeImGebietEdit.bestimmeGruppeImGebiet(gebiet, option.isUntergeordneteGebiete(), gebietDAO, gruppeImGebietDAO).stream()
        .collect(Collectors.groupingBy(GruppeImGebiet::getGebiet));

    if (!gigNachGebiet.isEmpty()) {
      Wahl wahl = gebiet.getWahl();

      if (wahl != null) {
        EntityAufStimmzettelDaten entityAufStimmzettelDaten = entityAufStimmzettelDatenDAOFactory.getEntityAufStimmzettelDaten();

        List<Wahlvorschlag> wahlvorschlaegeInWahl = new ArrayList<>(entityAufStimmzettelDaten.collectWahlvorschlaegeInWahl(wahl));
        List<Gebiet> gebiete = new ArrayList<>(gigNachGebiet.keySet());

        gebiete.sort(new GebietByBreadthFirstComparator());

        for (Gebiet gebietKey : gebiete) {
          List<GruppeImGebiet> gigsInGebiet = gigNachGebiet.get(gebietKey);

          if (option.isRelativePositionenUntergeordneterGebietUeberschreiben() && gebietKey.isUntergebietVon(gebiet)) {
            gigsInGebiet.forEach(g -> g.setRelativePosition(null));
          }

          gruppeImGebietReihenfolgeVorsortierer.sortiere(wahl, gigsInGebiet, wahlvorschlaegeInWahl, stimmenPoolDAOFactory);

          if (option.isRelativePositionenSetzen()) {
            setzeRelativePositionen(gigsInGebiet);
          }

          if (option.isVorgegebenePositionenSetzen()) {
            fixiereStimmzettelPositionen(gebietKey, gigsInGebiet, wahlvorschlaegeInWahl, entityAufStimmzettelDaten);

            // relative Positionen erneut setzen, könnten nach Setzen der Stimmzettelposition anders sein
            if (option.isRelativePositionenSetzen()) {
              setzeRelativePositionen(gigsInGebiet);
            }
          }
        }
      }
    }
  }

  private void fixiereStimmzettelPositionen(Gebiet gebiet, List<GruppeImGebiet> gigInGebiets, List<Wahlvorschlag> alleWahlvorschlaege, EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    for (GruppeImGebiet gigInGebiet : gigInGebiets) {
      Wahlvorschlag wohlvorschlag = gigInGebiet.getZugehoerigenWahlvorschlag(() -> alleWahlvorschlaege);

      if (wohlvorschlag != null) {
        if (gigInGebiet.getZugehoerigenWahlvorschlagDirektAufGebiet(() -> alleWahlvorschlaege) == null) {
          gigInGebiet.setVorgegebenePosition(null); // wenn kein Wahlvorschlaggebiet, dann vorgebene Position löschen, bestimmt sonst falsche Stimmzettelposition
        }

        Long stimmzettelPosition = wohlvorschlag.getStimmzettelPosition(gebiet, entityAufStimmzettelDaten).getPosition();
        gigInGebiet.setVorgegebenePosition(stimmzettelPosition);
      }
    }
  }

  protected void setzeRelativePositionen(List<GruppeImGebiet> gruppenImGebietSortiertUndAttached) {
    Set<Long> vergebeneRelativePositionen = new HashSet<>();
    long aktuellePosition = 1L;
    for (GruppeImGebiet gruppeImGebiet : gruppenImGebietSortiertUndAttached) {
      long zuSetzendeRelativePosition;
      if (gruppeImGebiet.getVorgegebenePosition() != null) {
        zuSetzendeRelativePosition = Math.max(gruppeImGebiet.getVorgegebenePosition(), aktuellePosition);
      } else {
        zuSetzendeRelativePosition = aktuellePosition;
      }

      // auf jeden Fall verhindern, dass relative Positionen doppelt vergeben
      while (!vergebeneRelativePositionen.add(zuSetzendeRelativePosition)) {
        zuSetzendeRelativePosition++;
      }
      gruppeImGebiet.setRelativePosition(zuSetzendeRelativePosition);
      aktuellePosition = zuSetzendeRelativePosition + 1L; // naechste zu setzende Position muss groesser sein, als die bisherige
    }
  }
}
