package com.ethlo.dachs.jpa;

import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.persistence.Entity;
import javax.persistence.MappedSuperclass;
import javax.persistence.PersistenceUnitUtil;

import com.ethlo.dachs.EntityDataChange;
import com.ethlo.dachs.EntityDataChangeImpl;
import com.ethlo.dachs.PropertyChange;

public class IdentityUtil
{
    private final PersistenceUnitUtil persistenceUnitUtil;
    
    public IdentityUtil(PersistenceUnitUtil persistenceUnitUtil)
    {
        this.persistenceUnitUtil = persistenceUnitUtil;
    }
    
    /**
     * Converts all the entities in the graph to simply contain their primary key. This is useful for logging and auditing as it 
     * avoids potential cycles in the graph.
     * 
     * @param list
     * @return
     */
    public Collection<EntityDataChange> toIndentityReferences(Collection<EntityDataChange> list)
    {
        return list.stream().map((entityData)->
        {
            return new EntityDataChangeImpl(entityData.getId(), entityData.getEntity(), entityData.getPropertyChanges().stream().map((change)->
            {
                return transform(change);
            }).collect(Collectors.toList()));
        }).collect(Collectors.toList());
    }    

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private <T> Object transform(Class<T> type, Object object)
    {
        if (Collection.class.isAssignableFrom(type))
        {
            return handleCollection((Collection<?>)object);
        }
        else if (Map.class.isAssignableFrom(type))
        {
            return handleMap((Map) object);
        }
        else if (isEntity(type))
        {
            return object != null ? getId(object) : null;
        }
        return object; 
    }
    
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private <T> PropertyChange<T> transform(PropertyChange<T> change)
    {
        final Class<?> type = change.getPropertyType();
        if (Collection.class.isAssignableFrom(type))
        {
            final Object oldRefColl = transform(type, (Collection<T>)change.getNewValue());
            final Object newRefColl = transform(type, (Collection<T>)change.getOldValue()); 
            return new PropertyChange(change.getPropertyName(), change.getPropertyType(), oldRefColl, newRefColl);
        }
        else if (Map.class.isAssignableFrom(type))
        {
            final Object oldRefColl = transform(type, (Map)change.getNewValue());
            final Object newRefColl = transform(type, (Map)change.getOldValue()); 
            return new PropertyChange(change.getPropertyName(), change.getPropertyType(), oldRefColl, newRefColl);
        }
        else if (isEntity(type))
        {
            final Serializable newId = change.getNewValue() != null ? getId(change.getNewValue()) : null;
            final Serializable oldId = change.getOldValue() != null ? getId(change.getOldValue()) : null;
            return new PropertyChange(change.getPropertyName(), change.getPropertyType(), oldId, newId);
        }
        return change; 
    }
    
    private Collection<?> handleCollection(Collection<?> coll)
    {
        if (coll == null)
        {
            return null;
        }
        final List<Object> tmp = new LinkedList<>();
        coll.forEach(e->{tmp.add(transform(e.getClass(), e));});
        return tmp;
    }
    
    private Map<?,?> handleMap(Map<Object,Object> map)
    {
        if (map == null)
        {
            return null;
        }
        final Map<Object, Object> tmp = new LinkedHashMap<>();
        map.entrySet().forEach(e->
        {
            tmp.put(e.getKey(), transform(e.getValue().getClass(), e.getValue()));
        });
        return tmp;
    }

    private boolean isEntity(Class<?> type)
    {
        return type.getAnnotation(Entity.class) != null 
           || type.getAnnotation(MappedSuperclass.class) != null;
    }
    
    private Serializable getId(Object entity)
    {
        return (Serializable) persistenceUnitUtil.getIdentifier(entity);
    }
}

@@ -2205,39 +2205,24 @@
 sform(type, 
-(Collection%3CT%3E)
 change.getNe
@@ -2211,35 +2211,35 @@
 type, change.get
-New
+Old
 Value());%0A      
@@ -2290,23 +2290,8 @@
 pe, 
-(Collection%3CT%3E)
 chan
@@ -2288,35 +2288,35 @@
 type, change.get
-Old
+New
 Value()); %0A     
@@ -2300,33 +2300,32 @@
 .getNewValue());
- 
 %0A            ret
@@ -2541,37 +2541,32 @@
 transform(type, 
-(Map)
 change.getNewVal
@@ -2551,35 +2551,35 @@
 type, change.get
-New
+Old
 Value());%0A      
@@ -2626,21 +2626,16 @@
 m(type, 
-(Map)
 change.g
@@ -2628,35 +2628,35 @@
 type, change.get
-Old
+New
 Value()); %0A     
@@ -2648,17 +2648,16 @@
 alue());
- 
 %0A       

Fix #8