
package de.ivu.elect.business.stimmzettel.entity;

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.stream.Collectors.toSet;

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

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.Transient;

import de.ivu.elect.business.ergebnis.control.StimmenPoolFactory;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppeimgebiet.control.GruppeImGebietReihenfolgeVorsortierer;
import de.ivu.elect.business.kandidatur.boundary.AbstractListenkandidaturDAO;
import de.ivu.elect.business.kandidatur.boundary.ListenkandidaturDAO;
import de.ivu.elect.business.kandidatur.control.ListenkandidaturByStimmzettelPositionComparator;
import de.ivu.elect.business.kandidatur.control.StimmzettelFormatter;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.KandidaturStimmzettelPersonendaten;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.kandidatur.entity.ListenkandidaturStimmzettelPosition;
import de.ivu.elect.business.pruefung.boundary.ZulassungPruefer;
import de.ivu.elect.business.stimmzettel.boundary.EntityAufStimmzettelDatenStimmzettelPositionBerechnung;
import de.ivu.elect.business.wahl.entity.AbstractWahlParameterLoader;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlvorschlag.boundary.AbstractWahlvorschlagDAO;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagStimmzettelPosition;

public class EntityAufStimmzettelDaten implements Serializable {

  private static final long serialVersionUID = 1L;

  private final AbstractWahlParameterLoader wahlParameterLoader;

  private final ZulassungPruefer zulassungPruefer;

  private final StimmzettelFormatter stimmzettelFormatter;

  private final EntityAufStimmzettelDatenStimmzettelPositionBerechnung entityAufStimmzettelDatenStimmzettelPositionBerechnung;

  private final GruppeImGebietReihenfolgeVorsortierer gruppeImGebietReihenfolgeVorsortierer;

  @Nullable
  private final AbstractWahlvorschlagDAO wahlvorschlagDAO;

  @Nullable
  private final AbstractListenkandidaturDAO listenkandidaturDAO;

  private final StimmenPoolFactory stimmenPoolFactory;

  @Nullable
  private Collection<Wahlvorschlag> wahlvorschlaegeInWahl;

  @Nullable
  private Map<Gebiet, Collection<Wahlvorschlag>> wahlvorschlaegeInWahlPerGebiet;

  @Nullable
  private Map<Gruppe, Collection<Wahlvorschlag>> wahlvorschlaegeInWahlPerGruppe;

  private transient Map<Object, Object> cache;

  public EntityAufStimmzettelDaten(AbstractWahlParameterLoader wahlParameterLoader, ZulassungPruefer zulassungPruefer, StimmzettelFormatter stimmzettelFormatter,
      EntityAufStimmzettelDatenStimmzettelPositionBerechnung entityAufStimmzettelDatenStimmzettelPositionBerechnung, StimmenPoolFactory stimmenPoolFactory,
      GruppeImGebietReihenfolgeVorsortierer gruppeImGebietReihenfolgeVorsortierer, @Nonnull Collection<Wahlvorschlag> wahlvorschlaegeInWahl) {
    this(wahlParameterLoader, zulassungPruefer, stimmzettelFormatter, entityAufStimmzettelDatenStimmzettelPositionBerechnung, stimmenPoolFactory, gruppeImGebietReihenfolgeVorsortierer,
        (AbstractWahlvorschlagDAO) null, (AbstractListenkandidaturDAO)null);

    this.wahlvorschlaegeInWahl = wahlvorschlaegeInWahl;
  }

  public EntityAufStimmzettelDaten(AbstractWahlParameterLoader wahlParameterLoader, ZulassungPruefer zulassungPruefer, StimmzettelFormatter stimmzettelFormatter,
      EntityAufStimmzettelDatenStimmzettelPositionBerechnung entityAufStimmzettelDatenStimmzettelPositionBerechnung, StimmenPoolFactory stimmenPoolFactory,
      GruppeImGebietReihenfolgeVorsortierer gruppeImGebietReihenfolgeVorsortierer, AbstractWahlvorschlagDAO wahlvorschlagDAO, AbstractListenkandidaturDAO listenkandidaturDAO) {
    super();

    this.wahlParameterLoader = wahlParameterLoader;

    this.zulassungPruefer = zulassungPruefer;

    this.stimmzettelFormatter = stimmzettelFormatter;

    this.entityAufStimmzettelDatenStimmzettelPositionBerechnung = entityAufStimmzettelDatenStimmzettelPositionBerechnung;

    this.stimmenPoolFactory = stimmenPoolFactory;
    this.gruppeImGebietReihenfolgeVorsortierer = gruppeImGebietReihenfolgeVorsortierer;
    this.wahlvorschlagDAO = wahlvorschlagDAO;
    this.listenkandidaturDAO = listenkandidaturDAO;
  }

  public AbstractWahlParameterLoader getWahlParameterLoader() {
    return wahlParameterLoader;
  }

  public ZulassungPruefer getZulassungPruefer() {
    return zulassungPruefer;
  }

  public StimmzettelFormatter getStimmzettelFormatter() {
    return stimmzettelFormatter;
  }

  public GruppeImGebietReihenfolgeVorsortierer getGruppeImGebietReihenfolgeVorsortierer() {
    return gruppeImGebietReihenfolgeVorsortierer;
  }

  public StimmenPoolFactory getStimmenPoolFactory() {
    return stimmenPoolFactory;
  }

  public WahlvorschlagStimmzettelPosition calculateStimmzettelPosition(Gebiet gebiet, @Nullable Wahlvorschlag wahlvorschlag) {
    return entityAufStimmzettelDatenStimmzettelPositionBerechnung.calculateWahlvorschlagStimmzettelPosition(this, gebiet, wahlvorschlag);
  }

  public ListenkandidaturStimmzettelPosition calculateStimmzettelPosition(Gebiet gebiet, @Nullable Listenkandidatur listenkandidatur) {
    return entityAufStimmzettelDatenStimmzettelPositionBerechnung.calculateListenkandidaturStimmzettelPosition(this, gebiet, listenkandidatur);
  }

  public KandidaturStimmzettelPersonendaten formatStimmzettelPersonendaten(Gebiet gebiet, Kandidatur kandidatur) {
    if (kandidatur != null) {
      return new KandidaturStimmzettelPersonendaten(stimmzettelFormatter.getKandidaturNamePraesentation(kandidatur));
    }

    return KandidaturStimmzettelPersonendaten.EMPTY;
  }

  public Collection<Wahlvorschlag> collectWahlvorschlaegeInWahl(Wahl wahl) {
    if (wahlvorschlaegeInWahl == null) {
      if (wahlvorschlagDAO == null) {
        throw new IllegalStateException("wahlvorschlagDAO nicht gesetzt");
      }

      wahlvorschlaegeInWahl = new HashSet<>(wahlvorschlagDAO.findByWahl(wahl));
    }

    return wahlvorschlaegeInWahl;
  }

  public Map<Gebiet, Collection<Wahlvorschlag>> collectWahlvorschlaegeInWahlPerGebiet(Wahl wahl) {
    if (wahlvorschlaegeInWahlPerGebiet == null) {
      wahlvorschlaegeInWahlPerGebiet = new HashMap<>(collectWahlvorschlaegeInWahl(wahl).stream().collect(Collectors.groupingBy(Wahlvorschlag::getGebiet)));
    }

    return wahlvorschlaegeInWahlPerGebiet;
  }

  public Map<Gruppe, Collection<Wahlvorschlag>> collectWahlvorschlaegeInWahlPerGruppe(Wahl wahl) {
    if (wahlvorschlaegeInWahlPerGruppe == null) {
      wahlvorschlaegeInWahlPerGruppe = new HashMap<>(collectWahlvorschlaegeInWahl(wahl).stream().collect(Collectors.groupingBy(Wahlvorschlag::getGruppe)));
    }

    return wahlvorschlaegeInWahlPerGruppe;
  }

  public Set<Liste> collectListenInWahl(Wahl wahl) {
    return collectWahlvorschlaegeInWahl(wahl).stream() //
        .filter(Wahlvorschlag::isListe) //
        .map(wahlvorschlag -> castTo(wahlvorschlag, Liste.class)) //
        .peek(liste-> liste.setListenkandidaturenPreloader(listenkandidaturDAO))// ELECT-11498
        .collect(toSet());
  }

  public Set<Listenkandidatur> collectListenkandidaturenInWahl(Wahl wahl) {
    return collectListenInWahl(wahl).stream() //
        .flatMap(liste -> liste.getListenkandidaturen().stream()) //
        .collect(toSet());
  }

  public List<Listenkandidatur> collectListenkandidaturenAufStimmzettelSortedByDefault(Liste liste) {
    Collection<Listenkandidatur> listenkandidaturen = liste.getListenkandidaturen();
    return entityAufStimmzettelDatenStimmzettelPositionBerechnung.collectListenkandidaturenAufStimmzettel(this, listenkandidaturen, new ListenkandidaturByStimmzettelPositionComparator(this));
  }

  public String getMessage(String msgKey, Object... params) {
    return getStimmzettelFormatter().getMessages().getMessageForMessageKey(msgKey, params);
  }

  public String getMessage(Enum< ? > enumValue) {
    return getStimmzettelFormatter().getMessages().lokalisiere(enumValue);
  }

  public Map<Object, Object> getCache(){
    if (cache == null) {
      cache = new HashMap<>();
    }
    return cache;
  }

  @Nullable
  public AbstractListenkandidaturDAO getListenkandidaturDAO() {
    return listenkandidaturDAO;
  }
}
