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

import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.*;
import java.util.Map.Entry;

import javax.annotation.Nullable;
import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;

import de.ivu.elect.business.protokollierung.boundary.ProtokollEintragDAO;
import de.ivu.elect.business.protokollierung.entity.ProtokollEintrag;
import de.ivu.elect.business.protokollierung.entity.ProtokollEintragConstants;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.protokollierung.AttributTyp;
import de.ivu.elect.jee.protokollierung.Eintrag;
import de.ivu.elect.jee.protokollierung.Protokoll;
import de.ivu.elect.jee.protokollierung.ProtokollSchreiben;
import de.ivu.elect.jee.protokollierung.VorherNachherWert;

public class DatenbankProtokollSchreiben implements ProtokollSchreiben {

  @Inject
  protected transient ProtokollEintragDAO protokollEintragDAO;

  @Inject
  protected transient Messages messages;

  public DatenbankProtokollSchreiben() {
    super();
  }

  @Override
  public void schreibe(Protokoll protokoll, Date zeitpunkt, boolean async) {
    //  eine Instanz für alle Eintraege
    DateFormat df = AttributTyp.createDateFormat(Locale.getDefault(), TimeZone.getDefault()); // Default Locale und Default  TimeZone sollten beim Serverstart korrekt gesetzt sein

    for (Eintrag eintrag : protokoll.getEintraege()) {
      List<ProtokollEintrag> protokollEintraege = erzeugeProtokollEintraege(protokoll, zeitpunkt, eintrag, df);

      for (ProtokollEintrag protokollEintrag : protokollEintraege) {
        protokollEintragDAO.persist(protokollEintrag);
      }
    }

    if (!async) {
      // Bug? Im synchronen Fall muss hier geflusht werden, sonst landet nichts in der Datenbank...
      protokollEintragDAO.flush();
    }
  }

  protected List<ProtokollEintrag> erzeugeProtokollEintraege(Protokoll protokoll, Date zeitpunkt, Eintrag eintrag, DateFormat df) {
    Map<String, VorherNachherWert> protokollierteAttributeMitWerten = eintrag.getProtokollierteAttributeMitWerten();

    List<ProtokollEintrag> result = new ArrayList<>(protokollierteAttributeMitWerten.size() + 1); // Einen  mehr, für den Fall, dass 0 Enträge vorhanden sind und somit ein unnötiges Kopieren vermieden wird...

    if (protokollierteAttributeMitWerten.isEmpty()) {
      result.add(erzeugeProtokollEintragOhneAttributAenderungen(protokoll, zeitpunkt, eintrag, AttributTyp.OHNE));
    } else {
      for (Entry<String, VorherNachherWert> attributeVorherNachherWertEntry : protokollierteAttributeMitWerten.entrySet()) {
        VorherNachherWert vorherNachherWert = attributeVorherNachherWertEntry.getValue();
        AttributTyp typ = vorherNachherWert.getTyp();

        ProtokollEintrag protokollEintrag = erzeugeProtokollEintragOhneAttributAenderungen(protokoll, zeitpunkt, eintrag, typ);
        protokollEintrag.setAlterWert(konvertAlterWert(typ, vorherNachherWert.getVorher(), df));
        protokollEintrag.setNeuerWert(konvertNeuerWert(typ, vorherNachherWert.getNachher(), df));
        protokollEintrag.setAttribut(attributeVorherNachherWertEntry.getKey()); // Kein Trimmen zulässig, der Name des Attributes muss immer passen!

        if (containsWertGeandert(protokollEintrag)) {
          result.add(protokollEintrag);
        }
      }
    }

    return result;
  }

  private boolean containsWertGeandert(ProtokollEintrag protokollEintrag) {
    AttributTyp attributTyp = protokollEintrag.getAttributTyp();

    String alterWert = protokollEintrag.getAlterWert();
    String neuerWert = protokollEintrag.getNeuerWert();

    if (AttributTyp.BIG_DECIMAL.equals(attributTyp)) {
      return isWertGeandert(attributTyp.getValueAsBigDecimal(alterWert), attributTyp.getValueAsBigDecimal(neuerWert));
    }

    return isWertGeandert(alterWert, neuerWert);
  }

  public static boolean isWertGeandert(BigDecimal alterWert, BigDecimal neuerWert) {
    return !(Objects.equals(alterWert, neuerWert) || alterWert != null && neuerWert != null && alterWert.compareTo(neuerWert) == 0);
  }

  public static boolean isWertGeandert(String alterWert, String neuerWert) {
    // Insbesondere bei Datumsangaben kann es sein, dass es technisch eine Änderung gibt, die aber aufgrund von Rundungen nicht sichtbar wird => Eintrag nur Speichern, wenn sichtbare Änderung.
    return !StringUtils.equals(alterWert, neuerWert);
  }

  @Nullable
  private String konvertNeuerWert(AttributTyp typ, Object nachher, DateFormat df) {
    return konvertWert(typ, nachher, ProtokollEintragConstants.NEUER_WERT_MAX_LENGTH, df);
  }

  @Nullable
  private String konvertAlterWert(AttributTyp typ, Object vorher, DateFormat df) {
    return konvertWert(typ, vorher, ProtokollEintragConstants.ALTER_WERT_MAX_LENGTH, df);
  }

  @Nullable
  private String konvertWert(AttributTyp typ, Object object, int maxLength, DateFormat df) {
    if (object == null) {
      return null;
    }

    return trimTo(typ.konvertiereInZuschreibendenString(object, df, messages), maxLength);
  }

  private ProtokollEintrag erzeugeProtokollEintragOhneAttributAenderungen(Protokoll protokoll, Date zeitpunkt, Eintrag eintrag, AttributTyp typ) {
    ProtokollEintrag protokollEintrag = new ProtokollEintrag();
    protokollEintrag.setBenutzerkennung(trimTo(protokoll.getBenutzerkennung(), ProtokollEintragConstants.BENUTZERKENNUNG_MAX_LENGTH));
    protokollEintrag.setEntitaet(eintrag.getEntityTechnicalName()); // Kein Trimmen zulässig, der Name der Entität muss immer passen!
    protokollEintrag.setPrimaerschluessel(eintrag.getId());
    protokollEintrag.setAttributTyp(typ);
    protokollEintrag.setProtokollEintragArt(eintrag.getProtokollEintragArt());
    protokollEintrag.setZeitpunkt(zeitpunkt);

    return protokollEintrag;
  }

  private static String trimTo(String string, int maxLength) {
    return StringUtils.substring(string, 0, maxLength);
  }

}
