/* 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.io.FileFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.jfox.framework.ComponentContext;
import org.jfox.framework.Registry;
import org.jfox.framework.extra.Servicable;
import org.jfox.framework.logger.Logger;
import org.jfox.framework.util.FileFilters;
import org.jfox.framework.util.Files;

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

public class DeployMonitor implements Servicable, Runnable {
    private boolean started = false;

    // default use components dir as component deploy dir
    private String moduleDir = System.getProperty("jfox.home", "..") + "/modules";
    private ComponentDeployer deployer = null;
    // Modue dir => Module
    private Map deployments = new HashMap();

    // module dirs that faild to deployed
    private List failedModules = new ArrayList();

    private static Logger logger = Logger.getLogger(DeployMonitor.class.getName());

    public DeployMonitor(ComponentDeployer deployer) {
        this.deployer = deployer;
    }

    public DeployMonitor(ComponentDeployer deployer, String componentsDir) {
        this.deployer = deployer;
        if(componentsDir != null && componentsDir.equals("")) {
            this.moduleDir = componentsDir;
        }
    }

    /**
     * start to monitor the Component directory
     *
     * @throws Exception
     */
    public void start() throws Exception {
        started = true;
        Thread t = new Thread(this, "DeployMonitor");
        t.setPriority(Thread.MIN_PRIORITY);
        t.start();
    }

    /**
     * stop monitor
     *
     * @throws Exception
     */
    public void stop() throws Exception {
        started = false;
    }

    /**
     * add jars in modules/lib to framework ClassLoaderRepo
     *
     * @param moduleDir
     */
    private void loadLibs(File moduleDir) {
        File libDir = new File(moduleDir, "lib");
        if(!libDir.exists() || libDir.isFile()) return;
        Registry registry = deployer.getRegistry();

        List jarFiles = Files.listFiles(libDir, FileFilters.suffixFileFilter(new String[]{"jar", "zip"}));
        for(int i = 0; i < jarFiles.size(); i++) {
            File file = (File) jarFiles.get(i);
            try {
                registry.addLibraryURL(file.toURL());
            }
            catch(Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void run() {
        File dir = new File(moduleDir);
        if(!dir.exists()) {
            logger.warn("the modules directory \"" + dir.toString() + "\" not exist, deploy monitor stop.");
            return;
        }

        // load jars in modules/lib dir
        loadLibs(dir);

        while(isStarted()) {
//      List moduleDirs = new ArrayList(Arrays.asList(dir.listFiles(Files.directoryFileFilter(new String[]{"lib"}))));
            List moduleDirs = new ArrayList(Arrays.asList(dir.listFiles(FileFilters.and(new FileFilter[]{
                FileFilters.directoryFileFilter(),
                FileFilters.not(FileFilters.nameFileFilter("lib")),
                FileFilters.not(FileFilters.nameFileFilter("CVS"))}))));
            Map modules = new HashMap(moduleDirs.size());
            for(Iterator it = moduleDirs.iterator(); it.hasNext();) {
                File moduleDir = (File) it.next();
                if(deployments.containsKey(moduleDir)) { // deployed
                    it.remove();
                    modules.put(moduleDir, deployments.remove(moduleDir));
                }
            }

            // the rest of the deployments must be undeployed
            for(Iterator it = deployments.values().iterator(); it.hasNext();) {
                Module module = (Module) it.next();
                logger.info("undeploying module " + module + " ...");
                try {
                    undeploy(module);
                    logger.info("undeploy module " + module + ", OK!");
                }
                catch(DeployException e) {
                    logger.info("undeploy module " + module + " failed.", e);
                }
            }
            deployments.clear();

            // load newModule Dirs
            List newModules = new ArrayList(moduleDirs.size());
            for(int i = 0; i < moduleDirs.size(); i++) {
                File moduleDir = (File) moduleDirs.get(i);
                if(failedModules.contains(moduleDir)) {
                    logger.warn("module " + moduleDir.toString() + " has been deployed failed, skip!!!");
                }
                else {
                    logger.debug("parsing module " + moduleDir.toString() + " ...");

                    try {
                        Module module = new Module(moduleDir, deployer.getRegistry().getClassLoaderRepository());
                        logger.debug("parse module " + moduleDir.toString() + ", OK!");
                        newModules.add(module);
                        failedModules.remove(module.getModuleFile());
                    }
                    catch(ComponentDescriptionException e) {
                        failedModules.add(moduleDir);
                        logger.warn("parse module " + moduleDir.toString() + " failed!", e);
                    }
                }
            }
            moduleDirs.clear();
            // sort by priority
            Collections.sort(newModules);
            for(int i = 0; i < newModules.size(); i++) {
                Module module = (Module) newModules.get(i);
                if(failedModules.contains(module.getModuleFile())) {
                    logger.warn("module " + module.getModuleFile().toString() + " has been deployed failed, skip!!!");
                }
                else {
                    try {
                        logger.debug("deploying module " + module + " ...");
                        deployer.deploy(module);
                        logger.debug("deploy module " + module + ", OK!");
                        modules.put(module.getModuleFile(), module);
                        failedModules.remove(module.getModuleFile());
                    }
                    catch(DeployException e) {
                        failedModules.add(module.getModuleFile());
                        logger.warn("deploy module " + module + " failed.", e);
                    }
                }
            }
            newModules.clear();
            // set the new deployments
            deployments = modules;
            try {
                Thread.sleep(5000L);
            }
            catch(InterruptedException e) {
                logger.warn(e);
            }
        }
    }

    private void undeploy(Module module) throws DeployException {
        deployments.remove(module);
        deployer.undeploy(module);
    }

    public boolean isStarted() {
        return started;
    }

    public File[] getDeployedModules() {
        return (File[]) deployments.keySet().toArray(new File[deployments.size()]);
    }

    public void setComponentContext(ComponentContext ctx) {
    }

    public static void main(String[] args) {

    }
}