/**
 * JaDOrT: JASMINe Deployment Orchestration Tool
 * Copyright (C) 2008-2009 Bull S.A.S.
 * Copyright (C) 2008-2009 France Telecom R&D
 * Contact: jasmine@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 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id: DummyServerAction.java 3617 2009-05-05 08:33:14Z alitokmen $
 * --------------------------------------------------------------------------
 */
package org.ow2.jasmine.jadort.service.action;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.ow2.jasmine.jadort.api.entities.deployment.ApplicationBean;
import org.ow2.jasmine.jadort.api.entities.topology.ServerBean;

/**
 * Dummy server action, used for testing the service.
 * 
 * @author Malek Chahine
 * @author Remy Bresson
 * @author S. Ali Tokmen
 */
public class DummyServerAction extends ServerAction {

    private String name;

    // Set this to 0 before tests, otherwise tests will never pass!
    public static int CRASH_PROBA_PERCENT = 0;

    // Important: MAX_SLEEP - MIN_SLEEP must be superior to 1
    public static int MIN_SLEEP = 0;

    public static int MAX_SLEEP = 250;

    // Maximal number of sessions per application (for random generation)
    private static int MAX_SESSIONS = 20;

    private static Random random = new Random();

    private enum State {
        STARTED, STOPPED
    };

    private State state = State.STARTED;

    static class ApplicationInstanceInformation {
        private enum State {
            PRESENT, DEPLOYED
        };

        private enum Policy {
            DEFAULT, RESERVED, DISABLED, PRIVATE
        };

        private String name;

        private String version;

        private State state;

        private Policy policy;

        private List<String> mbeans = new ArrayList<String>();

        public String getName() {
            return this.name;
        }

        public void setName(final String name) {
            this.name = name;
        }

        public String getVersion() {
            return this.version;
        }

        public void setVersion(final String version) {
            this.version = version;
        }

        public State getState() {
            return this.state;
        }

        public void setState(final State state) {
            this.state = state;
        }

        public Policy getPolicy() {
            return this.policy;
        }

        public void setPolicy(final Policy policy) {
            this.policy = policy;
        }

        public List<String> getMbeans() {
            return this.mbeans;
        }

        public void setMbeans(final List<String> mbeans) {
            this.mbeans = mbeans;
        }
    }

    private Map<String, ApplicationInstanceInformation> applications = new Hashtable<String, ApplicationInstanceInformation>();

    private String dummyAppName(final String application) {
        return "/" + this.name + "/apps/" + application + ".ear";
    }

    private void addDummyApplication(final String name, final ApplicationInstanceInformation.State state) {
        this.addDummyApplication(name, state, null, null);
    }

    private void addDummyApplication(final String name, final ApplicationInstanceInformation.State state, final String version,
        final ApplicationInstanceInformation.Policy policy) {
        String appName;
        if (version == null) {
            appName = this.dummyAppName(name);
        } else {
            appName = this.dummyAppName(name + "-version" + version);
        }
        ApplicationInstanceInformation appInfo = new ApplicationInstanceInformation();
        appInfo.setName(name);
        appInfo.setVersion(version);
        appInfo.setState(state);
        appInfo.setPolicy(policy);
        this.applications.put(appName, appInfo);
    }

    protected DummyServerAction(final ServerBean server) {
        this.name = server.getName();
        this.appendToLog("Created DummyServerAction for server '" + this.name + "'");

        this.addDummyApplication("app1", ApplicationInstanceInformation.State.DEPLOYED, "V1",
            ApplicationInstanceInformation.Policy.DISABLED);
        this.addDummyApplication("app1", ApplicationInstanceInformation.State.DEPLOYED, "V2",
            ApplicationInstanceInformation.Policy.DEFAULT);
        this.addDummyApplication("app2", ApplicationInstanceInformation.State.DEPLOYED);
    }

    @Override
    protected void connectViaJMX() throws Exception {
        long wait = DummyServerAction.random.nextInt(DummyServerAction.MAX_SLEEP - DummyServerAction.MIN_SLEEP)
            + DummyServerAction.MIN_SLEEP;
        int crash = DummyServerAction.random.nextInt(100);

        Thread.sleep(wait);
        if (crash < DummyServerAction.CRASH_PROBA_PERCENT) {
            throw new Exception("This has crashed for no reason !");
        }
    }

    @Override
    public List<ApplicationBean> listOfApplications() throws Exception {
        if (this.state != State.STARTED) {
            throw new IllegalArgumentException("Server '" + this.name + "' is not started!");
        }

        long wait = DummyServerAction.random.nextInt(DummyServerAction.MAX_SLEEP - DummyServerAction.MIN_SLEEP)
            + DummyServerAction.MIN_SLEEP;
        int crash = DummyServerAction.random.nextInt(100);

        Thread.sleep(wait);
        if (crash < DummyServerAction.CRASH_PROBA_PERCENT) {
            throw new Exception("This has crashed for no reason !");
        }

        List<ApplicationBean> result = new ArrayList<ApplicationBean>(this.applications.size());
        for (ApplicationInstanceInformation appInfo : this.applications.values()) {
            ApplicationBean application = new ApplicationBean(appInfo.getName());
            if (appInfo.getState() == ApplicationInstanceInformation.State.PRESENT) {
                application.setState(ServerAction.STATE_PRESENT);
            } else {
                application.setState(ServerAction.STATE_DEPLOYED);
            }
            if (appInfo.getVersion() != null) {
                application.setVersion(appInfo.getVersion());
                if (appInfo.getPolicy() != null) {
                    String policy = appInfo.getPolicy().toString();
                    policy = policy.charAt(0) + policy.substring(1).toLowerCase();
                    application.setPolicy(policy);
                }
            }
            result.add(application);
        }
        return result;
    }

    @Override
    public boolean canDeployApplications() throws Exception {
        return true;
    }

    /**
     * Checks that the state of an application is as expected, throws an
     * IllegalArgumentException otherwise.
     * 
     * @param appName Application name to check.
     * @param state State to check against, if null then the non-presence of
     *        appName is checked.
     * @return The ApplicationInstanceInformation for appName if state is as
     *         expected.
     */
    private ApplicationInstanceInformation checkState(final String appName, final ApplicationInstanceInformation.State state) {
        if (appName == null) {
            throw new NullPointerException("Application is null");
        }
        ApplicationInstanceInformation appInfo = this.applications.get(appName);
        if (state == null) {
            if (appInfo != null) {
                throw new IllegalArgumentException("Application '" + appName + "' is " + appInfo.getState().toString()
                    + " whereas it should not exist");
            } else {
                return null;
            }
        } else {
            if (appInfo == null) {
                throw new IllegalArgumentException("Application '" + appName + "' does not exist");
            } else if (!state.equals(appInfo.getState())) {
                throw new IllegalArgumentException("Application '" + appName + "' is " + appInfo.getState().toString()
                    + " whereas it should be " + state.toString());
            } else {
                return appInfo;
            }
        }
    }

    /**
     * Checks that the state and policy of an application is as expected, throws
     * an IllegalArgumentException otherwise.
     * 
     * @param appName Application name to check.
     * @param state State to check against, if null then the non-presence of
     *        appName is checked.
     * @param policies Array of Policy to check against, the application's
     *        policy must match one of the elements of this array.
     * @return The ApplicationInstanceInformation for appName if state and
     *         policy are as expected.
     */
    private ApplicationInstanceInformation checkPolicy(final String appName, final ApplicationInstanceInformation.State state,
        final ApplicationInstanceInformation.Policy[] policies) {
        ApplicationInstanceInformation appInfo = this.checkState(appName, state);
        if (policies == null) {
            if (appInfo.getPolicy() == null) {
                return appInfo;
            } else {
                throw new IllegalArgumentException("Application '" + appName + "' has policy " + appInfo.getPolicy().toString()
                    + " whereas it shouldn't have any");
            }
        } else {
            if (appInfo.getPolicy() == null) {
                throw new IllegalArgumentException("Application '" + appName + "' is not versioned (its policy is null)");
            } else {
                for (ApplicationInstanceInformation.Policy policy : policies) {
                    if (appInfo.getPolicy().equals(policy)) {
                        return appInfo;
                    }
                }
                throw new IllegalArgumentException("Application '" + appName + "' has policy " + appInfo.getPolicy().toString());
            }
        }
    }

    @Override
    public String upload(final ApplicationBean application) throws Exception {
        this.appendToLog("Starting upload of application '" + application.toString() + "'");

        if (this.state != State.STARTED) {
            throw new IllegalArgumentException("Server '" + this.name + "' is not started!");
        }

        long wait = DummyServerAction.random.nextInt(DummyServerAction.MAX_SLEEP - DummyServerAction.MIN_SLEEP)
            + DummyServerAction.MIN_SLEEP;
        int crash = DummyServerAction.random.nextInt(100);
        this.appendToLog("This should take " + wait + " milliseconds, crash is " + crash + ", CRASH_PROBA_PERCENT is "
            + DummyServerAction.CRASH_PROBA_PERCENT);

        String appName = this.dummyAppName(application.toString());
        this.checkState(appName, null);

        Thread.sleep(wait);
        if (crash < DummyServerAction.CRASH_PROBA_PERCENT) {
            throw new Exception("This has crashed for no reason !");
        }

        ApplicationInstanceInformation appInfo = new ApplicationInstanceInformation();
        appInfo.setName(application.getName());
        appInfo.setVersion(application.getVersion());
        appInfo.setState(ApplicationInstanceInformation.State.PRESENT);
        this.applications.put(appName, appInfo);

        this.appendToLog("Upload of application '" + application.toString() + "' seems to be OK, now checking");
        this.checkState(appName, ApplicationInstanceInformation.State.PRESENT);

        this.appendToLog("Application '" + application.toString() + "' uploaded as '" + appName + "'");
        return appName;
    }

    @Override
    public void deploy(final String appName) throws Exception {
        this.appendToLog("Starting deploy of application '" + appName + "'");

        if (this.state != State.STARTED) {
            throw new IllegalArgumentException("Server '" + this.name + "' is not started!");
        }

        long wait = DummyServerAction.random.nextInt(DummyServerAction.MAX_SLEEP - DummyServerAction.MIN_SLEEP)
            + DummyServerAction.MIN_SLEEP;
        int crash = DummyServerAction.random.nextInt(100);
        this.appendToLog("This should take " + wait + " milliseconds, crash is " + crash + ", CRASH_PROBA_PERCENT is "
            + DummyServerAction.CRASH_PROBA_PERCENT);

        ApplicationInstanceInformation appInfo = this.checkState(appName, ApplicationInstanceInformation.State.PRESENT);

        Thread.sleep(wait);
        if (crash < DummyServerAction.CRASH_PROBA_PERCENT) {
            throw new Exception("This has crashed for no reason !");
        }

        appInfo.setState(ApplicationInstanceInformation.State.DEPLOYED);
        if (appInfo.getVersion() != null) {
            appInfo.setPolicy(ApplicationInstanceInformation.Policy.RESERVED);
        }

        this.appendToLog("Deploy of application '" + appName + "' seems to be OK, now checking");
        this.checkState(appName, ApplicationInstanceInformation.State.DEPLOYED);

        this.appendToLog("Application '" + appName + "' now " + appInfo.getState().toString());
    }

    @Override
    public String setDefault(final String appName) throws Exception {
        this.appendToLog("Starting setting application '" + appName + "' as default");

        if (this.state != State.STARTED) {
            throw new IllegalArgumentException("Server '" + this.name + "' is not started!");
        }

        long wait = DummyServerAction.random.nextInt(DummyServerAction.MAX_SLEEP - DummyServerAction.MIN_SLEEP)
            + DummyServerAction.MIN_SLEEP;
        int crash = DummyServerAction.random.nextInt(100);
        this.appendToLog("This should take " + wait + " milliseconds, crash is " + crash + ", CRASH_PROBA_PERCENT is "
            + DummyServerAction.CRASH_PROBA_PERCENT);

        ApplicationInstanceInformation appInfo = this.checkPolicy(appName, ApplicationInstanceInformation.State.DEPLOYED,
            new ApplicationInstanceInformation.Policy[] {ApplicationInstanceInformation.Policy.RESERVED,
                ApplicationInstanceInformation.Policy.DISABLED, ApplicationInstanceInformation.Policy.PRIVATE});

        Thread.sleep(wait);
        if (crash < DummyServerAction.CRASH_PROBA_PERCENT) {
            throw new Exception("This has crashed for no reason !");
        }

        String oldDefaultName = null;
        for (Map.Entry<String, ApplicationInstanceInformation> entry : this.applications.entrySet()) {
            if (entry.getValue().getName().equals(appInfo.getName())
                && entry.getValue().getPolicy().equals(ApplicationInstanceInformation.Policy.DEFAULT)) {
                entry.getValue().setPolicy(ApplicationInstanceInformation.Policy.DISABLED);
                oldDefaultName = entry.getKey();
                this.appendToLog("Application '" + oldDefaultName + "' now has policy "
                    + entry.getValue().getPolicy().toString());
                break;
            }
        }
        appInfo.setPolicy(ApplicationInstanceInformation.Policy.DEFAULT);

        this.appendToLog("Set application '" + appName + "' as default seems to be OK, now checking");
        this.checkPolicy(appName, ApplicationInstanceInformation.State.DEPLOYED,
            new ApplicationInstanceInformation.Policy[] {ApplicationInstanceInformation.Policy.DEFAULT});

        this.appendToLog("Application '" + appName + "' now has policy " + appInfo.getPolicy().toString());
        return oldDefaultName;
    }

    @Override
    public void undeploy(final String appName) throws Exception {
        this.appendToLog("Starting undeploy of application '" + appName + "'");

        if (this.state != State.STARTED) {
            throw new IllegalArgumentException("Server '" + this.name + "' is not started!");
        }

        long wait = DummyServerAction.random.nextInt(DummyServerAction.MAX_SLEEP - DummyServerAction.MIN_SLEEP)
            + DummyServerAction.MIN_SLEEP;
        int crash = DummyServerAction.random.nextInt(100);
        this.appendToLog("This should take " + wait + " milliseconds, crash is " + crash + ", CRASH_PROBA_PERCENT is "
            + DummyServerAction.CRASH_PROBA_PERCENT);

        ApplicationInstanceInformation appInfo = this.checkState(appName, ApplicationInstanceInformation.State.DEPLOYED);

        Thread.sleep(wait);
        if (crash < DummyServerAction.CRASH_PROBA_PERCENT) {
            throw new Exception("This has crashed for no reason !");
        }

        appInfo.setState(ApplicationInstanceInformation.State.PRESENT);
        if (appInfo.getVersion() != null) {
            appInfo.setPolicy(null);
        }

        this.appendToLog("Undeploy of application '" + appName + "' seems to be OK, now checking");
        this.checkState(appName, ApplicationInstanceInformation.State.PRESENT);

        this.appendToLog("Application '" + appName + "' now " + appInfo.getState().toString());
    }

    @Override
    public void erase(final String appName) throws Exception {
        this.appendToLog("Starting erase of application '" + appName + "'");

        if (this.state != State.STARTED) {
            throw new IllegalArgumentException("Server '" + this.name + "' is not started!");
        }

        long wait = DummyServerAction.random.nextInt(DummyServerAction.MAX_SLEEP - DummyServerAction.MIN_SLEEP)
            + DummyServerAction.MIN_SLEEP;
        int crash = DummyServerAction.random.nextInt(100);
        this.appendToLog("This should take " + wait + " milliseconds, crash is " + crash + ", CRASH_PROBA_PERCENT is "
            + DummyServerAction.CRASH_PROBA_PERCENT);

        this.checkState(appName, ApplicationInstanceInformation.State.PRESENT);

        Thread.sleep(wait);
        if (crash < DummyServerAction.CRASH_PROBA_PERCENT) {
            throw new Exception("This has crashed for no reason !");
        }

        this.applications.remove(appName);

        this.appendToLog("Erase of application '" + appName + "' seems to be OK, now checking");
        this.checkState(appName, null);

        this.appendToLog("Application '" + appName + "' erased");
    }

    @Override
    public ApplicationBean getApplicationBean(final String appName) throws Exception {
        if (this.state != State.STARTED) {
            throw new IllegalArgumentException("Server '" + this.name + "' is not started!");
        }

        long wait = DummyServerAction.random.nextInt(DummyServerAction.MAX_SLEEP - DummyServerAction.MIN_SLEEP)
            + DummyServerAction.MIN_SLEEP;
        int crash = DummyServerAction.random.nextInt(100);

        Thread.sleep(wait);
        if (crash < DummyServerAction.CRASH_PROBA_PERCENT) {
            throw new Exception("This has crashed for no reason !");
        }

        if (appName == null) {
            return null;
        }

        ApplicationInstanceInformation appInfo = this.applications.get(appName);
        if (appInfo == null) {
            return null;
        }

        ApplicationBean application = new ApplicationBean(appInfo.getName());
        if (appInfo.getState() == ApplicationInstanceInformation.State.PRESENT) {
            application.setState(ServerAction.STATE_PRESENT);
        } else {
            application.setState(ServerAction.STATE_DEPLOYED);
        }
        if (appInfo.getVersion() != null) {
            application.setVersion(appInfo.getVersion());
            String policy = appInfo.getPolicy().toString();
            policy = policy.charAt(0) + policy.substring(1).toLowerCase();
            application.setPolicy(policy);
        }
        return application;
    }

    @Override
    public int getActiveSessions(final String appName) throws Exception {
        if (this.state != State.STARTED) {
            throw new IllegalArgumentException("Server '" + this.name + "' is not started!");
        }

        long wait = DummyServerAction.random.nextInt(DummyServerAction.MAX_SLEEP - DummyServerAction.MIN_SLEEP)
            + DummyServerAction.MIN_SLEEP;
        int crash = DummyServerAction.random.nextInt(100);

        Thread.sleep(wait);
        if (crash < DummyServerAction.CRASH_PROBA_PERCENT) {
            throw new Exception("This has crashed for no reason !");
        }

        if (this.applications.get(appName) == null) {
            throw new IllegalArgumentException("No application for appName '" + appName + "'!");
        }

        if (this.state == State.STARTED) {
            return DummyServerAction.random.nextInt(DummyServerAction.MAX_SESSIONS);
        } else {
            return 0;
        }
    }

    @Override
    public int getActiveSessions() throws Exception {
        if (this.state != State.STARTED) {
            throw new IllegalArgumentException("Server '" + this.name + "' is not started!");
        }

        long wait = DummyServerAction.random.nextInt(DummyServerAction.MAX_SLEEP - DummyServerAction.MIN_SLEEP)
            + DummyServerAction.MIN_SLEEP;
        int crash = DummyServerAction.random.nextInt(100);

        Thread.sleep(wait);
        if (crash < DummyServerAction.CRASH_PROBA_PERCENT) {
            throw new Exception("This has crashed for no reason !");
        }

        if (this.state == State.STARTED) {
            return this.applications.size() * DummyServerAction.random.nextInt(DummyServerAction.MAX_SESSIONS);
        } else {
            return 0;
        }
    }

    @Override
    public void start() throws Exception {
        this.appendToLog("Starting server '" + this.name + "'");

        if (this.state != State.STOPPED) {
            throw new IllegalArgumentException("Server '" + this.name + "' is not stopped!");
        }

        long wait = DummyServerAction.random.nextInt(DummyServerAction.MAX_SLEEP - DummyServerAction.MIN_SLEEP)
            + DummyServerAction.MIN_SLEEP;
        int crash = DummyServerAction.random.nextInt(100);
        this.appendToLog("This should take " + wait + " milliseconds, crash is " + crash + ", CRASH_PROBA_PERCENT is "
            + DummyServerAction.CRASH_PROBA_PERCENT);

        Thread.sleep(wait);
        if (crash < DummyServerAction.CRASH_PROBA_PERCENT) {
            throw new Exception("This has crashed for no reason !");
        }

        this.state = State.STARTED;

        this.appendToLog("Server '" + this.name + "' has been successfully started");
    }

    @Override
    public void stop() throws Exception {
        this.appendToLog("Stopping server '" + this.name + "'");

        if (this.state != State.STARTED) {
            throw new IllegalArgumentException("Server '" + this.name + "' is not started!");
        }

        long wait = DummyServerAction.random.nextInt(DummyServerAction.MAX_SLEEP - DummyServerAction.MIN_SLEEP)
            + DummyServerAction.MIN_SLEEP;
        int crash = DummyServerAction.random.nextInt(100);
        this.appendToLog("This should take " + wait + " milliseconds, crash is " + crash + ", CRASH_PROBA_PERCENT is "
            + DummyServerAction.CRASH_PROBA_PERCENT);

        Thread.sleep(wait);
        if (crash < DummyServerAction.CRASH_PROBA_PERCENT) {
            throw new Exception("This has crashed for no reason !");
        }

        this.state = State.STOPPED;

        this.appendToLog("Server '" + this.name + "' has been successfully stopped");
    }

    @Override
    public boolean isStarted() {
        boolean result = (this.state == State.STARTED);
        return result;
    }

    @Override
    public boolean enableOrDisableApplications(final boolean enable) throws Exception {
        this.appendToLog("Enabling or disabling all applications on server '" + this.name + "'");

        if (this.state != State.STARTED) {
            throw new IllegalArgumentException("Server '" + this.name + "' is not started!");
        }

        long wait = DummyServerAction.random.nextInt(DummyServerAction.MAX_SLEEP - DummyServerAction.MIN_SLEEP)
            + DummyServerAction.MIN_SLEEP;
        int crash = DummyServerAction.random.nextInt(100);
        this.appendToLog("This should take " + wait + " milliseconds, crash is " + crash + ", CRASH_PROBA_PERCENT is "
            + DummyServerAction.CRASH_PROBA_PERCENT);

        Thread.sleep(wait);

        if (crash < DummyServerAction.CRASH_PROBA_PERCENT) {
            throw new Exception("This has crashed for no reason !");
        }

        this.appendToLog("All applications on server '" + this.name + "' have been enabled or disabled successfully");
        return true;
    }

}
