/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2006-2009 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 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$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.management.domain.proxy.clusterd;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.ObjectName;

import org.objectweb.util.monolog.api.BasicLevel;
import org.ow2.jonas.lib.management.domain.DomainMonitor;
import org.ow2.jonas.lib.management.domain.proxy.JMXProxy;
import org.ow2.jonas.lib.management.domain.proxy.server.ServerProxy;
import org.ow2.jonas.lib.management.javaee.J2EEServerState;
import org.ow2.jonas.lib.util.JonasObjectName;

/**
 * ClusterDaemon proxy. It is created when a new element has been found in
 * domain.xml It holds all the necessary information to go to the ClusterDaemon
 * @author durieuxp
 * @author eyindanga
 * @author danesa
 */

public class ClusterDaemonProxy extends JMXProxy implements ClusterDaemonProxyMBean {
    // names of the server controlled by the cluster daemon: read-write.
    private ArrayList controlledServersNames = null;

    private String hostName;

    // Management attributtes
    // Runtime Mbean
    String runTimeSpecVendor = null;

    String runTimeSpecVersion = null;

    String runTimeVmName = null;

    String runTimeVmVendor = null;

    String runTimeVmVersion = null;

    // OperatingSystem mbean
    String operatingSystemAvailableProcessors = null;

    String operatingSystemArch = null;

    String operatingSystemName = null;

    String operatingSystemVersion = null;

    private boolean ALREADY_HAVE_HOST_REMOTE_INFOS = false;

    private boolean hasDiscovery = false;

    /**
     * Constructor. This is called when a ClusterDaemon element has been found
     * in domain.xml.
     * @param dm the DomainMonitor object reference
     * @param name cluster daemon name
     * @param urls possible urls that can be used for connection with the CD
     */
    public ClusterDaemonProxy(final DomainMonitor dm, final String name, final Collection urls) {
        super(dm, name, urls);
    }

    /**
     * Check the MBean server connection and possibly change state.
     */
    public void checkit() {
        logger.log(BasicLevel.DEBUG, getName());
        if (checkConnection()) {
            setState(J2EEServerState.RUNNING);
        } else {
            setState(J2EEServerState.UNREACHABLE);
        }
    }

    /**
     * Start a Remote JOnAS Server.
     * @param serverName Name of the jonas server
     * @param otherParams additional parameters to start a server via the
     *        ClusterDaemon
     * @return true if operation succeeded
     */
    public boolean startServer(final String serverName, final String otherParams) {
        logger.log(BasicLevel.DEBUG, "Start remote server: " + serverName);
        if (!checkConnection()) {
            // Not yet connected. Try to connect now.
            if (!connect(getUrls())) {
                logger.log(BasicLevel.ERROR, "Unable to get connection to cluster daemon " + getName());
                return false;
            }
        }
        try {
            ServerProxy srvProxy = DomainMonitor.getInstance().findServerProxy(serverName);
            /**
             * Keep the proxy coherent.
             */
            srvProxy.setStopThruAdmin(false);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot start server: " + e);
            return false;
        }

        // Ask Cluster Daemon to start the Server.
        String opName = "startJOnAS";
        String[] opParams = {serverName, getDomain(), otherParams};
        String[] opSignature = {"java.lang.String", "java.lang.String", "java.lang.String"};
        try {
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            getConnection().invoke(on, opName, opParams, opSignature);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot start server: " + e);
            return false;
        }
        return true;
    }

    /**
     * Stop a Remote JOnAS Server.
     * @param daemonName ClusterDaemon used to start the jonas server
     * @param serverName Name of the jonas server
     * @param domainName Domain name
     * @return true if operation succeded
     */
    public boolean stopServer(final String serverName, final String otherParams) {
        logger.log(BasicLevel.DEBUG, "Stop remote server: " + serverName);
        if (!checkConnection()) {
            // Not yet connected. Try to connect now.
            if (!connect(getUrls())) {
                logger.log(BasicLevel.ERROR, "Unable to get connection to cluster daemon " + getName());
                return false;
            }
        }
        try {
            ServerProxy srvProxy = DomainMonitor.getInstance().findServerProxy(serverName);
            /**
             * Keep the proxy coherent.
             */
            srvProxy.setStopThruAdmin(true);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot start server: " + e);
            return false;
        }
        // Ask Cluster Daemon to stop the Server.
        String opName = "stopJOnAS";
        String[] opParams = {serverName};
        String[] opSignature = {"java.lang.String"};
        try {
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            getConnection().invoke(on, opName, opParams, opSignature);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot stop server: " + e);
            return false;
        }
        return true;
    }

    /**
     * Halt a Remote JOnAS Server
     * @param daemonName ClusterDaemon used to start the jonas server
     * @param serverName Name of the jonas server
     * @param domainName Domain name
     * @return true if operation succeded
     */
    public boolean haltServer(final String serverName, final String otherParams) {
        logger.log(BasicLevel.DEBUG, "Halt remote server: " + serverName);
        if (!checkConnection()) {
            // Not yet connected. Try to connect now.
            if (!connect(getUrls())) {
                logger.log(BasicLevel.ERROR, "Unable to get connection to cluster daemon " + getName());
                return false;
            }
        }
        try {
            ServerProxy srvProxy = DomainMonitor.getInstance().findServerProxy(serverName);
            /**
             * Keep the proxy coherent.
             */
            srvProxy.setStopThruAdmin(false);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot start server: " + e);
            return false;
        }
        // Ask Cluster Daemon to stop the Server.
        String opName = "haltJOnAS";
        String[] opParams = {serverName};
        String[] opSignature = {"java.lang.String"};
        try {
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            getConnection().invoke(on, opName, opParams, opSignature);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot halt server: " + e);
            return false;
        }
        return true;
    }

    public void startAllServers(final String otherParams) {
        logger.log(BasicLevel.DEBUG, "");
        if (!checkConnection()) {
            // Not yet connected. Try to connect now.
            if (!connect(getUrls())) {
                logger.log(BasicLevel.ERROR, "Unable to get connection to cluster daemon " + getName());
                return;
            }
        }

        // Ask Cluster Daemon to start all the Servers.
        String opName = "startAllJOnAS";
        String[] opParams = {getDomain(), otherParams};
        String[] opSignature = {"java.lang.String", "java.lang.String"};
        try {
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            getConnection().invoke(on, opName, opParams, opSignature);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot start server: " + e);
            // TODO
        }
    }

    public void stopAllServers(final String otherParams) {
        logger.log(BasicLevel.DEBUG, "");
        if (!checkConnection()) {
            // Not yet connected. Try to connect now.
            if (!connect(getUrls())) {
                logger.log(BasicLevel.ERROR, "Unable to get connection to cluster daemon " + getName());
                return;
            }
        }

        // Ask Cluster Daemon to start all the Servers.
        String opName = "stopAllJOnAS";
        String[] opParams = {};
        String[] opSignature = {};
        try {
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            getConnection().invoke(on, opName, opParams, opSignature);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot stop server: " + e);
            // TODO
        }
    }

    public String pingAllJOnAS() {
        // TODO Auto-generated method stub
        return null;
    }

    public int pingJOnAS(final String name) {
        // TODO Auto-generated method stub
        return 0;
    }

    public void reloadConfiguration() {
        // TODO Auto-generated method stub

    }

    /*
     * (non-Javadoc)
     * @see
     * org.ow2.jonas.lib.management.ClusterDaemonProxyMBean#save(java.lang.String
     * )
     */
    public void save(final String name) {
        // TODO Auto-generated method stub
        logger.log(BasicLevel.DEBUG, "");
        if (!checkConnection()) {
            // Not yet connected. Try to connect now.
            if (!connect(getUrls())) {
                logger.log(BasicLevel.ERROR, "Unable to get connection to cluster daemon " + name);
                return;
            }
        }

        setName(name);
        String opName = "saveToXml";
        String[] opParams = {name};
        String[] opSignature = {"java.lang.String"};

        try {
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());

            getConnection().invoke(on, opName, opParams, opSignature);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot save cluster daemon Info To xml file: " + e);
            // TODO
        }

    }

    /**
     * remote call to the cluster daemon mbaen, by using jmx connection.
     */
    @SuppressWarnings("unchecked")
    public void getServersNames() {
        logger.log(BasicLevel.DEBUG, "getting controlled server names");
        if (!checkConnection()) {
            // Not yet connected. Try to connect now.
            if (!connect(getUrls())) {
                logger.log(BasicLevel.ERROR, "Unable to get connection to cluster daemon " + getName());
                return;
            }
        }
        ArrayList ret = null;
        String opName = "serversNames";
        try {
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            ret = (ArrayList) getConnection().invoke(on, opName, null, null);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get Controlled server names:" + e);
            // TODO
        }
        this.controlledServersNames = ret;

    }

    /**
     * @return controlled server names
     */
    @SuppressWarnings("unchecked")
    public ArrayList getControlledServersNames() {
        /**
         * optimize this further
         */
        getServersNames();
        return this.controlledServersNames;

    }

    /**
     * @return the clusterd host name
     */
    public String getHostName() {
        return hostName;
    }

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

    public void setControlledServersNames(final ArrayList controlledServersNames) {
        this.controlledServersNames = controlledServersNames;
    }

    public void setHostName(final String hostName) {
        this.hostName = hostName;
    }

    /**
     * is used to initialized host remote infos if have never done it.
     */
    private void initRemoteHostInfos() {
        if (!ALREADY_HAVE_HOST_REMOTE_INFOS) {
            getRemoteHostInfo();
        }
        return;
    }

    public void getRemoteHostInfo() {
        logger.log(BasicLevel.DEBUG, "getting cluster daemon jmx url");
        if (!checkConnection()) {
            // Not yet connected. Try to connect now.
            if (!connect(getUrls())) {
                logger.log(BasicLevel.ERROR, "Unable to get connection to cluster daemon " + getName());
                return;
            }
        }
        ObjectName on = null;
        try {
            on = JonasObjectName.clusterDaemon(getDomain());
            String[] attributes = new String[] {"RunTimeSpecVendor", "RunTimeSpecVersion", "RunTimeVmName", "RunTimeVmVendor",
                    "RunTimeVmVersion", "OperatingSystemAvailableProcessors", "OperatingSystemName", "OperatingSystemVersion",
                    "OperatingSystemArch"};
            // we will get the following attributes just once.
            AttributeList al = getConnection().getAttributes(on, attributes);
            for (int i = 0; i < al.size(); i++) {
                Attribute at = (Attribute) al.get(i);
                String name = at.getName();
                if ("RunTimeSpecVendor".equals(name)) {
                    runTimeSpecVendor = (String) at.getValue();
                } else if ("RunTimeSpecVersion".equals(name)) {
                    runTimeSpecVersion = (String) at.getValue();
                } else if ("RunTimeVmName".equals(name)) {
                    runTimeVmName = (String) at.getValue();
                } else if ("RunTimeVmVendor".equals(name)) {
                    runTimeVmVendor = (String) at.getValue();
                } else if ("RunTimeVmVersion".equals(name)) {
                    runTimeVmVersion = (String) at.getValue();
                } else if ("OperatingSystemAvailableProcessors".equals(name)) {
                    operatingSystemAvailableProcessors = (String) at.getValue();
                } else if ("OperatingSystemName".equals(name)) {
                    operatingSystemName = (String) at.getValue();
                } else if ("OperatingSystemArch".equals(name)) {
                    operatingSystemArch = (String) at.getValue();
                } else {
                    operatingSystemVersion = (String) at.getValue();
                }
            }

        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get Cluster daemon's Host Operating System:" + e);
            return;
        }
        ALREADY_HAVE_HOST_REMOTE_INFOS = true;
    }

    @SuppressWarnings("unchecked")
    public Hashtable<String, String> dynamicRemoteHostInfos() {
        logger.log(BasicLevel.DEBUG, "getting cluster daemon Dynamic host's infos");
        if (!checkConnection()) {
            // Not yet connected. Try to connect now.
            if (!connect(getUrls())) {
                logger.log(BasicLevel.ERROR, "Unable to get connection to cluster daemon " + getName());
                return null;
            }
        }
        Hashtable<String, String> ret = null;
        try {
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            ret = (Hashtable<String, String>) getConnection().getAttribute(on, "DynamicHostAttributes");

        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get monitoring informations for cluster daemon remote host :" + e);
            return null;
        }
        logger.log(BasicLevel.DEBUG, "Finisched to get clusterd host infos");
        return ret;
    }

    /**
     * @return the operatingSystemAvailableProcessors
     */
    public String getOperatingSystemAvailableProcessors() {
        return operatingSystemAvailableProcessors;
    }

    /**
     * @return the operatingSystemName
     */
    public String getOperatingSystemName() {
        initRemoteHostInfos();
        return operatingSystemName;
    }

    /**
     * @return the operatingSystemVersion
     */
    public String getOperatingSystemVersion() {
        initRemoteHostInfos();
        return operatingSystemVersion;
    }

    /**
     * @return the runTimeSpecVendor
     */
    public String getRunTimeSpecVendor() {
        initRemoteHostInfos();
        return runTimeSpecVendor;
    }

    /**
     * @return the runTimeSpecVersion
     */
    public String getRunTimeSpecVersion() {
        initRemoteHostInfos();
        return runTimeSpecVersion;
    }

    /**
     * @return the runTimeVmName
     */
    public String getRunTimeVmName() {
        initRemoteHostInfos();
        return runTimeVmName;
    }

    /**
     * @return the runTimeVmVendor
     */
    public String getRunTimeVmVendor() {
        initRemoteHostInfos();
        return runTimeVmVendor;
    }

    /**
     * @return the runTimeVmVersion
     */
    public String getRunTimeVmVersion() {
        initRemoteHostInfos();
        return runTimeVmVersion;
    }

    /**
     * @return the OperatingSystem Architecture
     */
    public String getOperatingSystemArch() {
        initRemoteHostInfos();
        return operatingSystemArch;
    }

    /**
     * Getting remote Vm used Memory
     * @return the value of current used memory
     */
    public String getVmCurrentUsedMemory() {
        // get remote static infos if it's not yet done of cluster daemon
        initRemoteHostInfos();
        String ret = null;
        try {
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            ret = (String) getConnection().getAttribute(on, "VmCurrentUsedMemory");

        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get monitoring informations for cluster daemon remote host :" + e);
            return null;
        }
        logger.log(BasicLevel.DEBUG, "Finisched to get clusterd host infos");
        return ret;
    }

    /**
     * Getting remote Vm Total Memory
     * @return the value of Vm Total memory
     */
    public String getVmTotalMemory() {
        // get remote static infos if it's not yet done of cluster daemon
        initRemoteHostInfos();
        String ret = null;
        try {
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            ret = (String) getConnection().getAttribute(on, "VmTotalMemory");

        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get monitoring informations for cluster daemon remote host :" + e);
            return null;
        }
        logger.log(BasicLevel.DEBUG, "Finisched to get Vm Total Memory");
        return ret;
    }

    /**
     * Getting remote Vm's Current used Heap memory
     * @return the value of Vm's Current used Heap memory
     */
    public String getVmCurrentUsedHeapMemory() {
        // get remote static infos if it's not yet done of cluster daemon
        initRemoteHostInfos();
        String ret = null;
        try {
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            ret = (String) getConnection().getAttribute(on, "VmCurrentUsedHeapMemory");

        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get monitoring informations for cluster daemon remote host :" + e);
            return null;
        }
        logger.log(BasicLevel.DEBUG, "Finisched to get clusterd host infos");
        return ret;
    }

    /**
     * Getting remote Vm's Current used non Heap memory
     * @return the value of Vm's Current used non Heap memory
     */
    public String getVmCurrentUsedNonHeapMemory() {
        // get remote static infos if it's not yet done of cluster daemon
        initRemoteHostInfos();
        String ret = null;
        try {
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            ret = (String) getConnection().getAttribute(on, "VmCurrentUsedNonHeapMemory");

        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get monitoring informations for cluster daemon remote host :" + e);
            return null;
        }
        logger.log(BasicLevel.DEBUG, "Finisched to get clusterd host infos");
        return ret;
    }

    /**
     * Getting Operating system Current used space
     * @return the value of Operating system Current used space
     */
    public String getOsCurrentUsedSpace() {
        // get remote static infos if it's not yet done of cluster daemon
        initRemoteHostInfos();
        String ret = null;
        try {
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            ret = (String) getConnection().getAttribute(on, "OsCurrentUsedSpace");

        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get monitoring informations for cluster daemon remote host :" + e);
            return null;
        }
        logger.log(BasicLevel.DEBUG, "Finisched to get clusterd host infos");
        return ret;
    }

    /**
     * Getting Operating system Current used space
     * @return the value of Operating system Total space
     */
    public String getOsTotalSpace() {
        // get remote static infos if it's not yet done of cluster daemon
        initRemoteHostInfos();
        String ret = null;
        try {
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            ret = (String) getConnection().getAttribute(on, "OsTotalSpace");

        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get monitoring informations for cluster daemon remote host :" + e);
            return null;
        }
        logger.log(BasicLevel.DEBUG, "Finisched to get clusterd host infos");
        return ret;
    }

    /**
     * Add a server to cluster daemon control
     * @param name the server name
     * @param description server description
     * @param javaHome path to JRE
     * @param jonasRoot path to bin repository
     * @param JonasBase path to lib repository
     * @param xprem extra parameter e.g: -Djava.net.preferIPv4Stack=true
     * @param autoBoot true if the server is launched when cluster daemon starts
     * @param jonasCmd user command
     * @param saveIt true to flush the clusterd configuration
     */

    @SuppressWarnings("unchecked")
    public void addServer(final String name, final String description, final String jonasRoot, final String jonasBase,
            final String javaHome, final String xprem, final String autoBoot, final String jonasCmd, final String saveIt) {
        // get remote host infos if we have not done it yet.
        initRemoteHostInfos();
        try {
            logger.log(BasicLevel.DEBUG, "Adding server" + name + " to cluster daemon " + getName() + " control ");
            // add the server to the current srv list
            controlledServersNames.add(name);
            String[] opParams = {name, description, jonasRoot, jonasBase, javaHome, xprem, autoBoot, jonasCmd, saveIt};
            String[] opSignature = {"java.lang.String", "java.lang.String", "java.lang.String", "java.lang.String",
                    "java.lang.String", "java.lang.String", "java.lang.String", "java.lang.String", "java.lang.String"};
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            getConnection().invoke(on, "addServer", opParams, opSignature);
        } catch (Exception e) {
            logger.log(BasicLevel.DEBUG, "Cannot Add server named " + name + " to cluster daemon " + getName() + " control "
                    + e);
            return;
        }
    }

    /**
     * Remove this server from cluster daemon control.
     * @param serverName the server to remove
     * @param saveIt true to flush the clusterd configuration
     */
    public void removeServer(final String serverName, final String saveIt) {
        // get remote host infos if we have not done it yet.
        initRemoteHostInfos();
        try {

            // add the server to the current srv list
            controlledServersNames.remove(serverName);
            String[] opParams = {serverName, saveIt};
            String[] opSignature = {"java.lang.String", "java.lang.String"};
            ObjectName on = JonasObjectName.clusterDaemon(getDomain());
            getConnection().invoke(on, "removeServer", opParams, opSignature);
        } catch (Exception e) {
            logger.log(BasicLevel.DEBUG, "Cannot Remove server named " + getName() + " to cluster daemon " + getName()
                    + " control " + e);
            return;
        }
    }

    /**
     * Notification from discovery: RUNNING.
     * @param urls for connection
     */
    @SuppressWarnings("unchecked")
    public void notifyStarting(final Collection urls) {
        logger.log(BasicLevel.DEBUG, getName());
        hasDiscovery = true;
        if (J2EEServerState.RUNNING.equals(getJ2EEServerState())) {
            logger.log(BasicLevel.DEBUG, "Already running");
        } else {
            if (connect(urls)) {
                setState(J2EEServerState.RUNNING);
            } else {
                setState(J2EEServerState.FAILED);
            }
        }
    }

    /**
     * Notification from discovery: STOPPING.
     */
    public void notifyStopping() {
        logger.log(BasicLevel.DEBUG, getName());
        hasDiscovery = true;
        if (J2EEServerState.STOPPED.equals(getJ2EEServerState())) {
            logger.log(BasicLevel.DEBUG, "Already stopped");
        } else if (J2EEServerState.UNKNOWN.equals(getJ2EEServerState())) {
            logger.log(BasicLevel.WARN, "Running now with discovery");
        } else {
            setState(J2EEServerState.STOPPED);
            disconnect();
        }
    }

}
