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

import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.stream.Stream;

import de.ivu.elect.jee.entity.Entity;
import de.ivu.elect.jee.entity.EntityPK;

/**
 * Problem: viele Entitäten haben keinen Unique Key und lassen sich folglich bei einem Import nicht ohne weiteres
 * wiederherstellen. Es gibt eine Referenz in der Datei, aber in der Datenbank geht die Eindeutigkeit verloren.
 * <p/>
 * Idee: wir merken uns im ImportMapper, welche Entität aus welcher Instanz gemappt wurde, und im Importer, welche
 * Entität welchen PK erhalten hat. Dann können wir später aus einer Referenz den PK bestimmen.
 */
public class DefaultImportInstanceTracker implements ImportInstanceTracker {

  /**
   * Speichert zu einer eingelesenen Entität die Referenz, unter der sie (auch in einer späteren Transaktion)
   * wieder identifiziert werden kann.
   * <p/>
   * NOTE (IdentityHashMap): beim Persistieren ändert sich der hashCode einer Entity, sodass wir sie mit einer regulären
   *   HashMap nicht wiedererkennen würden. Jedoch bleibt die Instanz dieselbe -- wofür sich IdentityHashMap nutzen
   *   lässt.
  */
  private final Map<Entity, Object> entityEingelesenMap = new IdentityHashMap<>();

  /**
   * Speichert zu einer persistierten Entität die Referenz in einer externen Datei sowie den PK in der Datenbank.
   */
  private final Map<Object, EntityPK> entityPersistiertMap = new HashMap<>();

  @Override
  public void onEntityEingelesen(Entity entityInDatei, Object referenzExtern) {
    entityEingelesenMap.put(entityInDatei, referenzExtern);
  }

  @Override
  public void onEntitiesPersistiert(Entity... entities) {
    Stream.of(entities).forEach(this::onEntityPersistiert);
  }

  private void onEntityPersistiert(Entity entity) {
    if (entity.getId() == null) {
      throw new IllegalArgumentException("onEntityPersistiert aufgerufen, aber id==null: " + entity);
    }

    Object referenzExtern = entityEingelesenMap.get(entity);
    if (referenzExtern != null) {
      // HINT: Ab jetzt kennen wir zu dieser Referenz den PK
      entityPersistiertMap.put(referenzExtern, (EntityPK) entity.getPk());

      // HINT: Löschen. Später kann wieder eine Entity mit derselben Referenz eingelesen werden -- und dann finden
      //   wir zu ihr den obigen PK wieder.
      entityEingelesenMap.remove(entity);
    }
  }

  /**
   * Prüft, ob zur gegebenen nicht-persistenten Entität ein PK bekannt ist.
   * <p/>
   * Das ist dann der Fall, wenn die Entität soeben mit einer Referenz aus einer Datei eingelesen wurde, und zuvor
   * unter derselben Referenz eine Entität persistiert worden ist.
   */
  public boolean isEntityPersistiert(Entity entityInDatei, Class<?> pkClazz) {
    Object referenzExtern = entityEingelesenMap.get(entityInDatei);
    if (referenzExtern != null) {
      Object pk = entityPersistiertMap.get(referenzExtern);
      return pkClazz.isInstance(pk);
    }

    return false;
  }

  /**
   * Prüft, ob die gegebene Entität als <i>eingelesen</i> (nicht notwendigerweise <i>persistiert</i>) bekannt ist.
   */
  public boolean isEntityEingelesen(Entity entity) {
    return entityEingelesenMap.containsKey(entity);
  }

  /**
   * Gibt zur gegebenen nicht-persistenten Entität einen PK zurück.
   * <p/>
   * Das ist dann möglich, wenn die Entität soeben mit einer Referenz aus einer Datei eingelesen wurde, und zuvor
   * unter derselben Referenz eine Entität persistiert worden ist.
   */
  public <P extends EntityPK> P getEntityPK(Entity entityInDatei, Class<P> pkClazz) {
    Object referenzExtern = entityEingelesenMap.get(entityInDatei);
    if (referenzExtern == null) {
      throw new IllegalArgumentException(
          String.format("Keine Referenz für Entity %x hinterlegt: %s", System.identityHashCode(entityInDatei), entityInDatei));
    }
    try {
      return getPkByReferenz(referenzExtern, pkClazz);
    } catch (IllegalArgumentException ex) {
      throw new IllegalArgumentException(String
          .format("Kein PK der Klasse %s für Entity %x hinterlegt: %s", pkClazz, System.identityHashCode(entityInDatei),
              entityInDatei), ex);
    }
  }

  private <P extends EntityPK> P getPkByReferenz(Object referenzExtern, Class<P> pkClazz) {
    Object pkObject = entityPersistiertMap.get(referenzExtern);
    if (pkObject == null) {
      throw new IllegalArgumentException("Kein PK für Referenz hinterlegt: " + referenzExtern);
    }
    if (!pkClazz.isInstance(pkObject)) {
      throw new IllegalArgumentException(
          "Erwarte PK der Klasse " + pkClazz + ", aber habe " + pkObject + " für Referenz: " + referenzExtern);
    }
    return pkClazz.cast(pkObject);
  }
}
