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

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

import de.ivu.elect.business.datei.entity.DateiImportBerichtCustomData;
import de.ivu.elect.business.exchange.entity.ImportUpdateReport;
import de.ivu.elect.jee.exchange.ImportOptions;
import de.ivu.elect.jee.exchange.Violations;
import de.ivu.elect.util.CollectionSeparator;

// Abgeleitete Klasse mit @Stateless annotieren!
public abstract class AbstractEntityImporter<Entity, EntityTK, Context, CustomDateiImportBerichtCustomData extends DateiImportBerichtCustomData>
    extends
      AbstractImporter<List<Entity>, Context, CustomDateiImportBerichtCustomData> {

  public AbstractEntityImporter() {
    super();
  }

  @Override
  protected void anlegenWennNichtVorhanden(List<Entity> importObject, Context context,
      CustomDateiImportBerichtCustomData customDateiImportBerichtCustomData, StringBuilder bericht) {
    throw new UnsupportedOperationException();
  }

  @Override
  protected void loescheBestehendenDatenWennNichtInImportDatenVorhanden(List<Entity> importObject, Context context,
      CustomDateiImportBerichtCustomData customDateiImportBerichtCustomData, StringBuilder bericht) {
    throw new UnsupportedOperationException();
  }

  @Override
  protected void aktualisiereBestehendenDaten(List<Entity> importObject, Context context,
      CustomDateiImportBerichtCustomData customDateiImportBerichtCustomData, StringBuilder bericht) {
    throw new UnsupportedOperationException();
  }

  /**
   * Diese Methode nur überschreiben, wenn es aus Performancegründen absolut notwendig ist!
   *
   */
  @Override
  protected void importiereImportObject(List<Entity> inDatei, Context context, ImportOptions importOptions,
      CustomDateiImportBerichtCustomData customDateiImportBerichtCustomData, StringBuilder bericht) {
    importiereImportObject(getSeparator(inDatei, context), context, importOptions, customDateiImportBerichtCustomData, bericht);
  }

  /**
   * Diese Methode nur überschreiben, wenn es aus Performancegründen absolut notwendig ist!
   *
   * @param separator
   *          mit First == importierte Entitäten und Second = bestehende Datenbank-Entitäten
   */
  protected void importiereImportObject(CollectionSeparator<Entity, EntityTK> separator, Context context, ImportOptions importOptions,
      CustomDateiImportBerichtCustomData customDateiImportBerichtCustomData, StringBuilder bericht) {

    if (importOptions.isLoescheBestehendenDatenWennNichtInImportDatenVorhanden()) {
      loescheAusDatenbank(separator.getInSecondNotInFirst(), context, customDateiImportBerichtCustomData, bericht);
      /*
       * tdu: An dieser Stelle wäre es eventuell hilfreich flush auf dem EntityManager aufzurufen. Falls jemand auf das
       * Problem stößt, dass in der persist-Methode bereits gelöschte Entitäten vom Finder des DAOs weiterhin gefunden
       * werden, sollte er in Erwägung ziehen, hier flush aufzurufen. Bisher wird hier kein flush aufgerufen, da die
       * Seiteneffekte nicht überschaubar sind und sich niemand getraut hat. Der KandidaturImporter ruft flush daher
       * selbst auf.
       */
    }

    if (importOptions.isAktualisiereBestehendenDaten()) {
      aktualisiere(separator.getInBoth(), separator.getFirstMap(), separator.getSecondMap(), context, customDateiImportBerichtCustomData, bericht);
    }

    if (importOptions.isImportDatenUebernehmenWennNichtBereitsVorhanden()) {
      persistiere(separator.getInFirstNotInSecond(), context, customDateiImportBerichtCustomData, bericht);
    }

  }

  protected void aktualisiere(Collection<EntityTK> entityTks, Map<EntityTK, Entity> inDatei, Map<EntityTK, Entity> inDb,
      Context context, CustomDateiImportBerichtCustomData customDateiImportBerichtCustomData, StringBuilder bericht) {
    for (EntityTK entityTk : entityTks) {
      aktualisiere(inDatei.get(entityTk), inDb.get(entityTk), context, customDateiImportBerichtCustomData, bericht);
    }
  }

  @Override
  protected ImportUpdateReport createImportUpdateReport(List<Entity> inDatei, Violations violations, Context context,
      ImportOptions importOptions, StringBuilder bericht) {
    CollectionSeparator<Entity, EntityTK> separator = getSeparator(inDatei, context);
    return createImportUpdateReport(separator, violations, context, importOptions, bericht);
  }

  /**
   * @param separator First == importierte Entitäten und Second = bestehende Datenbank-Entitäten.
   */
  protected ImportUpdateReport createImportUpdateReport(CollectionSeparator<Entity, EntityTK> separator,
      Violations violations, Context context, ImportOptions importOptions, StringBuilder bericht) {
    return new ImportUpdateReport(violations, getAnzahlInDateiNichtInDatenbank(separator, context),
        getAnzahlInDatenbankNichtInDatei(separator, context), getAnzahlInDateiUndInDatenbank(separator, context), importOptions, bericht);
  }

  protected int getAnzahlInDateiUndInDatenbank(CollectionSeparator<Entity, EntityTK> separator, Context context) {
    return separator.getInBoth().size();
  }

  protected int getAnzahlInDatenbankNichtInDatei(CollectionSeparator<Entity, EntityTK> separator, Context context) {
    return separator.getInSecondNotInFirst().size();
  }

  protected int getAnzahlInDateiNichtInDatenbank(CollectionSeparator<Entity, EntityTK> separator, Context context) {
    return separator.getInFirstNotInSecond().size();
  }

  /**
   * Schreibt die neueEntities (diejenigen, zu denen es noch keine Entsprechung in der Datenbank gab) in die Datenbank.
   *
   * @param neueEntities
   *          Entitäten, die aus der Importdatei gelesen wurden, zu denen es noch keine Entsprechung in der Datenbank
   *          gab.
   * @param context
   *          Kontext des Imports
   * @param customDateiImportBerichtCustomData
   *          darin kann über den Import berichtet werden
   * @param bericht
   *          darin kann über den Import berichtet werden
   */
  protected abstract void persistiere(Collection<Entity> neueEntities, Context context,
      CustomDateiImportBerichtCustomData customDateiImportBerichtCustomData, StringBuilder bericht);

  /**
   * Wird für Paare von Entitäten mit gleichem TK (fachlichem Schlüssel) aufgerufen. <code>entityInDatenbank</code> soll
   * mit den Daten der <code>entityInDatei</code> aktualisiert werden.
   *
   * @param entityInDatei
   *          eine Entität,, die aus der Importdatei gelesen wurde
   * @param entityInDatenbank
   *          eine Entität,, die aus der Datenbank gelesen wurde
   * @param context
   *          Kontext des Imports
   * @param customDateiImportBerichtCustomData
   *          darin kann über den Import berichtet werden
   * @param bericht
   *          darin kann über den Import berichtet werden
   */
  protected abstract void aktualisiere(Entity entityInDatei, Entity entityInDatenbank, Context context,
      CustomDateiImportBerichtCustomData customDateiImportBerichtCustomData, StringBuilder bericht);

  /**
   * Löscht bestehende Entitäten (diejenigen, zu denen es keine Entsprechung in der Import-Datei gab) aus der Datenbank.
   *
   * @param entitiesInDb
   *          Entitäten, die aus der Datenbank gelesen wurden, zu denen es keine Entsprechung in der Importdatei gab.
   * @param context
   *          Kontext des Imports
   * @param customDateiImportBerichtCustomData
   *          darin kann über den Import berichtet werden
   * @param bericht
   *          darin kann über den Import berichtet werden
   */
  protected abstract void loescheAusDatenbank(Collection<Entity> entitiesInDb, Context context,
      CustomDateiImportBerichtCustomData customDateiImportBerichtCustomData, StringBuilder bericht);

  /**
   * Erzeugt einen Separator, wobei First == importierte Entitäten und Second = bestehende Datenbank-Entitäten.
   */
  protected CollectionSeparator<Entity, EntityTK> getSeparator(List<Entity> inDatei, Context context) {
    return getSeparator(inDatei, getAllEntitiesInDB(context));
  }

  /**
   * Läd bestehende Entitäten, die zum Import gehören, aus der Datenbank. "Zum Import gehören" heißt dabei, dass diese
   * Daten durch importierte überschrieben werden könnten.
   */
  protected abstract List<Entity> getAllEntitiesInDB(Context context);

  private CollectionSeparator<Entity, EntityTK> getSeparator(List<Entity> inDatei, List<Entity> inDatenbank) {
    return new CollectionSeparator<>(inDatei, inDatenbank, getEntityToEntityTKFunction());
  }

  protected abstract Function<Entity, EntityTK> getEntityToEntityTKFunction();

  protected Set<EntityTK> validiereEntitiesNichtMehrfachInDatei(Violations violations, List<Entity> inDatei, Context context) {
    Set<EntityTK> entititesGefunden = new HashSet<>();
    Set<EntityTK> entititesDoppelt = new HashSet<>();
    for (Entity entity : inDatei) {
      EntityTK entityTK = getEntityToEntityTKFunction().apply(entity);
      if (!entititesGefunden.add(entityTK)) {
        entititesDoppelt.add(entityTK);
        violations.addError("importer.importieren.error.entity.mehrfach.in.datei", getMetaForEntity().getEinzahlMessage(),
            ((de.ivu.elect.jee.entity.tk.EntityTK) entityTK).toStringLokalisiert(getMessages()));
      }
    }
    return entititesDoppelt;
  }
}
