/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2004 Bull S.A.
 * Contact: jonas-team@objectweb.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.objectweb.jonas.discovery;

import java.util.ArrayList;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.remote.JMXServiceURL;
import javax.naming.Context;
import javax.naming.NamingException;

import org.objectweb.jonas.common.Log;
import org.objectweb.jonas.jmx.JmxService;
import org.objectweb.jonas.jmx.JonasObjectName;
import org.objectweb.jonas.service.AbsServiceImpl;
import org.objectweb.jonas.service.ServiceException;
import org.objectweb.jonas.service.ServiceManager;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 *
 * @author Adriana Danes
 *
 * The discovery service creates and starts at least a DiscoveryManager which is a MBean
 * that multicasts <code>discovery information</code> to all the servers who joined the
 * <code>discovery multicast group</code>.
 * <p>
 * The discovery service may also create a Enroller and a DiscoveryClient, and in this case
 * the current server becomes a <code>discovery server</code>.
 * <p>
 * <code>Discovery information</code> contains information allowing to remotely manage a server in the group.
 * <p>
 * <code>Discovery multicast group</code> is a group of servers which can be managed remotely by a discovery server.
 * <p>
 * <code>Discovery server</code> is the server in the group who detains the discovery information concerning all the
 * servers in the group.
 */
public class DiscoveryServiceImpl extends AbsServiceImpl implements DiscoveryService , DiscoveryServiceImplMBean {

    /**
     * The default port where responses to discovery events should be sent.
     */
    private static final String DISCOVERY_SOURCE_PORT_DEFAULT = "9888";
    /**
     * The default port where responses to greeting messages should be sent.
     */
    private static final String DISCOVERY_GREETING_PORT_DEFAULT = "9899";
    /**
     * The default timeout duration that server should wait to receive
     * responses to it's greeting message before continuing.
     */
    private static final String DISCOVERY_GREETING_TIMEOUT_DEFAULT = "1000";
    /**
     * String form of the IP Address where multicast messages are sent.
     */
    private String listeningIp = null;
    /**
     * The port to listen to for multicast messages.
     */
    private int listeningPort;
    /**
     * The port to listen to for responses to greeting message.
     */
    private int greetingListeningPort;
    /**
     * The time period in miliseconds to listen for greeting responses.
     */
    private int greetingAckTimeOut;
    /**
     * The port where discovery events are sent.
     */
    private int sourcePort;

    /**
     * TimeToLive value
     */
    private static final int DISCOVERY_TTL_DEFAULT = 1;
    private int ttl = DISCOVERY_TTL_DEFAULT;

    /**
     * set to true if enroller and discoveryClient started
     */
    private boolean isDiscoveryMaster = false;
    /**
     * Reference to a MBean server.
     */
    private MBeanServer mbeanServer = null;

    /**
     * jmx service reference
     */
    private JmxService jmxService = null;
    /**
     * Logger for this service.
     */
    private static Logger logger = null;
    /**
     * Reference to the Enroller MBean instance
     * The Enrooler can be used by the J2EEDomain MBean to sent a discovery notification to the JMX service
     * in order to add a server to the domain.
     */
    private Enroller enroller = null;

    /**
     * @return the multicast group IP address used by the discovery service
     */
    public String getMulticastAddress() {
        return listeningIp;
    }

    /**
     * @return the multicast group port number used by the discovery service
     */
    public String getMulticastPort() {
        return String.valueOf(listeningPort);
    }

    /**
     * @return true if the current server is a discovery server
     */
    public Boolean getIsDiscoveryMaster() {
        return new Boolean(isDiscoveryMaster);
    }
    /**
     * Management operation allowing to make the current server become a
     * master if its not already.
     * @throws JMException a JMX exception occured when trying to make current server a discovery master
     */
    public void startDiscoveryMaster() throws JMException {
        if (!isDiscoveryMaster) {
            // TODO This code will not work (no DomainMonitor)
            createEnroller();
            createDiscClient();
            isDiscoveryMaster = true;
        }
    }

    /**
     * Initialize the discovery service
     * @param ctx context containing initialization parameters
     * @exception discovery service could not be correctly initialized
     */
    protected void doInit(Context ctx) throws ServiceException {
        //Init the logger
        logger = Log.getLogger(Log.JONAS_DISCOVERY_PREFIX);

        // Get service configuration
        try {
            listeningIp = (String) ctx.lookup("jonas.service.discovery.multicast.address");
            String sListeningPort = (String) ctx.lookup("jonas.service.discovery.multicast.port");
            listeningPort = (Integer.valueOf(sListeningPort)).intValue();
        } catch (NamingException ne) {
            String err = "Cannot read initializations arguments in service context";
            logger.log(BasicLevel.ERROR, err);
            throw new ServiceException(err, ne);
        }

        // Get the ttl value
        try {
            String sttl = (String) ctx.lookup("jonas.service.discovery.ttl");
            if (sttl != null) {
                ttl = (Integer.valueOf(sttl)).intValue();
                logger.log(BasicLevel.DEBUG, "discovery TTL set to " + ttl);
            }
        } catch (NamingException ne) {
            // keep the default value for ttl
        }

        // Get info allowing to see if this is a discovery master
        try {
            String sMaster = (String) ctx.lookup("jonas.service.discovery.master");
            if (sMaster != null && sMaster.equals("true")) {
                isDiscoveryMaster = true;
            }
        } catch (NamingException ne) {
            // isDiscoveryMaster rests false
        }

        // Temporary strings to store string forms of ports and timeout.
        String sSourcePort = null;
        String greetListeningPort = null;
        String greetAckTimeOut = null;

        // Check if the source port number has been defined else use default
        try {
            sSourcePort = (String) ctx.lookup("jonas.service.discovery.source.port");
        } catch (NamingException ne) {
            sSourcePort = DISCOVERY_SOURCE_PORT_DEFAULT;
        }
        sourcePort = (Integer.valueOf(sSourcePort)).intValue();

        // Check if the greeting port has been defined else use default
        try {
            greetListeningPort = (String) ctx.lookup("jonas.service.discovery.greeting.port");
        } catch (NamingException e1) {
            greetListeningPort = DISCOVERY_GREETING_PORT_DEFAULT;
        }
        greetingListeningPort = (Integer.valueOf(greetListeningPort)).intValue();

        // Check if the greeting timeout has been defined else use default
        try {
            greetAckTimeOut = (String) ctx.lookup("jonas.service.discovery.greeting.timeout");
        } catch (NamingException e1) {
            greetAckTimeOut = DISCOVERY_GREETING_TIMEOUT_DEFAULT;
        }
        greetingAckTimeOut = (Integer.valueOf(greetAckTimeOut)).intValue();

        // Get JOnAS references allowing to start execution
        ServiceManager sm  = null;
        try {
            sm = ServiceManager.getInstance();
        } catch (Exception e) {
            String err = "Cannot get ServiceManager instance";
            logger.log(BasicLevel.ERROR, err);
            throw new ServiceException(err, e);
        }
        jmxService = ((JmxService) sm.getJmxService());
        try {
            mbeanServer = jmxService.getJmxServer();
        } catch (ServiceException e) {
            // the JMX service may not be started
            mbeanServer = null;
            String err = "Cannot get MBeanServer reference";
            logger.log(BasicLevel.ERROR, err);
            throw new ServiceException(err, e);
        }
    }

    /**
     * Start the discovery service
     *
     * @throws ServiceException
     *             An error occured when starting the service
     */
    protected void doStart() throws ServiceException {
        // Create discovery manager
        DiscoveryManager dm = new DiscoveryManager(this.getJonasServerName(), listeningPort,
                listeningIp, greetingListeningPort, greetingAckTimeOut);
        dm.setDomainName(getDomainName());
        dm.setJonasName(jmxService.getJonasServerName());
        dm.setTimeToLive(ttl);
        JMXServiceURL[] connectorServerURLs = jmxService
        .getConnectorServerURLs();
        ArrayList 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, JEREMIE, 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);
        }
        dm.setUrls(urls);
        try {
            // Register DiscoveryManager MBean
            if (mbeanServer != null) {
                mbeanServer.registerMBean(dm, JonasObjectName
                        .discoveryManager());
            }
        } catch (JMException e) {
            throw new ServiceException(
                    "Problem when starting the Discovery Service: ", e);
        }
        // Start discovery manager
        try {
            dm.start();
        } catch (DuplicateServerNameException e) {
            // Server with the same name already exists in domain.
            logger.log(BasicLevel.ERROR,
                    "Discovery manager failed to start due to a pre-existing"
                    + " server in the domain with the same name.", e);
            try {
                // Discovery manager failed to start so remove the mbean.
                mbeanServer.unregisterMBean(JonasObjectName.discoveryManager());
            } catch (JMException e1) {
                logger.log(BasicLevel.ERROR, "Problem trying to unregister "
                        + "DiscoveryManager: ", e);
            }
            // Discovery service had a problem.
            throw new ServiceException(
                    "Problem when starting the Discovery Service:", e);
        }

        if (isDiscoveryMaster) {
            // Create enroller
            try {
                createEnroller();
            } catch (JMException e) {
                throw new ServiceException(
                        "Problem when starting the Discovery Service: ", e);
            }

            // Create the discovery client
            try {
                createDiscClient();
            } catch (JMException e) {
                throw new ServiceException(
                        "Problem when starting the Discovery Service: ", e);
            }
        }

        // Create and register the service MBean
        try {
            if (mbeanServer != null) {
                mbeanServer.registerMBean(this, JonasObjectName
                        .discoveryService());
            }
        } catch (JMException e) {
            throw new ServiceException(
                    "Problem when starting the Discovery Service: ", e);
        }
    }
    /**
     * Create the Enroller MBean
     * @throws JMException
     */
    private void createEnroller() throws JMException {
        enroller = new Enroller(listeningPort, listeningIp);
        enroller.setTimeToLive(ttl);
        if (mbeanServer != null) {
            mbeanServer.registerMBean(enroller, JonasObjectName.discoveryEnroller());
        }
    }

    /**
     * Create the DiscoveryClient MBean
     * @throws JMException
     */
    private void createDiscClient() throws JMException {
        DiscoveryClient dc = new DiscoveryClient(listeningPort, listeningIp, sourcePort);
        dc.setTimeToLive(ttl);
        // Register MBean
        if (mbeanServer != null) {
            mbeanServer.registerMBean(dc, JonasObjectName.discoveryClient());
        }
    }

    protected void doStop() throws ServiceException {
        // TO DO
        // ....
        // Unregister discovery MBeans, cluster MBeans etc ..

        // Unegister the service MBean
        try {
            if (mbeanServer != null) {
                mbeanServer.unregisterMBean(JonasObjectName.discoveryService());
            }
        } catch (JMException e) {
            throw new ServiceException("Problem when stoping the Discovery Service: ", e);
        }
    }

    /**
     * @return Returns the enroller.
     */
    public Enroller getEnroller() {
        return enroller;
    }

    /**
     * Create a 'fake' DiscEvent object containing info to establish a JMX connection with a new server
     * in the domain
     * @param serverName the OBJECT_NAME of the server MBean corresponding to the server to connect
     * @param domainName the JOnAS server's domain name
     * @param connectorURLs the urls of the connector server of the server to connect
     * @param state the state of the server (RUNNING in case the DiscEvent is used to add a server; could be STOPPING
     * if the DiscEvent is used to remove a server)
     * @return a new DiscEvent object
     */
    public DiscEvent getDiscEvent(String serverName, String domainName, String[] connectorURLs, String state) {
        String sourceAddress = null;
        int sourcePort = 0;
        String serverId = null;
        DiscEvent fakeMessage = new DiscEvent(sourceAddress, sourcePort, serverName, domainName, serverId, connectorURLs);
        fakeMessage.setState(state);
        return fakeMessage;
    }
    /**
     * @return the discovery protocol version number
     */
     public String getDiscoveryProtocolVersion() {
         return DiscMessage.DISCOVERY_PROTOCOL_VERSION;
     }

    public String getDiscoveryTtl() {
        // TODO Auto-generated method stub
        return (new Integer(ttl)).toString();
    }
}