/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package flex.messaging.util;

import flex.messaging.FlexComponent;
import flex.messaging.config.ConfigMap;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

/**
 * This class watches for changes on files and forces a re-deploy by touching the specified files.
 */
public class RedeployManager implements FlexComponent {
    private boolean enabled;
    private long watchInterval;
    private List watches;
    private List touches;

    private ScheduledExecutorService redeployService;
    private boolean started;

    //--------------------------------------------------------------------------
    //
    // Constructor
    //
    //--------------------------------------------------------------------------

    /**
     * Constructs a new <code>RedeployManager</code> with default settings.
     */
    public RedeployManager() {
        this(null);
    }

    /**
     * Constructs a new <code>RedeployManager</code> with the supplied thread
     * factory.
     *
     * @param tf Thread factory to use for the scheduled service used.
     */
    public RedeployManager(ThreadFactory tf) {
        if (tf == null)
            tf = new MonitorThreadFactory();

        enabled = false;
        touches = new ArrayList();
        watchInterval = 20;
        watches = new ArrayList();

        redeployService = Executors.newSingleThreadScheduledExecutor(tf);
    }

    //--------------------------------------------------------------------------
    //
    // Initialize, validate, start, and stop methods. 
    //
    //--------------------------------------------------------------------------

    /**
     * Implements FlexComponents.initialize.
     * This is no-op for RedeployManager as it does not have an id and all
     * its properties are directly settable.
     */
    public void initialize(String id, ConfigMap properties) {
        // No-op
    }

    /**
     * Implements FlexComponent.start.
     * Starts the <code>RedeployManager</code>.
     */
    public void start() {
        if (!started && enabled) {
            redeployService.schedule(new RedeployTask(), 20 * 1000, TimeUnit.MILLISECONDS);
            started = true;
        }
    }

    /**
     * Stops the <code>RedeployManager</code>.
     */
    public void stop() {
        if (started && enabled) {
            started = false;
        }
        redeployService.shutdownNow();
    }

    //--------------------------------------------------------------------------
    //
    // Public Methods
    //         
    //--------------------------------------------------------------------------

    /**
     * Returns whether redeploy is enabled or not.
     *
     * @return <code>true</code> if redeploy is enabled; otherwise <code>false</code>.
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * Sets whether redeploy is enabled or not.
     *
     * @param enabled Whether redeploy is enabled or not.
     */
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * Implements FlexComponent.isStarted.
     * Returns whether the RedeployManager is started or not.
     *
     * @return <code>true</code> if the component is started; otherwise <code>false</code>.
     */
    public boolean isStarted() {
        return started;
    }

    /**
     * Returns the watch interval.
     *
     * @return The watch interval.
     */
    public long getWatchInterval() {
        return watchInterval;
    }

    /**
     * Sets the watch interval.
     *
     * @param watchInterval The watch interval to set.
     */
    public void setWatchInterval(long watchInterval) {
        this.watchInterval = watchInterval;
    }

    /**
     * Returns the list of watch files.
     *
     * @return The list of watch files.
     */
    public List getWatchFiles() {
        return watches;
    }

    /**
     * Adds a watch file. Note that the watch file set with this method should
     * not contain the context.root token.
     *
     * @param watch The watch file to add.
     */
    public void addWatchFile(String watch) {
        watches.add(watch);
    }

    /**
     * Sets the list of watch files. Note that watch files set with this method
     * should not contain contain the context.root token.
     *
     * @param watches The list of watch files to set.
     */
    public void setWatchFiles(List watches) {
        this.watches = watches;
    }

    /**
     * Returns the list of touch files.
     *
     * @return The list of touch files.
     */
    public List getTouchFiles() {
        return touches;
    }

    /**
     * Adds a touch file. Note that the touch file set with this method should
     * not contain the context.root token.
     *
     * @param touch The touch file to add.
     */
    public void addTouchFile(String touch) {
        touches.add(touch);
    }

    /**
     * Sets the list of touch files. Note that touch files set with this method
     * should not contain the context.root token.
     *
     * @param touches The list of touch files to set.
     */
    public void setTouchFiles(List touches) {
        this.touches = touches;
    }

    /**
     * Forces the redeployment.
     */
    public void forceRedeploy() {
        Iterator iter = touches.iterator();
        while (iter.hasNext()) {
            String filename = (String) iter.next();
            File file = new File(filename);
            if (file.exists() && (file.isFile() || file.isDirectory())) {
                file.setLastModified(System.currentTimeMillis());
            }
        }
    }

    //--------------------------------------------------------------------------
    //
    // Nested Classes
    //
    //--------------------------------------------------------------------------

    class MonitorThreadFactory implements ThreadFactory {
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setDaemon(true);
            t.setName("RedeployManager");
            return t;
        }
    }

    class RedeployTask implements Runnable {
        public void run() {
            boolean redeploy = false;

            // check if any of the redeploy watches have changed
            Iterator iter = watches.iterator();
            while (iter.hasNext() && !redeploy) {
                WatchedObject watched = (WatchedObject) iter.next();
                if (!watched.isUptodate())
                    redeploy = true;
            }

            if (redeploy)
                forceRedeploy();
            else
                redeployService.schedule(new RedeployTask(), watchInterval * 1000, TimeUnit.MILLISECONDS);
        }
    }
}
