/* 
 * This file is part of the HyperGraphDB source distribution. This is copyrighted 
 * software. For permitted uses, licensing options and redistribution, please see  
 * the LicensingInformation file at the root level of the distribution.  
 * 
 * Copyright (c) 2005-2010 Kobrix Software, Inc.  All rights reserved. 
 */
package org.hypergraphdb.type;

import java.util.ArrayList;
import java.util.List;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import org.hypergraphdb.HGHandle;
import org.hypergraphdb.HGException;
import org.hypergraphdb.HGLink;
import org.hypergraphdb.HGPersistentHandle;
import org.hypergraphdb.HyperGraph;
import org.hypergraphdb.HGQuery.hg;
import org.hypergraphdb.util.AccessibleObjectCache;


/**
 * 
 * <p>
 * The <code>JavaTypeFactory</code> is used to infer HyperGraph types based on
 * Java beans. Java bean classes are converted to instances of
 * <code>RecordType</code> and Java bean instances to the corresponding
 * <code>Record</code>s. Only properties that are both readable and writeable
 * are mapped to HyperGraph <code>Slot</code>s.
 * </p>
 * 
 * @author Borislav Iordanov
 */
public class JavaTypeFactory implements JavaTypeMapper
{
	protected HyperGraph graph;
	static private AccessibleObjectCache accessibleObjectCache = new AccessibleObjectCache();
	private ArrayList<JavaTypeMapper> mappers = new ArrayList<JavaTypeMapper>();
	private JavaObjectMapper objectMapper = null;
	private DefaultJavaTypeMapper defaultMapper = null;
		
	public JavaTypeFactory()
	{
		mappers.add(defaultMapper = new DefaultJavaTypeMapper());
		mappers.add(objectMapper = new JavaObjectMapper());
	}

//	public void initNonDefaultMappers()
//	{
//		objectMapper = new JavaObjectMapper();
//		objectMapper.setHyperGraph(graph);
//		mappers.add(0, objectMapper);
//	}
	
	void assign(Object bean, String property, Object value) 
	{
		BonesOfBeans.setProperty(bean, property, value);
	}

	public static void assignPrivate(Class<?> clazz, Object x, String field, Object value)
	{
		Field f = accessibleObjectCache.getField(clazz, field);
		try
		{
			f.set(x, value);
		}
		catch (IllegalAccessException ex)
		{
			throw new HGException(ex);
		}
	}
	
	public static Object retrievePrivate(Class<?> clazz, Object x, String field)
	{
		Field f = accessibleObjectCache.getField(x.getClass(), field);
		try
		{
			return f.get(x);
		}
		catch (IllegalAccessException ex)
		{
			throw new HGException(ex);
		}
	}

	public static boolean isAbstract(Class<?> c)
	{
		return Modifier.isAbstract(c.getModifiers()) || 
		  	   Modifier.isInterface(c.getModifiers());
	}
	
	public static Constructor<?> findDefaultConstructor(Class<?> c)
	{
        for (Constructor<?> con : c.getDeclaredConstructors())
            if (con.getParameterTypes().length == 0)
                return con;
        return null;	    
	}
	
	public static boolean isDefaultConstructible(Class<?> c)
	{
		try 
		{
			c.getConstructor(new Class[0]);
			return true;
		}
		catch (NoSuchMethodException ex) 
		{
			return findDefaultConstructor(c) != null;
		}		
	}

	public static Field findDeclaredField(Class<?> c, String name) throws SecurityException
	{
	    try
	    {
	        return c.getDeclaredField(name);
	    }
	    catch (NoSuchFieldException ex)
	    {
	        return (c.getSuperclass() == null) ? null : findDeclaredField(c.getSuperclass(), name);
	    }
	}
	
	public static Constructor<?> findHandleArgsConstructor(Class<?> c)
	{
	    for (Constructor<?> con : c.getDeclaredConstructors())
	    {
	    	if (con.getParameterTypes().length == 0)
	    		continue;
	        boolean match = true;
	        for (Class<?> paramClass : con.getParameterTypes())
	            if (!HGHandle.class.isAssignableFrom(paramClass))
	            {
	                match = false;
	                break;
	            }
	        if (match)
	            return con;
	    }
	    return null;
	}
	
	public static boolean isLink(Class<?> c)
	{
	    boolean b = HGLink.class.isAssignableFrom(c);
	    if (!b)
	        return false;
		try 
		{
			c.getDeclaredConstructor(new Class[] { HGHandle[].class });
			return true;
		}
		catch (NoSuchMethodException ex) 
		{
			return findHandleArgsConstructor(c) != null;
		}		
	}
	
	public static boolean isHGInstantiable(Class<?> c)
	{
		return !isAbstract(c) && (isDefaultConstructible(c) || isLink(c));
	}
	
	public static HGHandle getSlotHandle(HyperGraph graph, String label, HGHandle type)
	{
		HGHandle slotHandle = hg.findOne(graph, hg.eq(new Slot(label,type)));
		if (slotHandle == null)
			return graph.add(new Slot(label, type));
		else
			return slotHandle;
	}
	
	static HGHandle superSlot = null;	
    static HGHandle getSuperSlot(HyperGraph graph)
    {
        if (superSlot == null)
            superSlot = JavaTypeFactory.getSlotHandle(
                    graph,
                    "!super", 
                    graph.getTypeSystem().getTypeHandle(HGPersistentHandle.class));
        return superSlot;
    }   
    
	public HGAtomType defineHGType(Class<?> javaClass, HGHandle typeHandle)
	{
		for (JavaTypeMapper m : mappers)
		{
			HGAtomType t = m.defineHGType(javaClass, typeHandle);
			if (t != null)
				return t;
		}
		return null;
	}

	public HGAtomType getJavaBinding(HGHandle typeHandle, HGAtomType hgType, Class<?> javaClass)
	{
		for (JavaTypeMapper m : mappers)
		{
			HGAtomType t = m.getJavaBinding(typeHandle, hgType, javaClass);
			if (t != null)
				return t;
		}
		return null;
	}	
	
	public void setHyperGraph(HyperGraph graph)
	{
		this.graph = graph;
		for (JavaTypeMapper m : mappers)
			m.setHyperGraph(graph);
	}	
	
	public DefaultJavaTypeMapper getDefaultJavaMapper()
	{
		return defaultMapper;
	}
	
	public JavaObjectMapper getJavaObjectMapper()
	{
		return objectMapper;
	}
	
	public List<JavaTypeMapper> getMappers()
	{
		return mappers;
	}
}