/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007-2012 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.deployablemonitor;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.osgi.framework.BundleContext;
import org.ow2.jonas.depmonitor.MonitoringService;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.bootstrap.JProp;
import org.ow2.jonas.lib.management.reconfig.PropertiesConfigurationData;
import org.ow2.jonas.lib.service.AbsServiceImpl;
import org.ow2.jonas.lib.util.JonasObjectName;
import org.ow2.jonas.management.J2EEServerService;
import org.ow2.jonas.service.ServiceException;
import org.ow2.util.ee.deploy.api.deployer.IDeployerManager;
import org.ow2.util.ee.deploy.api.helper.IDeployableHelper;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.ow2.util.substitution.engine.DefaultSubstitutionEngine;
import org.ow2.util.substitution.resolver.PropertiesResolver;

/**
 * Service that will launch the Deployable Monitor.
 * @author Florent BENOIT
 */
public class DeployableMonitorService extends AbsServiceImpl implements MonitoringService {

    /**
     * Property for the development mode.
     */
    public static final String DEVELOPMENT_MODE_PROPERTY = "development";

    /**
     * Property for the list of extra directories to analyze.
     */
    public static final String DIRECTORIES_LIST_PROPERTY = "directories";

    /**
     * The name of the JONAS_BASE directory.
     */
    protected static final String JONAS_BASE = JProp.getJonasBase();

    /**
     *  Name as used to label configuration properties.
     */
    public static final String SERVICE_NAME = "depmonitor";

    static final String MONITOR_INTERVAL = "jonas.service.depmonitor.monitorInterval";

    static final String DEVELOPMENT = "jonas.service.depmonitor.development";
    /**
     * Logger.
     */
    private Log logger = LogFactory.getLog(DeployableMonitorService.class);

    /**
     * Development mode or not ? Default is true.
     */
    private boolean developmentMode = true;

    /**
     * JMX Service.
     */
    private JmxService jmxService = null;

    /**
     * List of directories.
     */
    private List<File> directories = null;

    /**
     * Link to the deployable monitor.
     */
    private DeployableMonitor deployableMonitor = null;

    /**
     * Reference to the J2EEServer service.
     */
    private J2EEServerService j2eeServer = null;

    /**
     * Monitor interval between each directory scan.
     */
    private int monitorInterval;

    /**
     * Ordered deployables.
     */
    private String orderedDeployables = null;

    /**
     * DeployableHelper
     */
    private IDeployableHelper deployableHelper = null;

    /**
     * The bundle context
     */
    private BundleContext bundleContext = null;

    /**
     * Default constructor.
     * @param bundleContext The bundle context
     */
    public DeployableMonitorService(final BundleContext bundleContext) {
        this.directories = new LinkedList<File>();
        this.bundleContext = bundleContext;
        this.deployableMonitor = new DeployableMonitor(this, this.bundleContext);
    }

    /**
     * Start the Deployable Monitor service.
     * @throws ServiceException if the startup failed.
     */
    @Override
    protected void doStart() throws ServiceException {
        logger.info("Use the deploy directories ''{0}'', development mode is ''{1}''", directories, developmentMode);

        // Add the default directory
        File defaultDirectory = new File(j2eeServer.getUploadDirectory());
        if (!defaultDirectory.exists()) {
            logger.info("Creating default deploy directory ''{0}''", defaultDirectory);
            defaultDirectory.mkdirs();
        }
        directories.add(defaultDirectory);

        // Load the Descriptors
        jmxService.loadDescriptors(getClass().getPackage().getName(), getClass().getClassLoader());

        // And register MBean
        try {
            jmxService.registerModelMBean(this, JonasObjectName.deployableMonitorService(getDomainName()));
        } catch (Exception e) {
            logger.warn("Cannot register MBean for Deployable Monitor service", e);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    /**
     * Starts the {@link DeployableMonitor} instance.
     */
    public void startMonitoring() {
        if (!getDeployableMonitor().isAlive()) {
            logger.debug("Start DeployableMonitor");
            deployableMonitor.start();
        }
    }

    /**
     * Stop the Deployable Monitor service.
     * @throws ServiceException if the stop failed.
     */
    @Override
    protected void doStop() throws ServiceException {
        // Unregister MBean
        if (jmxService != null) {
            try {
                jmxService.unregisterModelMBean(JonasObjectName.deployableMonitorService(getDomainName()));
            } catch (Exception e) {
                logger.warn("Cannot unregister MBean for Deployable Monitor service", e);
            }
        }
        // Stop deploy monitor
        deployableMonitor.stopOrder();
        /**
         * Don't keep the resources.
         */
        deployableMonitor = null;

        logger.info("DeployableMonitor stopped successfully");

    }

    /**
     * @param dirs List of deployment directories to observe.
     */
    public void setDirectories(final String dirs) {
        List<String> additionalDirectories = convertToList(dirs);
        boolean addDirectory = true;
        for (String dir : additionalDirectories) {
            updateDirectories(dir, addDirectory);
        }

        getDeployableMonitor().setDirectories(directories);
    }

    /**
     * Update the <code>directories</code> datastructure.
     * @param dir directory to add
     * @param addOp if true, add new directory, if false, remove directory
     * @return true if update occured
     */
    private boolean updateDirectories(final String dir, final boolean addOp) {

        DefaultSubstitutionEngine engine = new DefaultSubstitutionEngine();
        engine.setResolver(new PropertiesResolver(System.getProperties()));

        String substitutedDir = engine.substitute(dir);
        File tmpFile = new File(substitutedDir);

        // Not an absolute file, should be relative to JONAS_BASE.
        if (!tmpFile.isAbsolute()) {
            tmpFile = new File(JONAS_BASE + File.separator + substitutedDir);
        }

        // Directory exists ?
        if (!tmpFile.exists()) {
            logger.warn("The given directory ''{0}'' is neither present on the filesystem or in JONAS_BASE ''{1}''",
                    tmpFile, JONAS_BASE);
            return false;
        }
        if (addOp) {
            // Add the directory
            return directories.add(tmpFile);
        } else {
            return directories.remove(tmpFile);
        }
    }

    /**
     * @param exclusionList The list of exclusion patterns.
     */
    public void setExclusions(final String exclusionList) {
        List<String> exclusions = convertToList(exclusionList);
        getDeployableMonitor().setExclusionPatterns(exclusions);
    }

    /**
     * @param deployerManager the {@link IDeployerManager} to set
     */
    public void setDeployerManager(final IDeployerManager deployerManager) {
        // update the monitor object
        getDeployableMonitor().setDeployerManager(deployerManager);
    }

    /**
     * Sets the deployer helper
     * @param deployableHelper The instance of the deployer helper
     */
    public void setDeployableHelperComponent(final IDeployableHelper deployableHelper) {
        this.deployableHelper = deployableHelper;
    }

    /**
     *
     * @return The instance of the deployer helper
     */
    public IDeployableHelper getDeployableHelper() {
        return deployableHelper;
    }

    /**
     * @param jmxService the jmxService to set
     */
    public void setJmxService(final JmxService jmxService) {
        this.jmxService = jmxService;
    }

    /**
     * @param j2eeServer the j2eeServer to set
     */
    public void setJ2EEServer(final J2EEServerService j2eeServer) {
        this.j2eeServer = j2eeServer;
    }

    /**
     * Called when the DeploymentMonitor has finished its first round of
     * deployment (all that was under deploy/ at startup time).
     */
    public void firstCheckEnded() {
        j2eeServer.setRunning();
    }

    /**
     * Set developmentMode value to true (enable), or to false (disable). Component (IPOJO) initializer.
     * "inherit" case: correspondence between the service and the server
     * development mode
     * @param mode value to set
     */
    public void setDevelopmentMode(final String mode) {
        if (mode.equals("inherit")) {
            developmentMode = getServerProperties().isDevelopment();
        } else {
            developmentMode = Boolean.parseBoolean(mode);
        }

        setDevelopmentMode(developmentMode);
    }

    /**
     * Set developmentMode value to true (enable), or to false (disable).
     * @param mode value to set
     */
    public void setDevelopmentMode(final boolean mode) {
        developmentMode = mode;

        // if development mode has been enabled on the fly,
        // make sure the deployables' status are reset
        if (developmentMode && j2eeServer.isRunning()) {
            getDeployableMonitor().reset();
        }

        // update the monitor object
        getDeployableMonitor().setDevelopmentMode(developmentMode);
    }

    /**
     * Set the monitor interval between each directory scan. Component (IPOJO) initializer.
     * @param monitorInterval value to set
     */
    public void setMonitorInterval(final int monitorInterval) {
        this.monitorInterval = monitorInterval;

        // update the monitor object
        getDeployableMonitor().setMonitorInterval(monitorInterval);
    }

    /**
     * Get the deployable monitor.
     * @return the deployable monitor
     */
    private DeployableMonitor getDeployableMonitor() {
        // Create a new instance if necessary.
        if (deployableMonitor == null) {
            deployableMonitor = new DeployableMonitor(this, this.bundleContext);
        }
        return deployableMonitor;
    }

    // Management operations
    // ---------------------

    /**
     * @return <tt>true</tt> if the development mode set to true.
     */
    public boolean isDevelopment() {
        return developmentMode;
    }

    /**
     * Set developmentMode value to true (enable), or to false (disable). Management operation.
     * "inherit" case: correspondence between the service and the server
     * development mode
     * @param mode value to set
     */
    public void setDevelopment(final boolean mode) {
        String developmentMode = (new Boolean(mode)).toString();
        setDevelopmentMode(developmentMode);

        // Send a notification containing the new value of this property to the
        // listener MBean
        String propName = DEVELOPMENT;
        String propValue = developmentMode;
        sendReconfigNotification(getSequenceNumber(), SERVICE_NAME, new PropertiesConfigurationData(propName, propValue));
    }

    /**
     * Sets the list of deployables to use.
     * @param deployables the given deployables to use
     */
    public void setOrderedDeployables(final String deployables) {
        // Convert list
        List<String> ordering = convertToList(deployables);

        // sets the new comparator
        SortableFileDeployableComparator sortableFileDeployableComparator = new SortableFileDeployableComparator();
        sortableFileDeployableComparator.setOrderedDeployables(ordering);
        this.deployableMonitor.setSortDeployablesComparator(sortableFileDeployableComparator);
    }

    /**
     * Returns the monitor interval between each directory scan.
     */
    public int getMonitorPeriod() {
        return this.monitorInterval;
    }

    /**
     * Set the monitor interval between each directory scan. Management method.
     * @param monitorInterval value to set
     */
    public void setMonitorPeriod(final int monitorInterval) {
        setMonitorInterval(monitorInterval);
        // Send a notification containing the new value of this property to the
        // listener MBean
        String propName = MONITOR_INTERVAL;
        String propValue = new Integer(monitorInterval).toString();
        sendReconfigNotification(getSequenceNumber(), SERVICE_NAME, new PropertiesConfigurationData(propName, propValue));
    }

    /**
     * The list of the directory names.
     * @return The list of the directory names.
     */
    public String[] getDirectoryNames() {
        ArrayList<String> result = new ArrayList<String>();
        Iterator<File> it = directories.iterator();
        while (it.hasNext()) {
            File directory = it.next();
            result.add(directory.getName());
        }
        String[] dirNames = new String[result.size()];
        int i = 0;
        for (String dir : result) {
            dirNames[i++] = dir;
        }
        return dirNames;
    }

    /**
     * Add a new deployment directory (management operation).
     * @param directory the directory name.
     */
    public void addDirectory(final String directory) {
        if (updateDirectories(directory, true)) {
            getDeployableMonitor().setDirectories(directories);
        }
    }

    /**
     * Add a new deployment directory (management operation).
     * @param directory the directory name.
     */
    public void removeDirectory(final String directory) {
        if (updateDirectories(directory, false)) {
            getDeployableMonitor().setDirectories(directories);
        }
    }

    /**
     * Checks if a given directory is a deployment directory monitored by this service.
     * @param dir the directory name
     * @return true if the directory is a deployment directory
     */
    public boolean isDepmonitorDir(final String dir) {
        Iterator<File> it = directories.iterator();
        while (it.hasNext()) {
            File directory = it.next();
            try {
                if (directory.getCanonicalPath().equals(dir)) {
                    return true;
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * Save updated configuration.
     */
    public void saveConfig() {
        // Send save reconfig notification
        sendSaveNotification(getSequenceNumber(), SERVICE_NAME);
    }

}
