/* 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.deploy;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jfox.framework.Component;
import org.jfox.framework.ComponentContext;
import org.jfox.framework.ComponentException;
import org.jfox.framework.ComponentMeta;
import org.jfox.framework.ComponentMetaFactory;
import org.jfox.framework.ComponentName;
import org.jfox.framework.ComponentRefParameter;
import org.jfox.framework.ImplemetationRefParameter;
import org.jfox.framework.InstanceParameter;
import org.jfox.framework.Parameter;
import org.jfox.framework.ParameterPack;
import org.jfox.framework.Registry;
import org.jfox.framework.extra.InstantiatingInterceptable;
import org.jfox.framework.loader.LocalClassLoader;
import org.jfox.framework.util.Classes;


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

public class ComponentDeployer implements InstantiatingInterceptable, Deployer {
    private Registry registry;

    // module => ComponentNames
    private Map modules = new HashMap();

    public Component afterInstantiate(Component theComp, Registry registry) {
        this.registry = registry;
        return theComp;
    }

    public Registry getRegistry() {
        return registry;
    }

    public void deploy(Deployable comp) throws DeployException {
        if(!(comp instanceof Module)) {
            throw new DeployException("can not deploy " + comp + ", it's not a instance of" + Module.class.getName());
        }
        Module module = (Module) comp;
        ModuleDescriptor md = module.getModuleDescriptor();

        ComponentDescriptor[] cds = md.getComponentDescriptors();
        List names = new ArrayList(cds.length);
        for(int i = 0; i < cds.length; i++) {
            ComponentName name = deploy(((LocalClassLoader) module.getClassLoader()), cds[i], module.getTempDeployDirectory());
            names.add(name);
        }
        modules.put(module, names);
    }

    public void undeploy(Deployable comp) throws DeployException {
        if(!(comp instanceof Module)) {
            throw new DeployException("can not undeploy " + comp + ", it's not a instance of" + Module.class.getName());
        }
        Module module = (Module) comp;
        if(!modules.containsKey(module)) return;
        List names = (List) modules.get(module);

        for(int i = names.size() - 1; i >= 0; i--) {
            ComponentName name = (ComponentName) names.get(i);
            try {
                registry.unregisterComponent(name);
            }
            catch(ComponentException e) {
                throw new DeployException("undeploy " + name + " faild.", e);
            }
        }
        modules.remove(module);
    }

    private ComponentName deploy(LocalClassLoader cl, ComponentDescriptor cd, File moduleDeployDir) throws DeployException {
        String description = cd.getDescription();
        String compImpl = cd.getImplementation();
        String factor = cd.getFactor();
        boolean singleton = cd.isSingleton();
        boolean typeSingleton = cd.isTypeSingleton();
        int priority = cd.getPriority();
        ConstructorDescriptor constructorDesc = cd.getConstructorDescriptor();
        PropertyDescriptor[] propertyDesces = cd.getPropertyDescriptors();

        try {
            ComponentMeta meta = ComponentMetaFactory.getComponentMeta(ComponentName.parseString(compImpl + "@" + factor, cl),
                    buildConstrParameter(cl, constructorDesc),
                    buildPropertyParameterPacks(cl, propertyDesces),
                    typeSingleton,
                    singleton);
            meta.setPriority(priority);
            meta.setDescription(description);
            meta.setModule(cd.getModule());
            meta.setModuleDir(moduleDeployDir.getPath());
            registry.registerComponent(meta);
            return meta.getComponentName();
        }
        catch(Exception e) {
            throw new DeployException(e);
        }
    }

    private Parameter[] buildConstrParameter(ClassLoader cl, ConstructorDescriptor cd) throws ComponentException {
        if(cd == null) {
            // null will case the InstantiatingComponentFactory auto detect constructor
            return null;
        }
        ParameterDescriptor[] pds = cd.getParamDescriptors();
        Parameter[] params = new Parameter[pds.length];
        for(int i = 0; i < pds.length; i++) {
            params[i] = buildParameter(cl, pds[i]);
        }
        return params;
    }

    private ParameterPack[] buildPropertyParameterPacks(ClassLoader cl, PropertyDescriptor[] pds) throws ComponentException {
        ParameterPack[] ppps = new ParameterPack[pds.length];
        for(int i = 0; i < pds.length; i++) {
            ppps[i] = buildPropertyParameterPack(cl, pds[i]);
        }
        return ppps;
    }

    private ParameterPack buildPropertyParameterPack(ClassLoader cl, PropertyDescriptor pd) throws ComponentException {
        String name = pd.getName();
        ParameterDescriptor[] pds = pd.getParamDescriptors();
        Parameter param = buildParameter(cl, pds[0]);
        return new ParameterPack(name, param);
    }

    private Parameter buildParameter(ClassLoader cl, ParameterDescriptor pd) throws ComponentException {
        String ref = pd.getRef();
        if(ref != null && !ref.equals("")) {
            if(ref.indexOf("@") > 0) {
                return new ComponentRefParameter(ComponentName.parseString(ref, cl));
            }
            else {
                try {
                    return new ImplemetationRefParameter(cl.loadClass(ref));
                }
                catch(Exception e) {
                    throw new ComponentException("build parameter error.", e);
                }
            }
        }
        String type = pd.getType();
        String value = pd.getValue();
        try {
            Class typeClass = null;
            if(Classes.isPrimitiveClass(type)) {
                typeClass = Classes.loadPrimitiveClass(type);
            }
            else {
                typeClass = cl.loadClass(type);
            }

            Object param = Classes.newObject(typeClass, value);
            return new InstanceParameter(param, typeClass);
        }
        catch(Exception e) {
            throw new ComponentException("build parameter error.", e);
        }
    }

    public void setComponentContext(ComponentContext ctx) {

    }

    public static void main(String[] args) {

    }
}

