package net.sf.sze.model.base;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.Embedded;
import javax.persistence.EntityManager;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PersistenceContext;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PreUpdate;
import javax.persistence.Transient;

import net.sf.sze.model.base.RevisionLog.Action;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;

import de.ppi.fuwesta.jpa.helper.VersionedModel;

/**
 * This class handles the revisionlog, it put's after read all values into a map
 * and use this information at write-time.
 *
 */
@Service
public class RevisionModelListener implements InitializingBean {

    private static final Logger LOG = LoggerFactory.getLogger(
            RevisionModelListener.class);

    @PersistenceContext
    private EntityManager entityManager;

    private static EntityManager em;

    /**
     * Save the data.
     * @param model the model.
     */
    @PostLoad
    public void postLoad(RevisionModel model) {
        final Map<String, Object> map = new HashMap<String, Object>();
        putValuesToMap(map, "", model);
        model.setLoadedValues(map);
    }

    /**
     * Let's log a Model Removal.
     * @param model the model.
     */
    @PostRemove
    public void preRemove(RevisionModel model) {
        saveChanges(Action.DELETE, model, model.getLoadedValues(), new HashMap<String, Object>());
    }

    /**
     * Let's log a model creation.
     * @param model the model.
     */
    @PostPersist
    public void prePersist(RevisionModel model) {
        final Map<String, Object> map = new HashMap<String, Object>();
        putValuesToMap(map, "", model);
        saveChanges(Action.CREATE, model, new HashMap<String, Object>(), map);
    }

    /**
     * Let's log a model change.
     * @param model the model.
     */
    @PreUpdate
    public void preUpdate(RevisionModel model) {
        final Map<String, Object> map = new HashMap<String, Object>();
        putValuesToMap(map, "", model);
        saveChanges(Action.UPDATE, model, model.getLoadedValues(), map);
    }

    private void saveChanges(Action action, RevisionModel model,
            Map<String, Object> oldValues, Map<String, Object> newValues) {
        String user = "unkown";
        try {
            final Subject subject = SecurityUtils.getSubject();
            user = "" + subject.getPrincipal();
            if (user.length() > 20) {
                // Shorten and add elipses.
                user = user.substring(0, 19) +  "\u2026";
            }
        } catch (Exception e) {
            LOG.warn("Security-User konnte nicht ermittelt werden.", e);
        }
        final String entityName = model.getClass().getSimpleName();
        final Long entityId = model.getId();
        for (Entry<String, Object> keyValue : oldValues.entrySet()) {
            final String key = keyValue.getKey();
            final Object oldValue = keyValue.getValue();
            final Object newValue = newValues.get(key);
            boolean changes = false;
            if (oldValue == null) {
                changes = newValue != null;
            } else {
                //Das funktioniert alle nicht bei Listen und bei ForeignKeys auch nicht.
                changes = !oldValue.equals(newValue);
            }
            if (changes) {
                RevisionLog revLog = new RevisionLog();
                revLog.setAction(action);
                revLog.setColumnName(key);
                revLog.setEntityName(entityName);
                revLog.setEntityId(entityId);
                revLog.setUser(user);
                revLog.setOldValue("" + oldValue);
                revLog.setNewValue("" + newValue);
                em.merge(revLog);
            }
        }

    }

    private void putValuesToMap(Map<String, Object> map, String prefix, Object model) {
        Class<?> clazz = model.getClass();
        while (clazz != RevisionModel.class && clazz != Object.class) {
            for (Field field : clazz.getDeclaredFields()) {

                // Skip fields
                if (field.getAnnotation(Transient.class) != null
                        || field.getAnnotation(OneToMany.class) != null) {
                    continue;
                }
                final String fieldName = field.getName();
                final Object value = getFieldValue(field, model);
                if (field.getAnnotation(Embedded.class) != null) {
                    putValuesToMap(map, prefix + fieldName + ".", value);
                } else if (value != null && value instanceof VersionedModel) {
                    map.put(fieldName, ((VersionedModel) value).getId());
                } else if (field.getAnnotation(ManyToOne.class) != null
                        || field.getAnnotation(OneToOne.class) != null) {
                    continue;
                } else {
                    map.put(fieldName, value);
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

    private Object getFieldValue(Field field, Object object) {
        field.setAccessible(true);
        try {
            return field.get(object);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            LOG.error("Can't read " + field.getName(), e);
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        em = entityManager;
    }
}


@@ -100,19 +100,23 @@
 va.util.
-Map
+HashSet
 ;%0Aimport
@@ -133,14 +133,8 @@
 .Map
-.Entry
 ;%0A%0Ai
@@ -1959,18 +1959,19 @@
 c void p
-re
+ost
 Persist(
@@ -3219,45 +3219,89 @@
    f
-or (Entry%3CString, Object%3E keyValue : 
+inal HashSet%3CString%3E keyValues = new HashSet%3CString%3E();%0A        keyValues.addAll(
 oldV
@@ -3310,40 +3310,78 @@
 ues.
+k
 e
-ntr
 ySet())
- %7B%0A
+;%0A 
        
-     final 
+keyValues.addAll(newValues.keySet());%0A        for (
 Stri
@@ -3387,17 +3387,17 @@
 ing key 
-=
+:
  keyValu
@@ -3401,18 +3401,12 @@
 alue
-.getKey();
+s) %7B
 %0A   
@@ -3442,26 +3442,25 @@
 e = 
-key
+old
 Value
+s
 .get
-Value(
+(key
 );%0A 

FIX ein Create wurde nicht sauber protokolliert.