
package de.ivu.elect.business.stimmeingabe.control.plausi;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;

import javax.annotation.Nullable;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.stimmeingabe.control.AbstractStimmeingabeErfassenContext;
import de.ivu.elect.business.stimmeingabe.control.DefaultStimmeingabeErfassenLabelGenerator;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeErfassenContext;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeGruppeStimmenErfassenLabelGenerator;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeKopfzahlErfassenLabelGenerator;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeKopfzahlKonfigurationContext;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeListenkandidaturStimmenErfassenLabelGenerator;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeReferendumOptionStimmenErfassenLabelGenerator;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabefehler;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahl.entity.AbstractWahlParameterLoader;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.jee.entity.EntityUtil;
import de.ivu.elect.jee.entity.tk.EntityTK;

public class StimmeingabePlausiBuilderContext implements StimmeingabeKopfzahlKonfigurationContext {

  private final Ergebniseingang ergebniseingang;

  @Nullable
  private final Ergebniseingang gueltigenVorlaeufigerErgebniseingang;

  private final Set<Stimmeingabe> stimmeingabenZuPlausibilisieren;

  private final Supplier<Map<Gruppe, Collection<Wahlvorschlag>>> wahlvorschlaege;

  private Map<EntityTK, Stimmeingabe> stimmeingabenPerTK;

  private Map<EntityTK, Stimmeingabe> vorlaeufigeStimmeingabenPerTK;

  private StimmeingabeErfassenContext stimmeingabeErfassenContext;

  private final AbstractWahlParameterLoader wahlParameterLoader;

  private final EntityAufStimmzettelDaten entityAufStimmzettelDaten;

  private final Map<String, Object> cache = new HashMap<>();

  private final Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer;

  public StimmeingabePlausiBuilderContext(Ergebniseingang ergebniseingang,
                                          @Nullable Ergebniseingang gueltigerVorlaeufigerErgebniseingang,
                                          Supplier<Map<Gruppe, Collection<Wahlvorschlag>>> wahlvorschlaege,
                                          EntityAufStimmzettelDaten entityAufStimmzettelDaten,
                                          AbstractWahlParameterLoader wahlParameterLoader,
                                          Function<Gebiet, List<Wahldurchfuehrungsgebiet>> erfasstFuer) {
    super();

    this.ergebniseingang = ergebniseingang;
    this.entityAufStimmzettelDaten = entityAufStimmzettelDaten;
    this.stimmeingabenZuPlausibilisieren = new HashSet<>(ergebniseingang.getStimmeingaben()); // TODO: Noch benötigt?
    this.gueltigenVorlaeufigerErgebniseingang = gueltigerVorlaeufigerErgebniseingang;
    this.wahlvorschlaege = wahlvorschlaege;
    this.wahlParameterLoader = wahlParameterLoader;
    this.erfasstFuer = erfasstFuer;
  }

  public Ergebniseingang getErgebniseingang() {
    return ergebniseingang;
  }

  public boolean isBriefwahl() {
    return getGebiet().isBriefwahlgebiet();
  }

  public Wahldurchfuehrungsgebiet getGebiet() {
    return getErgebniseingang().getWahldurchfuehrungsgebiet();
  }

  public Ergebniseingangsart getErgebniseingangsart() {
    return getErgebniseingang().getErgebniseingangsart();
  }

  public boolean isErgebniseingangsartVorlaeufig() {
    return getErgebniseingang().isErgebniseingangsartVorlaeufig();
  }

  public boolean isErgebniseingangsartEndgueltig() {
    return getErgebniseingang().isErgebniseingangsartEndgueltig();
  }

  protected Wahl getWahl() {
    return getGebiet().getWahl();
  }

  public Set<Stimmeingabe> getStimmeingabenZuPlausibilisieren() {
    return stimmeingabenZuPlausibilisieren;
  }

  public boolean hasStimmeingabeWithTk(EntityTK tk) {
    return getStimmeingabenPerTk().containsKey(tk);
  }

  public Stimmeingabe getStimmeingabeByTk(EntityTK tk) {
    return getStimmeingabenPerTk().get(tk);
  }

  public Map<EntityTK, Stimmeingabe> getStimmeingabenPerTk() {
    if (stimmeingabenPerTK == null) {
      stimmeingabenPerTK = EntityUtil.mappeNachKonkretestemTk(getErgebniseingang().getStimmeingaben());
    }

    return stimmeingabenPerTK;
  }

  public Stimmeingabe getVorlaeufigeStimmeingabePerTk(EntityTK tk) {
    return getVorlaeufigeStimmeingabenPerTk().get(tk);
  }

  public Map<EntityTK, Stimmeingabe> getVorlaeufigeStimmeingabenPerTk() {
    if (isErgebniseingangsartEndgueltig() && hasGueltigenVorlaeufigenErgebniseingang()) {
      if (vorlaeufigeStimmeingabenPerTK == null) {
        vorlaeufigeStimmeingabenPerTK = EntityUtil.mappeNachKonkretestemTk(getGueltigenVorlaeufigenErgebniseingang().getStimmeingaben());
      }

      return vorlaeufigeStimmeingabenPerTK;
    }

    return Collections.emptyMap();
  }

  private boolean hasGueltigenVorlaeufigenErgebniseingang() {
    return getGueltigenVorlaeufigenErgebniseingang() != null;
  }

  @Nullable
  private Ergebniseingang getGueltigenVorlaeufigenErgebniseingang() {
    return gueltigenVorlaeufigerErgebniseingang;
  }

  public boolean isStimmeingabeZuPlausibilisieren(Stimmeingabe stimmeingabe) {
    return getStimmeingabenZuPlausibilisieren().contains(stimmeingabe);
  }

  /**
   * Manchmal liefern die Views nicht genügend Modelle, um eine Plausibilisierung durchzuführen. Zum Nachliefern der Modelle braucht die Plausibilisierung dann einen StimmeinabeErfassenContext.
   */
  public StimmeingabeErfassenContext createStimmeingabeErfassenContext() {
    if (stimmeingabeErfassenContext == null) {
      stimmeingabeErfassenContext = new AbstractStimmeingabeErfassenContext() {

        @Override
        public Function<Gebiet, List<Wahldurchfuehrungsgebiet>> getErfasstFuer() {
          return StimmeingabePlausiBuilderContext.this.getErfasstFuer();
        }

        public Set<GruppeImGebiet> gruppenImGebiet;

        @Override
        public Ergebniseingang getErgebniseingang() {
          return StimmeingabePlausiBuilderContext.this.getErgebniseingang();
        }

        @Override
        public Collection<GruppeImGebiet> getGruppenImGebiet() {
          if (gruppenImGebiet == null) {
            gruppenImGebiet = new HashSet<>();

            for (Stimmeingabe stimmeingabe : getErgebniseingang().getStimmeingaben()) {
              gruppenImGebiet.add(stimmeingabe.getGruppeImGebiet());
            }
          }

          return gruppenImGebiet;
        }

        @Override
        public Ergebniseingang getLetzterErgebniseingangAktuell() {
          return null;
        }

        @Override
        public Collection<Stimmeingabe> getLetzterErgebniseingangAktuellStimmeingaben() {
          return Collections.emptyList();
        }

        @Override
        public Collection<Stimmeingabe> getVergleichErgebniseingangStimmeingaben() {
          return Collections.emptyList();
        }

        @Override
        public Collection<Stimmeingabe> getLetzterErgebniseingangAktuellVorlaeufigStimmeingaben() {
          return Collections.emptyList(); // ELECT-8352 spielt hier keine Rolle
        }

        @Override
        public List<Stimmeingabefehler> getLetzterErgebniseingangAktuellStimmeingabefehler() {
          return Collections.emptyList();
        }

        @Override
        public EntityAufStimmzettelDaten getEntityAufStimmzettelDaten() {
          return entityAufStimmzettelDaten;
        }

        @Override
        public StimmeingabeKopfzahlErfassenLabelGenerator getStimmeingabeKopfzahlErfassenLabelGenerator() {
          return DefaultStimmeingabeErfassenLabelGenerator.INSTANCE;
        }

        @Override
        public StimmeingabeGruppeStimmenErfassenLabelGenerator getGruppeStimmenErfassenLabelGenerator() {
          return DefaultStimmeingabeErfassenLabelGenerator.INSTANCE;
        }

        @Override
        public StimmeingabeListenkandidaturStimmenErfassenLabelGenerator getListenkandidaturStimmenErfassenLabelGenerator() {
          return DefaultStimmeingabeErfassenLabelGenerator.INSTANCE;
        }

        @Override
        public StimmeingabeReferendumOptionStimmenErfassenLabelGenerator getReferendumOptionStimmenErfassenLabelGenerator() {
          return DefaultStimmeingabeErfassenLabelGenerator.INSTANCE;
        }

        @Override
        public Supplier<Map<Gruppe, Collection<Wahlvorschlag>>> getWahlvorschlaege() {
          return wahlvorschlaege;
        }
      };
    }

    return stimmeingabeErfassenContext;
  }

  public AbstractWahlParameterLoader getWahlParameterLoader() {
    return wahlParameterLoader;
  }

  public EntityAufStimmzettelDaten getEntityAufStimmzettelDaten() {
    return entityAufStimmzettelDaten;
  }

  public Map<String, Object> getCache() {
    return cache;
  }

  @Override
  public Function<Gebiet, List<Wahldurchfuehrungsgebiet>> getErfasstFuer() {
    return erfasstFuer;
  }
}
