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

/**
 * @author Takoua Abdellatif
 */
import javax.management.JMException;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;

import org.ow2.jonas.discovery.base.DuplicateServerNameException;
import org.ow2.jonas.discovery.multicast.comm.DiscoveryComm;
import org.ow2.jonas.discovery.multicast.comm.DiscoveryGreetingListener;
import org.ow2.jonas.discovery.multicast.comm.DiscoveryGreetingResponder;
import org.ow2.jonas.lib.management.javaee.J2eeObjectName;
import org.ow2.jonas.lib.util.Log;

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

/**
 * DiscoveryManager goal is to listen to a well known and reconfigurable
 * IpAddress and to give appropriate JMXURL to manage remotely Jonas server.
 *
 * @author <a href="mailto:Takoua.Abdellatif@inria.fr">Takoua Abdellatif </a>
 * @author Vivek Lakshmanan
 * @version 1.0
 *
 */
public class DiscoveryManager implements DiscoveryManagerMBean,
        MBeanRegistration, NotificationListener {

    private static Logger logger = Log.getLogger(Log.JONAS_DISCOVERY_PREFIX);

    /**
     * <code>greetingListeningPort</code> unicast port to listen to for
     * responses to greeting message
     */
    private int greetingListeningPort;

    /**
     * <code>listeningPort</code> for multicast socket creation
     */
    private int listeningPort;

    /**
     * <code>listeningIp</code> for multicast socket creation
     */
    private String listeningIp;

    /**
     * Runnable object that implements multicast communication with the servers
     * in the management domain
     */
    private DiscoveryComm dc;

    private DiscoveryGreetingResponder dgr;

    private DiscoveryGreetingListener dgl;

    /**
     * Thread associated to DiscoveryManager
     */
    private Thread commDaemon;

    /**
     * Thread associated with DiscoveryGreetingListener. This will continue
     * listening on the multicast port throughout the life of the server for
     * greeting messages from new servers.
     */
    private Thread discoveryGreetingListenerThread;

    /**
     * Current MBean Server
     */
    private MBeanServer mbeanServer;

    private ObjectName myOn;

    private int ttl = 1;

    private int greetingAckTimeOut;

    private String jonasName = null;

    private String domainName = null;

    private String serverId = null;

    private String[] urls = null;

    /**
     * @param serverId TODO
     * @param listeningPort
     *            Port to listen to for multicast messages.
     * @param listeningIp
     *            IP where the multicast group will listen.
     * @param greetingListeningPort
     *            Port to listen to greeting replies on. A message on this port
     *            signifies that a server already in the group has the same
     *            server name as used by this instance.
     * @param greetingAckTimeOut
     *            The amount of time the server will listen to
     *            greetingListeningPort for replies.
     */
    public DiscoveryManager(String serverId, int listeningPort,
            String listeningIp, int greetingListeningPort, int greetingAckTimeOut) {
        this.serverId = serverId;
        this.listeningIp = listeningIp;
        this.listeningPort = listeningPort;
        this.greetingListeningPort = greetingListeningPort;
        this.greetingAckTimeOut = greetingAckTimeOut;
    }

    /*
     */
    public void start() {

        dgl = new DiscoveryGreetingListener(this);
        dgr = new DiscoveryGreetingResponder(this);

        // Start greeting listener and greeting responder in this order.
        if (discoveryGreetingListenerThread == null) {
            discoveryGreetingListenerThread = new Thread(dgl, "GreetingListener");
        }
        discoveryGreetingListenerThread.start();

        try {
            // We dont need this to happen inside a thread since it is a
            // serial operation, this will only listen for greetingAckTimeOut
            // and listen for rejection messages.
            dgr.handleGreeting();
        } catch (DuplicateServerNameException e) {
            // A server with the same servername already exists.
            logger.log(BasicLevel.ERROR,
                    "Discovery manager failed to start due to a pre-existing"
                            + " server in the domain with the same ID.", e);
            throw e;
        }

        // creates an instance of the Discovery communication class
        dc = new DiscoveryComm(this);
        if (commDaemon == null) {
            commDaemon = new Thread(dc, "commDaemon");
        }
        commDaemon.start();
    }

    /**
     */
    public int getGreetingAckTimeOut() {
        return greetingAckTimeOut;

    }

    /**
     */
    public void setGreetingAckTimeOut(int greetingAckTimeOut) {
        this.greetingAckTimeOut = greetingAckTimeOut;

    }

    /**
     */
    public int getGreetingListeningPort() {
        return greetingListeningPort;

    }

    /**
      */
    public void setGreetingListeningPort(int greetingListeningPort) {
        this.greetingListeningPort = greetingListeningPort;

    }

    /**
      */
    public int getListeningPort() {
        return listeningPort;
    }

    /**
     */
    public void setListeningPort(int listeningPort) {
        this.listeningPort = listeningPort;

    }

    /**
      */
    public String getListeningIp() {
        return listeningIp;
    }

    /**
     */
    public void setListeningIp(String listeningIp) {
        this.listeningIp = listeningIp;

    }

    /**
     */
    public void setTimeToLive(int ttl) {
        this.ttl = ttl;
    }

    /**
     */
    public int getTimeToLive() {
        return this.ttl;
    }

    /**
     * @see javax.management.MBeanRegistration#preRegister(javax.management.MBeanServer,
     *      javax.management.ObjectName)
     */
    public ObjectName preRegister(MBeanServer mbeanServer, ObjectName on)
            throws Exception {
        // set mbeanServer
        this.mbeanServer = mbeanServer;
        this.myOn = on;

        return myOn;
    }

    /**
     * @see javax.management.MBeanRegistration#postRegister(java.lang.Boolean)
     */
    public void postRegister(Boolean arg0) {
        // Add discovery manager as a listener of the delegate object to receive
        // unregistration notification of the j2EEServer.
        ObjectName delegate;
        try {
            delegate = new ObjectName("JMImplementation:type=MBeanServerDelegate");
            mbeanServer.addNotificationListener(delegate, this, null, null);
        } catch (JMException e) {
            e.printStackTrace();
        }
    }

    /**
     * @see javax.management.MBeanRegistration#preDeregister()
     */
    public void preDeregister() throws Exception {
        // stop the Discovery Manager
        stop();
    }

    /**
     * @see javax.management.MBeanRegistration#postDeregister()
     */
    public void postDeregister() {

    }

    /**
     * @see javax.management.NotificationListener#handleNotification(javax.management.Notification,
     *      java.lang.Object)
     */
    public void handleNotification(Notification notification, Object handback) {
        if (notification.getType().equals(
                MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
            ObjectName notificationSender = ((MBeanServerNotification) notification)
                    .getMBeanName();
            ObjectName j2eeServerName = J2eeObjectName.J2EEServer(domainName,
                    jonasName);
            if (notificationSender.equals(j2eeServerName)) {
                // If DiscoveryComm has been created then stop it.
                // If the discovery greeting checks passed then dc != null.
                if(dc != null) {
                    dc.stop();
                }

            }
        }
    }

    /**
     * @return jonasName The jonasName.
     */
    public String getJonasName() {
        return jonasName;
    }

    /**
     * @param jonasName
     *            The jonasName to set.
     */
    public void setJonasName(String jonasName) {
        this.jonasName = jonasName;
    }

    /**
     * @return jonasName The domain name.
     */
    public String getDomainName() {
        return domainName;
    }

    /**
     * @param domainNamle
     *            The domainName to set.
     */
    public void setDomainName(String domainNamle) {
        this.domainName = domainNamle;
    }

    /**
     * @param urls
     *            The urls.
     */
    public String[] getUrls() {
        return urls;
    }

    /**
     * @param urls
     *            The urls to set.
     */
    public void setUrls(String[] urls) {
        this.urls = urls;
    }

    /**
     * Stop
     */
    public void stop() {
        // dc is null if a DuplicateServerNameException is thrown
        // at start up time, so do not send a notification.
        if (dc != null) {
            dc.stop();
        }
        dgl.stop();
    }

    public String getServerId() {
        return serverId;
    }

    public void setServerId(String serverId) {
        this.serverId = serverId;
    }
}