/* JFox, the OpenSource J2EE Application Server
 *
 * Distributable under GNU LGPL license by gun.org
 * more details please visit http://www.huihoo.org/jfox
 */

package org.jfox.framework;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.Arrays;

import org.jfox.framework.extra.InstantiatingInterceptable;
import org.jfox.framework.util.Classes;

/**
 * @author <a href="mailto:young_yy@hotmail.com">Young Yang</a>
 */

public abstract class InstantiatingComponentFactory extends ComponentFactorySupport {
    private Parameter[] constrParams;

    private Class implementation;
    private ComponentName compName;
    private Registry registry;

    private boolean instantiating = false;
    private Constructor constructor;

    public InstantiatingComponentFactory(Class implementation, Parameter[] constrParams) throws ComponentException {
        this.constrParams = constrParams;
        if(implementation == null) {
            throw new ComponentException("component implemetation is null.");
        }
        checkImplementation(implementation);
        this.implementation = implementation;
        setComponentName(ComponentName.newInstance(implementation));
    }

    protected InstantiatingComponentFactory(ComponentName name, Parameter[] constrParams) throws ComponentException {
        this.constrParams = constrParams;
        this.implementation = name.getImplementation();
        checkImplementation(implementation);
        setComponentName(name);
    }

    /**
     * when not defined ConstructorComponentParameterPack, mean constrParams is null,
     * the Component must has only one constructor
     * <p/>
     * if want to use the Constructor with no parameter, please use
     * new ConstructorComponentParameterPack(new ComponentParameter[0]);
     *
     * @return
     * @throws ComponentException
     */
    public Component makeComponent() throws ComponentException {
        if(instantiating == true) {
            throw new ComponentException("cycle depencies in component " + getImplementation().getName());
        }
        instantiating = true;

        try {
            if(constructor == null) {
                constructor = getContructor();
            }
            Parameter[] packs = getParameters();
            Object[] params = new Object[]{};
            if(packs != null) {
                params = parseParameters(packs);
            }
            Component comp = instantiateComponent(constructor, params);
            if(comp instanceof InstantiatingInterceptable) {
                comp = ((InstantiatingInterceptable) comp).afterInstantiate(comp, getRegistry());
            }
            return comp;
        }
        finally {
            instantiating = false;
        }

    }

    public ComponentName getComponentName() {
        return compName;
    }

    protected void setComponentName(ComponentName compName) {
        this.compName = compName;
    }

    public Class getImplementation() {
        return implementation;
    }

    public Registry getRegistry() {
        return registry;
    }

    public void setRegistry(Registry registry) {
        this.registry = registry;
    }

    public Parameter[] getParameters() {
        return constrParams;
    }

    public String toString() {
        StringBuffer paramString = new StringBuffer("(");
        if(constrParams != null) {
            for(int i = 0; i < constrParams.length; i++) {
                if(i > 0) paramString.append(", ");
                paramString.append(constrParams[i].toString());
            }
        }
        paramString.append(")");

        String constructor = getImplementation().getName();
        int index = constructor.lastIndexOf(".");
        if(index > 0) {
            constructor = constructor.substring(index + 1);
        }
        return getClass().getName() + "{" + constructor + paramString + "}";
    }

    protected Parameter[] buildComponentParameterPackByType(Class[] types) throws ComponentException {
        Parameter[] compParams = new Parameter[types.length];
        for(int i = 0; i < types.length; i++) {
            ImplemetationRefParameter ref = new ImplemetationRefParameter(types[i]);
            compParams[i] = ref;
        }
        return compParams;
    }

    private static void checkImplementation(Class implementation) throws ComponentException {
        if(!Component.class.isAssignableFrom(implementation)) {
            throw new ComponentException("component implementation " + implementation.getName() + " is not a valid component, because it not implements " + Component.class.getName());
        }

        // Assert that the component class is concrete.
        boolean isAbstract = (implementation.getModifiers() & Modifier.ABSTRACT) == Modifier.ABSTRACT;
        boolean isPrivate = (implementation.getModifiers() & Modifier.PRIVATE) == Modifier.PRIVATE;
        boolean isProtected = (implementation.getModifiers() & Modifier.PROTECTED) == Modifier.PROTECTED;
        if(implementation.isInterface() || isAbstract) {
            throw new ComponentException("component implementation is not a concrete class " + implementation.getName());
        }
        if(isPrivate || isProtected) {
            throw new ComponentException("component implementation is a private or protected class " + implementation.getName());
        }
    }

    private Constructor getContructor() throws ComponentException {
        // not specified, must have only 1 constructor
        // then, auto build the ParamPack
        if(constrParams == null) {
            Constructor[] constructors = implementation.getConstructors();
            if(constructors.length != 1) {
                throw new ComponentException("implemetation " + implementation.getName() + " has many constructors, but you not define by ConstructorParameterPack which to use.");
            }
            Constructor constructor = constructors[0];
            Class[] types = constructor.getParameterTypes();

            // then build the ParamPack and set it
            Parameter[] params = buildComponentParameterPackByType(types);
            this.constrParams = params;
            return constructor;
        }
        else {
            Class[] types = parseParamTypes(constrParams);
            try {
                Constructor constructor = Classes.getConstructor(implementation, types);
                return constructor;
            }
            catch(Exception e) {
                throw new ComponentException("get " + implementation.getName() + "'s constructor " + Arrays.asList(types) + " error", e);
            }
        }
    }

    protected abstract Component instantiateComponent(Constructor constructor, Object[] params) throws ComponentException;

}

