package de.ivu.elect.jee.protokollierung;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;

import org.apache.commons.lang3.StringUtils;

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


import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

/**
 *  on 14.02.2017.
 */
public class ProtokollFlashback implements Serializable {

  public <E extends Entity> Optional<E> flashback(E aktuelleVersion, Supplier<E> entityCreator, Collection<? extends ProtokollEintragPersistent> alleProtokolleintraege, Date flashbackZeitpunt) {
    // Entität existierte zum angegebenen Zeitpunkt noch nicht
    if (isAngelegtNachZeitpunkt(flashbackZeitpunt, alleProtokolleintraege)) {
      return Optional.empty();
    }

    List<ProtokollEintragPersistent> relevanteProtokolleintrage = bestimmeRelevanteProtokolleintraege(alleProtokolleintraege, flashbackZeitpunt);
    List<ProtokollEintragPersistent> ersteAenderungProAttribut = bestimmeErsteAenderungProAttribut(relevanteProtokolleintrage);

    E flashbackTarget = entityCreator.get();
    // erstmal die Flashback-Version auf den aktuellen Stand setzen
    aktuelleVersion.copyEntityAttributesToOtherEntity(flashbackTarget);

    Map<String, AttributWertVorher> collect = ersteAenderungProAttribut.stream().collect(toMap(ProtokollEintragPersistent::getAttribut, this::mapppeAttributWertVorher));

    flashbackTarget.setProtokollierteAttributeMitWerten(collect);

    return Optional.of(flashbackTarget);
  }

  private AttributWertVorher mapppeAttributWertVorher(ProtokollEintragPersistent eintragPersistent) {
    return new AttributWertVorher(eintragPersistent.getAttributTyp(), eintragPersistent.getAlterWert());
  }

  protected List<ProtokollEintragPersistent> bestimmeErsteAenderungProAttribut(List<ProtokollEintragPersistent> relevanteProtokolleintrage) {
    List<ProtokollEintragPersistent> ersteAenderungProAttribut = new ArrayList<>();

    Set<String> veraenderteAttribute = new HashSet<>();
    // Änderungen aufsteigend sortiert
    relevanteProtokolleintrage.sort(Comparator.comparing(ProtokollEintragPersistent::getZeitpunkt));
    // nehme für jedes Attribute die erste (am weitesten zurückliegende) Änderung

    relevanteProtokolleintrage.stream()
        .filter(pe -> pe.getProtokollEintragArt().isAenderung())
        .forEach(protokollEintragPersistent -> {
          String attribut = protokollEintragPersistent.getAttribut();
          if (StringUtils.isNotBlank(attribut) && !veraenderteAttribute.contains(attribut)) {
            veraenderteAttribute.add(attribut);
            ersteAenderungProAttribut.add(protokollEintragPersistent);
          }
        });
    return ersteAenderungProAttribut;
  }

  protected boolean isAngelegtNachZeitpunkt(Date flashbackZeitpunt, Collection<? extends ProtokollEintragPersistent> relevanteProtokolleintrage) {
    return relevanteProtokolleintrage.stream()
        .filter(pe -> pe.getZeitpunkt().after(flashbackZeitpunt)) // nur wirklich Einträge NACH dem Zeitpunkt betrachten
        .map(ProtokollEintragPersistent::getProtokollEintragArt).anyMatch(ProtokollEintragArt.ANLEGEN::equals);
  }

  protected List<ProtokollEintragPersistent> bestimmeRelevanteProtokolleintraege(Collection<? extends ProtokollEintragPersistent> alleProtokolleintraege, Date flashbackZeitpunt) {
    return alleProtokolleintraege.stream()
        .filter(pe -> !pe.getZeitpunkt().before(flashbackZeitpunt))
        .filter(pe->pe.getAttributTyp().isFlashbackSupported())
        .collect(toList());
  }
}

