package org.squirrelframework.foundation.component;

import java.lang.reflect.Constructor;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.squirrelframework.foundation.util.ReflectUtils;
import org.squirrelframework.foundation.util.TypeReference;

/**
 * Central factory class for components used by squirrel-foundation.
 * 
 * @author Henry.He
 *
 */
public class SquirrelProvider implements SquirrelSingleton {

    private static SquirrelProvider instance = new SquirrelProvider();

    public static SquirrelProvider getInstance() {
        return instance;
    }

    public static void setInstance(SquirrelProvider instance) {
        SquirrelProvider.instance = instance;
    }

    private Map<Class<?>, Class<?>> implementationRegistry = new ConcurrentHashMap<Class<?>, Class<?>>();

    public <T> T newInstance(TypeReference<T> typeRef) {
        return newInstance(typeRef, null, null);
    }
    
    public <T> T newInstance(TypeReference<T> typeRef, Class<?>[] argTypes, Object[] args) {
        Class<T> clz = typeRef.getRawType();
        return clz.cast(newInstance(clz, argTypes, args));
    }
    
    /**
     * Create a new instance of the requested class using the internal registry.
     * @param clz class of new instance
     * @param <T> type of new instance
     * @return new instance
     */
    public <T> T newInstance(Class<T> clz) {
        return newInstance(clz, null, null);
    }
    
    /**
     * Create a new instance of the requested class using the internal registry.
     * @param clz class of new instance
     * @param argTypes arguments type of new instance constructor
     * @param args arguments of new instance constructor
     * @param <T> type of new instance
     * @return new instance
     */
    public <T> T newInstance(Class<T> clz, Class<?>[] argTypes, Object[] args) {
        Class<T> implementationClass = getImplementation(clz);
        if (args == null) {
            return postProcess(clz, ReflectUtils.newInstance(implementationClass));
        }
        Constructor<T> constructor = ReflectUtils.getConstructor(implementationClass, argTypes);
        return postProcess(clz, ReflectUtils.newInstance(constructor, args));
    }
    
    private <T> T postProcess(Class<T> clz, T component) {
        SquirrelPostProcessor<T> postProcessor = 
                SquirrelPostProcessorProvider.getInstance().getPostProcessor(clz);
        if(postProcessor!=null && component!=null) {
            postProcessor.postProcess(component);
        }
        return component;
    }

    /**
     * Register the implementation class for a certain class. Note, if there is already an entry in the registry for
     * the class, then it will be overwritten.
     *
     * @param clazz register class
     * @param implementationClass implementation class of register class
     */
    public void register(Class<?> clazz, Class<?> implementationClass) {
        // TODO: handle the case that there is already an entry...
        implementationRegistry.put(clazz, implementationClass);
    }
    
    public void unregister(Class<?> clazz) {
        implementationRegistry.remove(clazz);
    }
    
    public void clearRegistry() {
        implementationRegistry.clear();
    }

    /**
     * Return the current registered implementation. 
     * If class has register a implementation class, return register implementation class. If register class or implement 
     * class is an interface, try to find corresponding implementation class over naming convention. 
     * (implementation class simple name = interface class simple name + "Impl") First try to find the implementation class 
     * with conventional naming under the same package as interface class. If still not exist, try to find implementation class 
     * in (interface class package + ".impl").
     * 
     * @param clz registered class
     * @return current registered implementation
     */
    public <T> Class<T> getImplementation(Class<T> clz) {
        return resolveImplIfInterface(clz, new HashSet<Class<?>>());
    }

    private <T> Class<T> resolveImplIfInterface(Class<T> clz, Set<Class<?>> visited) {
        if (!visited.add(clz)) {
            throw new IllegalStateException("Registration cycles: " + visited);
        }

        if (!clz.isInterface()) {
            Class<T> possibleImpl = fromRegistry(clz);
            if(possibleImpl!=null && !possibleImpl.isInterface()) 
                clz = possibleImpl;
            return clz;
        }

        Class<T> possibleImpl = fromRegistry(clz);
        if (possibleImpl == null) {
            possibleImpl = findImplementationClass(clz);
            // We only register actual implementations so cannot introduce
            // cycles through this...
            register(clz, possibleImpl);
        }

        return resolveImplIfInterface(possibleImpl, visited);
    }

    private <T> Class<T> fromRegistry(Class<T> clz) {
        @SuppressWarnings("unchecked")
        Class<T> impl = (Class<T>) implementationRegistry.get(clz);
        return impl;
    }

    // find implementation class name according to programming convention
    @SuppressWarnings("unchecked")
    private <T> Class<T> findImplementationClass(Class<T> interfaceClass) {
        Class<?> implementationClass = null;
        String implClassName = interfaceClass.getName() + "Impl";
        try {
            implementationClass = Class.forName(implClassName);
        } catch (ClassNotFoundException e) {
            implClassName = ReflectUtils.getPackageName(interfaceClass.getName())+
                    ".impl."+interfaceClass.getSimpleName()+"Impl";
            implementationClass = ReflectUtils.getClass(implClassName);
        }
        return (Class<T>) implementationClass;
    }
}
