/* 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.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.jfox.framework.extra.ActiveComponent;
import org.jfox.framework.extra.Destroyable;
import org.jfox.framework.extra.Initializable;
import org.jfox.framework.extra.Servicable;
import org.jfox.framework.loader.ClassLoaderRepository;
import org.jfox.framework.logger.Logger;

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

public class Registry {
    /**
     * ComponentName => ComponentMeta
     */
    private Map components = new LinkedHashMap();
    /**
     * Component implemetation class => count
     */
    private Map compCounts = new HashMap();

    private boolean started = false;

    private Logger logger = Logger.getLogger(Registry.class.getName());

    private ClassLoaderRepository loaderRepo = new ClassLoaderRepository(new URL[0], Thread.currentThread().getContextClassLoader());

    private static final Registry me = new Registry();

    public static Registry getInstance() {
        return me;
    }

    private Registry() {
        Thread.currentThread().setContextClassLoader(loaderRepo);
    }

    public synchronized ComponentMeta registerComponent(Component component) throws ComponentException {
        ComponentMeta meta = ComponentMetaFactory.getComponentMeta(component);
        doRegister(meta);
        return meta;
    }

    public synchronized ComponentMeta registerComponent(Component component, boolean typeSingleton) throws ComponentException {
        ComponentMeta meta = ComponentMetaFactory.getComponentMeta(component, typeSingleton);
        doRegister(meta);
        return meta;
    }


    public synchronized ComponentMeta registerComponent(ComponentMeta componentMeta) throws ComponentException {
        doRegister(componentMeta);
        return componentMeta;
    }

    public synchronized ComponentMeta registerComponent(Class implementation) throws ComponentException {
        ComponentMeta meta = ComponentMetaFactory.getComponentMeta(implementation, null, null);
//    ComponentMeta meta = ComponentMeta.newInstance(new ConstructorComponentFactory(implementation,null));
        registerComponent(meta);
        return meta;
    }

    public synchronized ComponentMeta registerComponent(Class implementation, Parameter[] constrParams) throws ComponentException {
        ComponentMeta meta = ComponentMetaFactory.getComponentMeta(implementation, constrParams, null);
//    ComponentMeta meta = ComponentMeta.newInstance(new PropertyComponentFactory(new ConstructorComponentFactory(implementation,constrParams),propParamPacks));
        registerComponent(meta);
        return meta;
    }

    public synchronized ComponentMeta registerComponent(Class implementation, Parameter[] constrParams, ParameterPack[] propParamPacks) throws ComponentException {
        ComponentMeta meta = ComponentMetaFactory.getComponentMeta(implementation, constrParams, propParamPacks);
//    ComponentMeta meta = ComponentMeta.newInstance(new PropertyComponentFactory(new ConstructorComponentFactory(implementation,constrParams),propParamPacks));
        registerComponent(meta);
        return meta;
    }

    public synchronized ComponentMeta registerComponent(Class implementation, Parameter[] constrParams, ParameterPack[] propParamPacks, boolean typeSingleton, boolean singleton) throws ComponentException {
        ComponentMeta meta = ComponentMetaFactory.getComponentMeta(implementation, constrParams, propParamPacks, typeSingleton, singleton);
//    ComponentMeta meta = ComponentMeta.newInstance(new PropertyComponentFactory(new ConstructorComponentFactory(implementation,constrParams),propParamPacks));
        registerComponent(meta);
        return meta;
    }

    public synchronized ComponentMeta getComponentMeta(ComponentName name) {
        return (ComponentMeta) components.get(name);
    }

    /**
     * if user reference a component by "class_name@" or "class_name@*",it means want to
     * get first ComponentMeta by the class_name
     *
     * @param name
     * @return
     * @throws ComponentException
     */
    public synchronized Component getComponentInstance(ComponentName name) throws ComponentException {
        ComponentMeta meta = null;
        if(!isRegistered(name)) {
            throw new ComponentException("component " + name + " not registered.");
        }
        meta = getComponentMeta(name);

        Component comp = meta.getComponentInstance();
        return comp;
    }

    /**
     * unregister a component and stop & destroy it if needed
     *
     * @param name
     * @return
     * @throws ComponentException
     */
    public synchronized ComponentMeta unregisterComponent(ComponentName name) throws ComponentException {
        logger.info("unregister component " + name);
        if(isRegistered(name)) {
            ComponentMeta meta = (ComponentMeta) components.remove(name);

            int count = ((Integer) compCounts.get(meta.getComponentImplementation())).intValue() - 1;
            if(count == 0) {
                compCounts.remove(meta.getComponentImplementation());
            }
            else {
                compCounts.put(meta.getComponentImplementation(), new Integer(count));
            }
            // only stop & destroy singlton component, otherwise, must done by yourself
            if(meta.isSingleton()) {
                Component comp = meta.getComponentInstance();
                stopComponent(meta, comp);
                destroyComponent(meta, comp);
            }
            return meta;
        }
        return null;
    }

    public synchronized int countComponents() {
        return components.size();
    }

    public synchronized int countComponentTypes() {
        return compCounts.size();
    }

    public synchronized boolean isRegisteredImplemetation(Class implementation) {
        return compCounts.containsKey(implementation);
    }

    public boolean isRegistered(ComponentName name) {
        return components.containsKey(name);
    }

    public ComponentName[] getAllComponentNames() {
        List compNames = new ArrayList(components.keySet());
        return (ComponentName[]) compNames.toArray(new ComponentName[compNames.size()]);
    }

    public ComponentMeta[] getAllComponentMetas() {
        List comps = new ArrayList(components.values());
        return (ComponentMeta[]) comps.toArray(new ComponentMeta[comps.size()]);
    }

    /**
     * type can be a super class
     *
     * @param type
     * @return
     */
    public ComponentMeta[] getComponentMetasbyImplemetation(Class type) {
        List metas = new ArrayList();
        for(Iterator it = components.entrySet().iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry) it.next();
            if(type.isAssignableFrom(((ComponentName) entry.getKey()).getImplementation())) {
                metas.add(entry.getValue());
            }
        }
        return (ComponentMeta[]) metas.toArray(new ComponentMeta[metas.size()]);
//    return new ComponentMeta[0];
    }

    public synchronized ComponentMeta getFirstComponentMetabyImplemetation(Class implementaion) {
        for(Iterator it = components.values().iterator(); it.hasNext();) {
            ComponentMeta meta = (ComponentMeta) it.next();
            if(implementaion.isAssignableFrom(meta.getComponentImplementation())) {
                return meta;
            }
        }
        return null;
    }

    public synchronized void start() {
        if(started) return;
        List compMetas = new ArrayList(components.values());
        Collections.sort(compMetas);
        for(Iterator it = compMetas.iterator(); it.hasNext();) {
            ComponentMeta meta = (ComponentMeta) it.next();
            try {
                Component comp = meta.getComponentInstance();
                try {
                    initComponent(meta, comp);
                }
                catch(ComponentException e) {
                    logger.warn("init component error", e);
                }
                try {
                    startComponent(meta, comp);
                }
                catch(ComponentException e) {
                    logger.warn("start component error", e);
                }
            }
            catch(Exception e) {
                logger.warn("get component instance " + meta.getComponentName() + "failed.", e);
            }
        }
        started = true;
    }

    public synchronized void stop() {
        if(started == false) return;
        List compMetas = new ArrayList(components.values());
        Collections.sort(compMetas);
        Collections.reverse(compMetas);

        for(Iterator it = compMetas.iterator(); it.hasNext();) {
            ComponentMeta meta = (ComponentMeta) it.next();
            try {
                Component comp = meta.getComponentInstance();
                if(Servicable.class.isAssignableFrom(comp.getClass())) {
                    Servicable scomp = (Servicable) comp;
                    if(scomp.isStarted()) {
                        scomp.stop();
                    }
                }
                if(Destroyable.class.isAssignableFrom(comp.getClass())) {
                    Destroyable dcomp = (Destroyable) comp;
                    dcomp.destroy();
                }
            }
            catch(Exception e) {
                e.printStackTrace();
            }
        }
        started = false;
    }

    public synchronized boolean isStarted() {
        return started;
    }

    public Class loadClass(String impl) throws ClassNotFoundException {
        return loaderRepo.loadClass(impl);
    }

    public void addLibraryURL(URL url) {
        loaderRepo.addURL(url);
    }

    public ClassLoaderRepository getClassLoaderRepository() {
        return loaderRepo;
    }

    private synchronized void doRegister(ComponentMeta meta) throws ComponentException {
        ComponentName name = meta.getComponentName();
        logger.info("register component " + name);
        if(isRegistered(name)) {
            throw new ComponentException("Component " + name + " has already registered.");
        }
        else { // type singleton check
            if(meta.isTypeSingleton()) {
                if(isRegisteredImplemetation(meta.getComponentImplementation())) {
                    throw new ComponentException(meta + " is type singleton, but this type has already been registered.");
                }
                else {
                    if(isRegisteredImplemetation(meta.getComponentImplementation())) {
                        ComponentMeta lastMeta = getLastComponentMetabyImplemetation(name.getImplementation());
                        if(lastMeta.isTypeSingleton()) {
                            throw new ComponentException("a type singlton component with this type " + name.getImplementation() + " has already been registered.");
                        }
                    }
                }
            }
        }

        int count = 1;
        if(isRegisteredImplemetation(meta.getComponentImplementation())) {
            count += ((Integer) compCounts.get(meta.getComponentImplementation())).intValue();
        }
        compCounts.put(meta.getComponentImplementation(), new Integer(count));
        components.put(name, meta);
        // set registry
        meta.setRegistry(this);

        // instance the component immediately
        if(ActiveComponent.class.isAssignableFrom(meta.getComponentImplementation())) {
            getComponentInstance(name);
        }
    }

    public ComponentMeta getLastComponentMetabyImplemetation(Class implementaion) {
        List keys = new ArrayList(components.keySet());
        for(int i = keys.size() - 1; i >= 0; i--) {
            ComponentName name = (ComponentName) keys.get(i);
            if(name.getImplementation().isAssignableFrom(implementaion)) {
                return (ComponentMeta) components.get(name);
            }
        }
        return null;
    }

    void initComponent(ComponentMeta meta, Component comp) throws ComponentException {
        if(Initializable.class.isAssignableFrom(comp.getClass())) {
            Initializable icomp = ((Initializable) comp);
            if(!icomp.isInitialized()) {
                try {
                    logger.info("initializing component " + meta.getComponentName());
                    icomp.init();
                    logger.info("initialize component " + meta.getComponentName() + ", OK!");
                }
                catch(Exception e) {
                    logger.warn("initialize component " + meta.getComponentName() + " failed.", e);
                    throw new ComponentException(e);
                }
            }
        }
    }

    void startComponent(ComponentMeta meta, Component comp) throws ComponentException {
        // try to start component
        if(Servicable.class.isAssignableFrom(meta.getComponentImplementation())) {
            Servicable scomp = (Servicable) comp;
            try {
                if(!scomp.isStarted()) {
                    logger.info("starting component " + meta.getComponentName());
                    scomp.start();
                    logger.info("start component " + meta.getComponentName() + ", OK!");
                }
            }
            catch(Exception e) {
                logger.info("start component " + meta.getComponentName() + " failed.", e);
                throw new ComponentException(e);
            }
        }
    }

    void stopComponent(ComponentMeta meta, Component comp) throws ComponentException {
        if(comp instanceof Servicable) {
            try {
                Servicable scomp = ((Servicable) comp);
                if(scomp.isStarted()) {
                    logger.info("stoping component " + meta.getComponentName());
                    scomp.stop();
                    logger.info("stop component " + meta.getComponentName() + ", OK!");
                }
            }
            catch(Exception e) {
                logger.warn("stop component " + meta.getComponentName() + " failed.", e);
                throw new ComponentException(e);
            }
        }
    }

    void destroyComponent(ComponentMeta meta, Component comp) throws ComponentException {
        if(comp instanceof Destroyable) {
            try {
                Destroyable scomp = ((Destroyable) comp);
                logger.info("destroying component " + meta.getComponentName());
                scomp.destroy();
                logger.info("destroy component " + meta.getComponentName() + ", OK!");
            }
            catch(Exception e) {
                logger.warn("destroy component " + meta.getComponentName() + " failed.", e);
                throw new ComponentException(e);
            }
        }
    }


    public static void main(String[] args) {

    }
}

