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

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.concurrent.atomic.AtomicLong;
import java.util.function.Function;

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

import de.ivu.elect.business.datei.entity.DateiImportBerichtCustomData;
import de.ivu.elect.business.exchange.control.AbstractEntityImporter;
import de.ivu.elect.business.exchange.control.ImportExchange;
import de.ivu.elect.business.gebietsbaum.control.GebietGeometrieImportExchange;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietGeometrieImportContext;
import de.ivu.elect.business.gebietsbaum.entity.GebietPolygonPoint;
import de.ivu.elect.business.gebietsbaum.entity.GebietPolygonPointMeta;
import de.ivu.elect.business.gebietsbaum.entity.GebietPolygonPointTK;
import de.ivu.elect.business.gebietsbaum.entity.GebietTK;
import de.ivu.elect.jee.entity.Meta;
import de.ivu.elect.jee.exchange.ImportOptions;
import de.ivu.elect.jee.exchange.Violations;

@Stateless
public class GebietGeometrieImporter
    extends
      AbstractEntityImporter<GebietPolygonPoint, GebietPolygonPointTK, GebietGeometrieImportContext, DateiImportBerichtCustomData> {

  @Inject
  private transient GebietPolygonPointMeta meta;

  @Inject
  private transient GebietGeometrieImportExchange gebietGeometrieImportExchange;

  @Inject
  private transient GebietPolygonPointDAO gebietPolygonPointDAO;

  @Inject
  private transient GebietDAO gebietDAO;

  public GebietGeometrieImporter() {
    super();
  }

  @Override
  protected void validiere(Violations violations, List<GebietPolygonPoint> importObject, GebietGeometrieImportContext context, ImportOptions importOptions) {
    for (GebietPolygonPoint gebietPolygonPoint : importObject) {
      validateEntity(violations, gebietPolygonPoint, gebietPolygonPoint.getTk(), meta);
    }

    List<Gebiet> gebieteInD = getGebieteInDb(context);

    Set<GebietTK> gefunden = new HashSet<>();
    Set<GebietTK> nichtInDb = new HashSet<>();
    Map<GebietTK, Gebiet> gebieteInDbNachTk = GebietTK.mappeGebietNachTK(gebieteInD);

    Map<String, AtomicLong> anzahlGebieteNachGebietsartGefunden = new HashMap<>();
    Map<String, AtomicLong> anzahlGebieteNachGebietsartNichtInDbGefunden = new HashMap<>();
    Map<String, AtomicLong> anzahlGebieteNachGebietsartNichtInDateiGefunden = new HashMap<>();

    Set<GebietTK> betrachteteTks = new HashSet<>();

    for (GebietPolygonPoint gebietPolygonPoint : importObject) {
      GebietTK tk = gebietPolygonPoint.getGebiet().getGebietTK();
      // Wenn noch nicht vorhanden
      if (betrachteteTks.add(tk)) {
        if (!gebieteInDbNachTk.containsKey(tk)) {
          anzahlGebieteNachGebietsartNichtInDbGefunden.computeIfAbsent(tk.getGebietsartTK().getName(), x -> new AtomicLong(0L)).addAndGet(1);
          nichtInDb.add(tk);
        } else {
          anzahlGebieteNachGebietsartGefunden.computeIfAbsent(tk.getGebietsartTK().getName(), x -> new AtomicLong(0L)).addAndGet(1);
          gefunden.add(tk);
        }
      }
    }

    for (GebietTK gebietTK : gebieteInDbNachTk.keySet()) {
      if (!gefunden.contains(gebietTK)) {
        anzahlGebieteNachGebietsartNichtInDateiGefunden.computeIfAbsent(gebietTK.getGebietsartTK().getName(), x -> new AtomicLong(0L)).addAndGet(1L);
      }
    }

    anzahlGebieteNachGebietsartGefunden.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(e-> {
      violations.addInfo("gebiet.geometrie.import.gebiete.mit.art.gefunden", e.getKey(), e.getValue().get());
    });

    anzahlGebieteNachGebietsartNichtInDbGefunden.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(e-> {
      violations.addInfo("gebiet.geometrie.import.gebiete.mit.art.nicht.in.db.gefunden", e.getKey(), e.getValue().get());
    });

    anzahlGebieteNachGebietsartNichtInDateiGefunden.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(e-> {
      violations.addInfo("gebiet.geometrie.import.gebiete.mit.art.nicht.in.datei.gefunden", e.getKey(), e.getValue().get());
    });

    for (GebietTK gebietTK : nichtInDb) {
      violations.addInfo("gebiet.geometrie.import.gebiet.nicht.in.db", gebietTK.toStringLokalisiert(getMessages()));
    }

    for (GebietTK gebietTK : gebieteInDbNachTk.keySet()) {
      if (!gefunden.contains(gebietTK)) {
        violations.addInfo("gebiet.geometrie.import.gebiet.nicht.in.datei", gebietTK.toStringLokalisiert(getMessages()));
      }
    }

  }

  private List<Gebiet> getGebieteInDb(GebietGeometrieImportContext context) {
    if (context.getWahl() != null) {
      return gebietDAO.findByWahl(context.getWahl());
    }

    return gebietDAO.findByWahltermin(context.getWahltermin());

  }

  @Override
  protected void erstelleBericht(List<GebietPolygonPoint> importObject, GebietGeometrieImportContext context, ImportOptions importOptions,
      StringBuilder bericht, boolean vorab) {
  }

  @Override
  protected void persistiere(Collection<GebietPolygonPoint> neueEntities, GebietGeometrieImportContext context,
      DateiImportBerichtCustomData customDateiImportBerichtCustomData, StringBuilder bericht) {

    List<Gebiet> gebieteInD = getGebieteInDb(context);

    Map<GebietTK, Gebiet> gebieteInDbNachTk = GebietTK.mappeGebietNachTK(gebieteInD);

    for (GebietPolygonPoint neueEntity : neueEntities) {
      neueEntity.setGebiet(gebieteInDbNachTk.get(neueEntity.getGebiet().getGebietTK()));
      if (neueEntity.getGebiet() != null) {
        gebietPolygonPointDAO.persist(neueEntity);
      }
    }
  }

  @Override
  protected void aktualisiere(GebietPolygonPoint entityInDatei, GebietPolygonPoint entityInDatenbank, GebietGeometrieImportContext context,
      DateiImportBerichtCustomData customDateiImportBerichtCustomData, StringBuilder bericht) {
    entityInDatenbank.setLatitude(entityInDatei.getLatitude());
    entityInDatenbank.setLongitude(entityInDatei.getLongitude());
    entityInDatenbank.setGebietPolygonPointArt(entityInDatei.getGebietPolygonPointArt());
    entityInDatenbank.setPosition(entityInDatei.getPosition());
  }

  @Override
  protected void loescheAusDatenbank(Collection<GebietPolygonPoint> entitiesInDb, GebietGeometrieImportContext context,
      DateiImportBerichtCustomData customDateiImportBerichtCustomData, StringBuilder bericht) {
    for (GebietPolygonPoint gebietPolygonPoint : entitiesInDb) {
      gebietPolygonPointDAO.remove(gebietPolygonPoint);
    }
  }

  @Override
  protected List<GebietPolygonPoint> getAllEntitiesInDB(GebietGeometrieImportContext context) {
    if (context.getWahl() != null) {
      return gebietPolygonPointDAO.findByWahl(context.getWahl());
    }

    return gebietPolygonPointDAO.findByWahltermin(context.getWahltermin());
  }

  @Override
  protected Function<GebietPolygonPoint, GebietPolygonPointTK> getEntityToEntityTKFunction() {
    return GebietPolygonPointTK.getGebietPolygonPointToTKFunction();
  }

  @Override
  protected ImportExchange<List<GebietPolygonPoint>, GebietGeometrieImportContext> getExchange() {
    return gebietGeometrieImportExchange;
  }

  @Override
  public Meta getMetaForEntityInternal() {
    return meta;
  }

  @Override
  protected boolean isUnterstuetztAnlegenWennNichtVorhanden() {
    return true;
  }

  @Override
  protected boolean isUnterstuetztAktualisiereBestehendenDaten() {
    return true;
  }

  @Override
  protected boolean isUnterstuetztLoescheBestehendenDatenWennNichtInImportDatenVorhanden() {
    return true;
  }

}
