/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007-2008 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.discovery.jgroups;

import java.util.ArrayList;
import java.util.HashMap;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.remote.JMXServiceURL;

import org.jgroups.ChannelException;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.jonas.discovery.base.BaseDiscovery;
import org.ow2.jonas.discovery.jgroups.comm.exception.StopDiscException;
import org.ow2.jonas.discovery.jgroups.manager.DiscoveryManager;
import org.ow2.jonas.discovery.jgroups.utils.JGroupsDiscoveryUtils;
import org.ow2.jonas.ha.HaService;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.util.JonasObjectName;
import org.ow2.jonas.lib.util.Log;
import org.ow2.jonas.service.ServiceException;
/**
 * This is a JGroups based JOnAS discovery implementation.
 * @author   eyindanga
 */
public class JgroupsDiscoveryServiceImpl extends BaseDiscovery implements JgroupsDiscoveryServiceImplMBean {

    /**
     * Default reconnection timeout.
     */
    private static final String DEFAULT_RECONNECTION_TIMEOUT = "5000";
    /**
     * Logger for this service.
     */
    private static Logger logger = Log.getLogger(Log.JONAS_DISCOVERY_PREFIX);
    /**
     *  Reconnection timeout for JGroups Channel. This property is injected.
     */
    private long reconnectionTimeout;

    /**
     *  This property is injected.
     */
    private String groupName;

    /**
     *  This property is injected.
     */
    private String conf;

    /**
     * The discovery Manager.
     */
    private DiscoveryManager dm = null;

    /**
     * The multicast address.
     */
    private String multicastAddress = null;

    /**
     * The multicast port.
     */
    private String multicastPort = null;

    /**
     * The ha service.
     */
    private HaService haService;


    /* (non-Javadoc)
     * @see org.ow2.jonas.lib.service.AbsServiceImpl#doStart()
     */
    @SuppressWarnings("unchecked")
    @Override
    public void doStart() throws ServiceException {
        String discoveryType = JGroupsDiscoveryUtils.DISCOVERY_IS_SLAVE;
        // Why copying the value instead of using it from ServerProperties ?
        setMaster(getServerProperties().isMaster());
        if (getIsDiscoveryMaster()) {
            discoveryType = JGroupsDiscoveryUtils.DISCOVERY_IS_MASTER;
        }

        JmxService jmx = getJmxService();

        logger.log(BasicLevel.DEBUG,
                   "Starting discovery " + discoveryType + " on " + jmx.getDomainName()
                   + "for " + jmx.getJonasServerName() + " server.");
        logger = Log.getLogger(Log.JONAS_DISCOVERY_PREFIX);
        String domainName = jmx.getDomainName();
        JMXServiceURL[] connectorServerURLs = jmx.getConnectorServerURLs();
        urlsList = new ArrayList();
        for (int i = 0; i < connectorServerURLs.length; i++) {
            // The connectorServerURLs may contain null
            // if the list of protocols in Carol contain
            // other protocols than the standard ones (JRMP, IIOP, CMI)
            if (connectorServerURLs[i] != null) {
                urlsList.add(connectorServerURLs[i].toString());
            }
        }
        String[] urls = new String[urlsList.size()];
        for (int i = 0; i < urls.length; i++) {
            urls[i] = (String) urlsList.get(i);
        }
        try {
            //init configuration
            JGroupsDiscoveryUtils.init(jmx.getJonasServerName(),
                                       jmx.getDomainName(),
                                       discoveryType,
                                       conf,
                                       groupName,
                                       jmx.getJmxServer(),
                                       urls,
                                       reconnectionTimeout);
            JGroupsDiscoveryUtils.connectChannel();

        } catch (Throwable e) {
            throw new ServiceException(e.getMessage(), e);
        }
        startDomainMonitor(discoveryType);
        /**
         * multicast settings.
         */
        try {
            multicastAddress = JGroupsDiscoveryUtils.getMulticastAddress();
            multicastPort = JGroupsDiscoveryUtils.getMulticastPort();
        } catch (Exception e1) {
            logger.log(BasicLevel.DEBUG, "Cannot retrieve multicast address. Communication protocol is not multicast \n");
        }

        // Create and register the service MBean
        try {
            jmx.registerMBean(this, JonasObjectName.discoveryService(domainName));
        } catch (Exception e) {
            logger.log(BasicLevel.DEBUG, " Unable to register discovery MBean: ", e);
        }
        logger.log(BasicLevel.INFO, "JGroups based Discovery service started from "+conf+" Stack file \n");

    }


    /**
     * Initializes and starts domain monitor.
     * @param discoveryType MASTER, SLAVE or CLUSTERD
     * @throws ServiceException if the service cannot be started.
     */
    private void  startDomainMonitor(final String discoveryType) throws ServiceException {
        setTtl(JGroupsDiscoveryUtils.getInstance().getTtl());
        dm = new DiscoveryManager(discoveryType);
        // Start discovery manager
        try {
            dm.start();
        } catch (StopDiscException e) {
            // Server with the same name already exists in domain.
            logger.log(BasicLevel.DEBUG,
                    "Discovery manager failed to start due to a pre-existing"
                    + " server in the domain with the same name.", e);
            // Discovery service had a problem.
            throw new ServiceException(
                    "Problem when starting the Discovery Service: ", e);
        }
    }

    /**
     * Starts JGroups discovery with a given environment and a mbean server.
     * @param env Contains discovery properties
     * @param mbeanSrv Contains discovery properties
     * @param discoveryType MBean server of discovery host.
     * @throws Exception Any
     */
    @SuppressWarnings("unchecked")
    public void start(final HashMap<String, Object> env, final MBeanServer mbeanSrv, final String discoveryType) throws Exception{
        if (discoveryType.equals(JGroupsDiscoveryUtils.DISCOVERY_IS_CLUSTERD)) {
            // false is already the initial value, so this just enforce that.
            setMaster(false);
            groupName =(String) env.get("group.name");
            conf = (String) env.get("jgroups.conf");
            String hostName = (String) env.get("host.name");
            String domainName = (String) env.get("domain.name");
            String reconnectionTimeoutStr = (String) env.get("reconnection.timeout");

            if (reconnectionTimeoutStr == null) {
                reconnectionTimeoutStr = DEFAULT_RECONNECTION_TIMEOUT;
                logger.log(BasicLevel.DEBUG, " Reconnection timeout is set to default value\n");
            }
            long reconnectionTimeout = new Long(reconnectionTimeoutStr);
            logger.log(BasicLevel.DEBUG,
                       " Cluster daemon named " + hostName + " is starting discovery on domain "
                       + domainName + ".\n");
            String[] urls = null;
            if (this.urlsList == null) {
                this.urlsList = new ArrayList<String>();
                try {
                    urls =(String[]) env.get("connector.urls");
                    for (int i = 0; i < urls.length; i++) {
                        urlsList.add(urls[i]);
                    }
                } catch (Exception e) {
                    logger.log(BasicLevel.DEBUG,
                               " Exception occurred while getting connector urls for host named "
                               + hostName + " in JGroups discovery \n");
                    throw new Exception("Exception occurred while getting connector urls for host named "
                                        + hostName + " in JGrpups discovery", e);
                }

            }

            try {
                JGroupsDiscoveryUtils.init(hostName,
                                           domainName,
                                           JGroupsDiscoveryUtils.DISCOVERY_IS_CLUSTERD,
                                           conf,
                                           groupName,
                                           mbeanSrv,
                                           urls,
                                           reconnectionTimeout);
                JGroupsDiscoveryUtils.connectChannel();
            } catch (Throwable e) {
                throw new ServiceException(e.getMessage(), e);
            }
            startDomainMonitor(discoveryType);

        }else {
            logger.log(BasicLevel.DEBUG, " JOnAS instance should not use this method");
        }
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.lib.service.AbsServiceImpl#doStop()
     */
    @Override
    public void doStop() throws ServiceException {
        //TODO Stop Clusterd discovery.
        dm.stop();
        try {
            JGroupsDiscoveryUtils.disconnectChannel();
        } catch (ChannelException e) {
            throw new ServiceException(e.getMessage());
        }
        JmxService jmxService = getJmxService();
        if (jmxService != null) {
            jmxService.unregisterMBean(JonasObjectName.discoveryService(getDomainName()));
        }
    }
    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.DiscoveryServiceImplMBean#getDiscoveryProtocolVersion()
     */
    public String getDiscoveryProtocolVersion() {
        return JGroupsDiscoveryUtils.DISCOVERY_PROTOCOL_VERSION;
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.DiscoveryServiceImplMBean#getDiscoveryTtl()
     */
    public String getDiscoveryTtl() {
        return String.valueOf(getTtl());
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.DiscoveryServiceImplMBean#startDiscoveryMaster()
     */
    public void startDiscoveryMaster() throws JMException {

    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.jgroups.JgroupsDiscoveryServiceImplMBean#getJonasName()
     */
    public String getJonasName() {
        return JGroupsDiscoveryUtils.getInstance().getJonasName();
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.jgroups.JgroupsDiscoveryServiceImplMBean#getMyOn()
     */
    public ObjectName getMyOn() {
        return JonasObjectName.discoveryService(JGroupsDiscoveryUtils.getInstance().getDomainName());
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.jgroups.JgroupsDiscoveryServiceImplMBean#getServerId()
     */
    public String getServerId() {
        return JGroupsDiscoveryUtils.getInstance().getServerId();
    }
    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.jgroups.JgroupsDiscoveryServiceImplMBean#getUrls()
     */
    public String[] getUrls() {
        return (String[])this.urlsList.toArray();
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.jgroups.JgroupsDiscoveryServiceImplMBean#setDomainName(java.lang.String)
     */
    public void setDomainName(final String domainName) {
        JGroupsDiscoveryUtils.getInstance().setDomainName(domainName);
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.jgroups.JgroupsDiscoveryServiceImplMBean#setJonasName(java.lang.String)
     */
    public void setJonasName(final String jonasName) {
        JGroupsDiscoveryUtils.getInstance().setJonasName(jonasName);
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.jgroups.JgroupsDiscoveryServiceImplMBean#setMyOn(javax.management.ObjectName)
     */
    public void setMyOn(final ObjectName myOn) {

    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.jgroups.JgroupsDiscoveryServiceImplMBean#setServerId(java.lang.String)
     */
    public void setServerId(final String serverId) {
        JGroupsDiscoveryUtils.getInstance().setServerId(serverId);
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.jgroups.JgroupsDiscoveryServiceImplMBean#setUrls(java.lang.String[])
     */
    public void setUrls(final String[] urls) {
        this.urlsList = toArrayList(urls);

    }
    /**
     * Build and Arraylist from String[] containing jmx urls.
     * @param urls
     * @return
     * @throws NullPointerException
     */
    @SuppressWarnings("unchecked")
    private ArrayList toArrayList(final String[] urls) throws NullPointerException {
        ArrayList<String> ret = new ArrayList();
        try {
            for (int i = 0; i < urls.length; i++) {
                ret.add(urls[i]);
            }
        } catch (NullPointerException e) {
            // TODO What's that ugly thing ???
            throw new NullPointerException("NullPointerException occurred in JgroupsDiscoveryServiceImpl. Urls list must not be null in toArrayList method \n"+e);
        }
        return ret;
    }

    /**
     * @param groupName the groupName to set. Used by the injector
     */
    public void setGroupName(final String groupName) {
        this.groupName = groupName;
    }

    /**
     * @param conf The JGroups configuration file name to set. Used by the injector
     */
    public void setJgroupsConf(final String conf) {
        this.conf = conf;
    }


    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.BaseDiscovery#getListeningIp()
     */
    @Override
    public String getListeningIp() {
        return JGroupsDiscoveryUtils.getInstance().getLocalAddress().getIpAddress().toString();
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.BaseDiscovery#getListeningPort()
     */
    @Override
    public int getListeningPort() {
        return JGroupsDiscoveryUtils.getInstance().getDiscPort();
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.BaseDiscovery#getUrlsList()
     */
    @SuppressWarnings("unchecked")
    @Override
    public ArrayList getUrlsList() {
        return urlsList;
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.BaseDiscovery#setListeningIp(java.lang.String)
     */
    @Override
    public void setListeningIp(final String listeningIp) {
    //TODO Implement this.
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.BaseDiscovery#setListeningPort(int)
     */
    @Override
    public void setListeningPort(final int listeningPort) {
        //TODO Implement this.
    }
    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.BaseDiscovery#setUrlsList(java.util.ArrayList)
     */
    @SuppressWarnings("unchecked")
    @Override
    public void setUrlsList(final ArrayList urlsList) {
        logger.log(BasicLevel.DEBUG, "Setting urls \n");
        this.urlsList = urlsList;
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.jgroups.JgroupsDiscoveryServiceImplMBean#getGreetingListeningPort()
     */
    public int getGreetingListeningPort() {
        logger.log(BasicLevel.DEBUG, "Getting listening port \n");
        return JGroupsDiscoveryUtils.getInstance().getDiscPort();
    }


    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.jgroups.JgroupsDiscoveryServiceImplMBean#setTimeToLive(int)
     */
    public void setTimeToLive(final int ttl) {
        logger.log(BasicLevel.DEBUG, "Setting ttl: not yet implemented \n");
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.DiscoveryServiceImplMBean#getMulticastAddress()
     */
    public String getMulticastAddress() {
        return multicastAddress;
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.DiscoveryServiceImplMBean#getMulticastPort()
     */
    public String getMulticastPort() {
        return multicastPort;
    }

    public void setReconnectionTimeout(final long reconnectionTimeout) {
        this.reconnectionTimeout = reconnectionTimeout;
    }


    public void setHaService(final HaService haService) {
        this.haService = haService;
    }
}
