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

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nullable;

import de.ivu.elect.business.gebietsbaum.boundary.GebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietTK;
import de.ivu.elect.business.gruppe.boundary.GruppeDAO;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.GruppeTK;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietDAO;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietFactory;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebietTK;
import de.ivu.elect.business.wahl.boundary.WahlDAO;
import de.ivu.elect.business.wahl.entity.Wahl;

public abstract class AbstractGruppeImGebietCache implements GruppeImGebietCache {

  protected GebietDAO gebietDAO;

  protected GruppeDAO gruppeDAO;

  protected WahlDAO wahlDAO;

  protected GruppeImGebietDAO gruppeImGebietDAO;

  protected GruppeImGebietFactory gruppeImGebietFactory;

  private final Map<GebietTK, Gebiet> gebietePerGebietTk;

  private final Map<GruppeTK, Gruppe> gruppenPerGruppeTk;

  private final Map<GruppeImGebietTK, GruppeImGebiet> gruppenImGebietPerGruppeImGebietTk;

  AbstractGruppeImGebietCache(WahlDAO wahlDAO, GebietDAO gebietDAO, GruppeDAO gruppeDAO,
      GruppeImGebietDAO gruppeImGebietDAO, GruppeImGebietFactory gruppeImGebietFactory, Wahl wahl) {
    this.wahlDAO = wahlDAO;
    this.gebietDAO = gebietDAO;
    this.gruppeDAO = gruppeDAO;
    this.gruppeImGebietDAO = gruppeImGebietDAO;
    this.gruppeImGebietFactory = gruppeImGebietFactory;
    this.gebietePerGebietTk = GebietTK.mappeGebietNachTK(findGebieteByWahl(wahl));
    this.gruppenPerGruppeTk = GruppeTK.mappeGruppeNachTK(findGruppenByWahl(wahl));
    this.gruppenImGebietPerGruppeImGebietTk = GruppeImGebietTK
        .mappeGruppeImGebietNachTK(findGruppenImGebietByWahl(wahl));
  }

  private List<Gebiet> findGebieteByWahl(Wahl wahl) {
    return gebietDAO.findByWahl(wahl);
  }

  private List<Gruppe> findGruppenByWahl(Wahl wahl) {
    return gruppeDAO.findByWahl(wahl);
  }

  protected abstract Collection<GruppeImGebiet> findGruppenImGebietByWahl(Wahl wahl);


  /**
   * Liefert die GruppeImGebiet zum passenden TK, falls nötig wird sie neu erzeugt.
   */
  @Override
  public GruppeImGebiet findOrCreate(GruppeImGebiet gruppeImGebiet) {
    GruppeImGebietTK gruppeImGebietTk = gruppeImGebiet.getGruppeImGebietTK();

    GruppeImGebiet result = getCachedGruppeImGebiet(gruppeImGebietTk);
    if (result != null) {
      if (gruppeImGebiet.getRelativePosition()!=null) {
        gruppeImGebiet.copyEntityAttributesUsingSetter(result);
      }
      return result;
    }

    return createByTk(gruppeImGebietTk, gruppeImGebiet);
  }

  @Override
  public GruppeImGebiet findOrCreate(GruppeImGebietTK gruppeImGebietTk) {

    GruppeImGebiet result = getCachedGruppeImGebiet(gruppeImGebietTk);

    if (result != null) {
      return result;
    }

    return createByTk(gruppeImGebietTk, null);
  }

  protected GruppeImGebiet createByTk(GruppeImGebietTK gruppeImGebietTk, GruppeImGebiet gruppeImGebiet) {

    GruppeImGebiet result = gruppeImGebietFactory.create();
    result.setGebiet(getCachedGebiet(gruppeImGebietTk.getGebietTK()));
    result.setGruppe(getCachedGruppe(gruppeImGebietTk.getGruppeTK()));

    if (gruppeImGebiet != null) {
      gruppeImGebiet.copyEntityAttributesUsingSetter(result);
    }

    if (result.getGebiet() != null && result.getGruppe() != null) {
      return persist(result);
    }

    throw new IllegalStateException(
        "Versuch, GruppeImGebiet mit unbekannter Gruppe/Gebiet zu benutzen! [gruppeImGebietTK = " + gruppeImGebietTk
            + ", cachedGebiet = " + result.getGebiet() + ", cachedGruppe = " + result.getGruppe() + "]");
  }

  public GruppeImGebiet persistTauscheGruppeUndGebiet(GruppeImGebiet gruppeImGebiet) {
    // Tausche vorhandenes Gebiet und vorhandene Gruppe durch bereits persistente Variante
    gruppeImGebiet.setGebiet(getCachedGebiet(gruppeImGebiet.getGebiet().getGebietTK()));
    gruppeImGebiet.setGruppe(getCachedGruppe(gruppeImGebiet.getGruppe().getGruppeTK()));

    return persist(gruppeImGebiet);
  }

  protected GruppeImGebiet persist(GruppeImGebiet gruppeImGebiet) {

    Collection<GruppeImGebiet> persistenteGruppenImGebiet = persistUndGibNeueEntitaetenZurueck(gruppeImGebiet);

    for (GruppeImGebiet persistenteGruppeImGebiet : persistenteGruppenImGebiet) {
      addCachedGruppeImGebiet(persistenteGruppeImGebiet);
    }

    return getCachedGruppeImGebiet(gruppeImGebiet.getGruppeImGebietTK());
  }

  protected abstract Collection<GruppeImGebiet> persistUndGibNeueEntitaetenZurueck(GruppeImGebiet gruppeImGebiet);

  @Override
  @Nullable
  public Gebiet getCachedGebiet(GebietTK gebietTK) {
    return gebietePerGebietTk.get(gebietTK);
  }

  @Override
  @Nullable
  public Gruppe getCachedGruppe(GruppeTK gruppeTK) {
    return gruppenPerGruppeTk.get(gruppeTK);
  }

  @Override
  public boolean isGruppeVorhanden(GruppeTK gruppeTK) {
    return getCachedGruppe(gruppeTK) != null;
  }

  @Override
  public Gruppe addGruppeToCached(Gruppe gruppe) {
    return gruppenPerGruppeTk.put(gruppe.getGruppeTK(), gruppe);
  }

  protected Map<GruppeImGebietTK, GruppeImGebiet> getGruppenImGebietPerGruppeImGebietTk() {
    return gruppenImGebietPerGruppeImGebietTk;
  }

  private GruppeImGebiet getCachedGruppeImGebiet(GruppeImGebietTK gruppeImGebietTK) {
    return gruppenImGebietPerGruppeImGebietTk.get(gruppeImGebietTK);
  }

  private GruppeImGebiet addCachedGruppeImGebiet(GruppeImGebiet gruppeImGebiet) {
    return gruppenImGebietPerGruppeImGebietTk.put(gruppeImGebiet.getGruppeImGebietTK(), gruppeImGebiet);
  }

  @Override
  public void aktualisierePositionen(Set<GruppeImGebiet> gruppenImGebietAusDatei) {
    gruppenImGebietAusDatei.stream().filter(otherGig -> otherGig.getRelativePosition() != null).forEach(this::findOrCreate);
  }

}
