
package de.ivu.elect.presentation.stimmeingabe.status;

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.Collections.emptyMap;

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import de.ivu.elect.business.gebietsbaum.boundary.AbstractWahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.stimmeingabe.boundary.AbstractErgebniseingangDAO;
import de.ivu.elect.business.stimmeingabe.entity.AbstractErgebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingabestufe;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.presentation.security.AbstractGebietSecurity;

public class ErgebniseingangPerErfassungsgebietCollector {

  private final AbstractWahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDao;

  private final AbstractErgebniseingangDAO ergebniseingangDao;

  public ErgebniseingangPerErfassungsgebietCollector(AbstractWahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDao, AbstractErgebniseingangDAO ergebniseingangDao) {
    super();

    this.wahldurchfuehrungsgebietDao = wahldurchfuehrungsgebietDao;

    this.ergebniseingangDao = ergebniseingangDao;
  }

  public Map<Wahldurchfuehrungsgebiet, Ergebniseingang> findLetzterErgebniseingangAktuellPerUntergeordnetesErfassungsgebiet(AbstractGebietSecurity gebietSecurity, Gebiet uebergeordnetesGebiet,
      Ergebniseingangsart ergebniseingangsart, boolean nichtErgebnisRelevanteGebieteEinschliessen) {
    return findLetzterErgebniseingangPerUntergeordnetesErfassungsgebiet(gebietSecurity, uebergeordnetesGebiet, ergebniseingangsart, EnumSet.allOf(Ergebniseingabestufe.class),
        nichtErgebnisRelevanteGebieteEinschliessen);
  }

  public Map<Wahldurchfuehrungsgebiet, Ergebniseingang> findLetzterErgebniseingangInErgebniseingabestufePerUntergeordnetesErfassungsgebiet(AbstractGebietSecurity gebietSecurity,
      Gebiet uebergeordnetesGebiet, Ergebniseingangsart ergebniseingangsart, Ergebniseingabestufe ergebniseingabestufe, boolean nichtErgebnisRelevanteGebieteEinschliessen) {
    return findLetzterErgebniseingangPerUntergeordnetesErfassungsgebiet(gebietSecurity, uebergeordnetesGebiet, ergebniseingangsart, EnumSet.of(ergebniseingabestufe),
        nichtErgebnisRelevanteGebieteEinschliessen);
  }

  private Map<Wahldurchfuehrungsgebiet, Ergebniseingang> findLetzterErgebniseingangPerUntergeordnetesErfassungsgebiet(AbstractGebietSecurity gebietSecurity, Gebiet uebergeordnetesGebiet,
      Ergebniseingangsart ergebniseingangsart, Collection<Ergebniseingabestufe> ergebniseingabestufen, boolean nichtErgebnisRelevanteGebieteEinschliessen) {
    if (uebergeordnetesGebiet != null && uebergeordnetesGebiet.isInstanceOf(Wahldurchfuehrungsgebiet.class)) {
      return findLetzterErgebniseingangPerUntergeordnetesErfassungsgebiet(gebietSecurity, castTo(uebergeordnetesGebiet, Wahldurchfuehrungsgebiet.class), ergebniseingangsart, ergebniseingabestufen,
          nichtErgebnisRelevanteGebieteEinschliessen);
    }

    return emptyMap();
  }

  public Map<Wahldurchfuehrungsgebiet, Ergebniseingang> findLetzterErgebniseingangAktuellPerUntergeordnetesErfassungsgebiet(AbstractGebietSecurity gebietSecurity,
      Wahldurchfuehrungsgebiet uebergeordnetesGebiet, Ergebniseingangsart ergebniseingangsart, boolean nichtErgebnisRelevanteGebieteEinschliessen) {
    return findLetzterErgebniseingangPerUntergeordnetesErfassungsgebiet(gebietSecurity, uebergeordnetesGebiet, ergebniseingangsart, EnumSet.allOf(Ergebniseingabestufe.class),
        nichtErgebnisRelevanteGebieteEinschliessen);
  }

  public Map<Wahldurchfuehrungsgebiet, Ergebniseingang> findLetzterErgebniseingangInErgebniseingabestufePerUntergeordnetesErfassungsgebiet(AbstractGebietSecurity gebietSecurity,
      Wahldurchfuehrungsgebiet uebergeordnetesGebiet, Ergebniseingangsart ergebniseingangsart, Ergebniseingabestufe ergebniseingabestufe, boolean nichtErgebnisRelevanteGebieteEinschliessen) {
    return findLetzterErgebniseingangPerUntergeordnetesErfassungsgebiet(gebietSecurity, uebergeordnetesGebiet, ergebniseingangsart, EnumSet.of(ergebniseingabestufe),
        nichtErgebnisRelevanteGebieteEinschliessen);
  }

  private Map<Wahldurchfuehrungsgebiet, Ergebniseingang> findLetzterErgebniseingangPerUntergeordnetesErfassungsgebiet(AbstractGebietSecurity gebietSecurity,
      Wahldurchfuehrungsgebiet uebergeordnetesGebiet, Ergebniseingangsart ergebniseingangsart, Collection<Ergebniseingabestufe> ergebniseingabestufen,
      boolean nichtErgebnisRelevanteGebieteEinschliessen) {
    Map<Wahldurchfuehrungsgebiet, Ergebniseingang> result = new HashMap<>();

    if (uebergeordnetesGebiet != null) {
      findLetzterErgebniseingangPerErfassungsgebiet(gebietSecurity, uebergeordnetesGebiet.getWahl(), ergebniseingangsart, ergebniseingabestufen, nichtErgebnisRelevanteGebieteEinschliessen)
          .forEach((erfassungsgebiet, ergebniseingang) -> {
            if (isUntergeordnetesGebiet(uebergeordnetesGebiet, erfassungsgebiet)) {
              result.put(erfassungsgebiet, ergebniseingang);
            }
          });
    }

    return result;
  }

  public Map<Wahldurchfuehrungsgebiet, Ergebniseingang> findLetzterErgebniseingangAktuellPerErfassungsgebiet(AbstractGebietSecurity gebietSecurity, Wahl wahl, Ergebniseingangsart ergebniseingangsart,
      boolean nichtErgebnisRelevanteGebieteEinschliessen) {
    return findLetzterErgebniseingangPerErfassungsgebiet(gebietSecurity, wahl, ergebniseingangsart, EnumSet.allOf(Ergebniseingabestufe.class), nichtErgebnisRelevanteGebieteEinschliessen);
  }

  public Map<Wahldurchfuehrungsgebiet, Ergebniseingang> findLetzterErgebniseingangInErgebniseingabestufePerErfassungsgebiet(AbstractGebietSecurity gebietSecurity, Wahl wahl,
      Ergebniseingangsart ergebniseingangsart, Ergebniseingabestufe ergebniseingabestufe, boolean nichtErgebnisRelevanteGebieteEinschliessen) {
    return findLetzterErgebniseingangPerErfassungsgebiet(gebietSecurity, wahl, ergebniseingangsart, EnumSet.of(ergebniseingabestufe), nichtErgebnisRelevanteGebieteEinschliessen);
  }

  private Map<Wahldurchfuehrungsgebiet, Ergebniseingang> findLetzterErgebniseingangPerErfassungsgebiet(AbstractGebietSecurity gebietSecurity, Wahl wahl, Ergebniseingangsart ergebniseingangsart,
      Collection<Ergebniseingabestufe> ergebniseingabestufen, boolean nichtErgebnisRelevanteGebieteEinschliessen) {
    Map<Wahldurchfuehrungsgebiet, Ergebniseingang> result = new HashMap<>();

    if (wahl != null && wahl.isInstanceOf(Wahldurchfuehrung.class)) {
      AbstractErgebniseingang.findeLetzteEingaengeProGebiet(castTo(wahl, Wahldurchfuehrung.class), ergebniseingangsart, ergebniseingabestufen, ergebniseingangDao)
          .forEach((wahldurchfuehrungsgebiet, ergebniseingang) -> {
            if (isErfassungsgebietAndRechtVorhanden(gebietSecurity, wahldurchfuehrungsgebiet, nichtErgebnisRelevanteGebieteEinschliessen)) {
              result.put(wahldurchfuehrungsgebiet, ergebniseingang);
            }
          });
    }

    return result;
  }

  public List<Wahldurchfuehrungsgebiet> findUntergeordneteErfassungsgebiete(AbstractGebietSecurity gebietSecurity, Gebiet uebergeordnetesGebiet) {
    List<Wahldurchfuehrungsgebiet> result = new ArrayList<>();

    findErfassungsgebiete(gebietSecurity, uebergeordnetesGebiet.getWahl()).forEach(erfassungsgebiet -> {
      if (isUntergeordnetesGebiet(uebergeordnetesGebiet, erfassungsgebiet)) {
        result.add(erfassungsgebiet);
      }
    });

    return result;
  }

  private List<Wahldurchfuehrungsgebiet> findErfassungsgebiete(AbstractGebietSecurity gebietSecurity, Wahl wahl) {
    List<Wahldurchfuehrungsgebiet> result = new ArrayList<>();

    wahldurchfuehrungsgebietDao.findByWahl(wahl).forEach(wahldurchfuehrungsgebiet -> {
      if (isErfassungsgebietAndRechtVorhanden(gebietSecurity, wahldurchfuehrungsgebiet, false)) {
        result.add(wahldurchfuehrungsgebiet);
      }
    });

    return result;
  }

  private boolean isUntergeordnetesGebiet(Gebiet uebergeordnetesGebiet, Gebiet gebiet) {
    return uebergeordnetesGebiet == null || gebiet.equals(uebergeordnetesGebiet) || gebiet.isUntergebietVon(uebergeordnetesGebiet);
  }

  private boolean isErfassungsgebietAndRechtVorhanden(AbstractGebietSecurity gebietSecurity, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, boolean nichtErgebnisRelevanteGebieteEinschliessen) {
    return (wahldurchfuehrungsgebiet.isErfassungsgebiet() || nichtErgebnisRelevanteGebieteEinschliessen && !wahldurchfuehrungsgebiet.isErgebnisRelevant())
        && gebietSecurity.isRechtVorhanden(wahldurchfuehrungsgebiet);
  }
}
