package com.huskycode.jpaquery.persister;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.Id;

import com.huskycode.jpaquery.DependenciesDefinition;
import com.huskycode.jpaquery.link.Link;
import com.huskycode.jpaquery.persister.store.PropogatedValueStore;
import com.huskycode.jpaquery.populator.CannotSetValueException;
import com.huskycode.jpaquery.populator.RandomValuePopulator;
import com.huskycode.jpaquery.populator.RandomValuePopulatorImpl;
import com.huskycode.jpaquery.populator.ValuesPopulator;
import com.huskycode.jpaquery.populator.ValuesPopulatorImpl;
import com.huskycode.jpaquery.types.tree.CreationPlan;
import com.huskycode.jpaquery.types.tree.EntityNode;
import com.huskycode.jpaquery.types.tree.PersistedResult;

/**
 * @author Varokas Panusuwan
 */
public class PersisterImpl implements Persister {
	private EntityManager em;
	private BeanCreator beanCreator;
    private RandomValuePopulator randomValuePopulator;
    private DependenciesDefinition deps;
    
    private ValuesPopulator valuesPopulator = ValuesPopulatorImpl.getInstance();
	
	private PersisterImpl(EntityManager em, DependenciesDefinition deps) {
		this.em = em;
		this.deps = deps;
	}
	
	//VisibleForTesting
	PersisterImpl(EntityManager em, BeanCreator beanCreator,
			RandomValuePopulator randomValuePopulator, 
			DependenciesDefinition deps) {
		this(em, deps);
		this.beanCreator = beanCreator;
		this.randomValuePopulator = randomValuePopulator;
	}
	
	public static PersisterImpl newInstance(EntityManager em, DependenciesDefinition deps) {
		PersisterImpl persisterImpl = new PersisterImpl(em, deps);
		persisterImpl.beanCreator = new BeanCreator();
		persisterImpl.randomValuePopulator = new RandomValuePopulatorImpl();
		return persisterImpl;
	}

    @Override
    public PersistedResult persistValues(CreationPlan plan) {
        List<Object> objects = new ArrayList<Object>();

        PropogatedValueStore valueStore = new PropogatedValueStore();
        
        for (EntityNode node : plan.getPlan()) {
        	Class<?> c = node.getEntityClass();
            Object obj = beanCreator.newInstance(c);
            randomValuePopulator.populateValue(obj);
            
            Field idField = findIdField(c);

            Map<Field, Object> valuesToPopulate = valueStore.get(node);
            if(idField != null) {
            	valuesToPopulate.put(idField, null); //Reset id field to null for JPA to autogen id.
            }
            
            valuesPopulator.populateValue(obj, valuesToPopulate);
            
            em.persist(obj);
            
            objects.add(obj);
            storeFieldValueToPopulate(obj, node, valueStore);
        }

        return PersistedResult.newInstance(objects);
    }
    
    private void storeFieldValueToPopulate(Object obj, EntityNode parent,
			PropogatedValueStore valueStore) {
		for (EntityNode child : parent.getChilds()) {
			List<Link<?,?,?>> links = deps.getDependencyLinks(child.getEntityClass(), parent.getEntityClass());
			for (Link<?,?,?> link : links) {
				Field parentField = link.getTo().getField();
				valueStore.putValue(child, link.getFrom().getField(), getValue(obj, parentField));
			}
		}
	}
    
    private Object getValue(Object obj, Field field) {
    	field.setAccessible(true);
		try {
			return field.get(obj);
		} catch (IllegalArgumentException e) {
			throw new CannotSetValueException(e);
		} catch (IllegalAccessException e) {
			throw new CannotSetValueException(e);
		}
    }
	
	private Field findIdField(Class<?> entityClass) {
		Field[] fields = entityClass.getDeclaredFields();
		for(Field field : fields) {
			if(field.getAnnotation(Id.class) != null) {
				return field;
			}
		}
		return null;
	}
}

@@ -2210,16 +2210,26 @@
         
+%0A        %09
 Object o
@@ -2237,86 +2237,153 @@
 j = 
-beanCreator.newInstance(c);%0A            random
+createNodeInDatabase(valueStore, node, c);%0A            %0A            objects.add(obj);%0A            storeField
 Value
+To
 Populat
-or.populateValue(obj
+e(obj, node, valueStore
 );%0A 
@@ -2381,36 +2381,34 @@
 Store);%0A        
-  
-  
+%7D%0A
 %0A            Fie
@@ -2404,454 +2404,617 @@
     
-    Field idField = findIdField(c
+return PersistedResult.newInstance(objects
 );%0A
-%0A 
     
-       Map%3CField, Object%3E valuesToPopulate = valueStore.get(node);%0A            if(idField != null) %7B%0A            %09valuesToPopulate.put(idField, null); //Reset id field to null for JPA to autogen id.%0A            %7D%0A            %0A            valuesPopulator.populateValue(obj, valuesToPopulate);%0A            %0A            em.persist(obj);%0A            %0A            objects.add(obj);%0A            storeFieldV
+%7D%0A%0A%09private Object createNodeInDatabase(PropogatedValueStore valueStore,%0A%09%09%09EntityNode node, Class%3C?%3E c) %7B%0A%09%09Object obj = beanCreator.newInstance(c);%0A%09%09%0A%09%09Map%3CField, Object%3E valuesToPopulate = getValuesToOverride(%0A%09%09%09%09valueStore, node, c);%0A%09%09%0A%09%09randomValuePopulator.populateValue(obj);%0A%09%09valuesPopulator.populateValue(obj, valuesToPopulate);%0A%09%09%0A%09%09em.persist(obj);%0A%09%09return obj;%0A%09%7D%0A%0A%09private Map%3CField, Object%3E getValuesToOverride(%0A%09%09%09PropogatedValueStore valueStore, EntityNode node, Class%3C?%3E c) %7B%0A%09%09Field idField = findIdField(c);%0A%0A%09%09Map%3CField, Object%3E v
 alue
+s
 ToPo
@@ -3023,101 +3023,177 @@
 late
-(obj, node, valueStore);%0A        %7D%0A%0A        return PersistedResult.newInstance(objects);%0A    
+ = valueStore.get(node);%0A%09%09if(idField != null) %7B%0A%09%09%09valuesToPopulate.put(idField, null); //Reset id field to null for JPA to autogen id.%0A%09%09%7D%0A%09%09return valuesToPopulate;%0A%09
 %7D%0A  

refactor persister into smaller methods