/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2005 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.comm;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import org.ow2.jonas.discovery.base.DuplicateServerNameException;
import org.ow2.jonas.discovery.base.comm.DiscGreeting;
import org.ow2.jonas.discovery.multicast.manager.DiscoveryManager;
import org.ow2.jonas.discovery.multicast.utils.DiscoveryHelper;
import org.ow2.jonas.lib.util.Log;
import org.ow2.jonas.lib.util.NetUtils;
import org.ow2.jonas.service.ServiceException;

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

/**
 * This class sends a multicast message stating the servername and
 * the port at which it will listen for messages. It listens at the
 * unicast port for a pre-determined time (greetingTimeOut) and if
 * it receives a message indicating that a server with the same ID
 * already exists in the domain then it terminates the discovery service.
 *
 * @author Vivek Lakshmanan
 * @version 1.0
 */
public class DiscoveryGreetingResponder extends DiscoveryComm {
    /**
     * Used to recieve responses to the initial greeting message sent out on the multicast port.
     */
    protected DatagramSocket recvUnicastSocket;

    /**
     * Port to listen to for greeting responses.
     */
    protected int greetingPort;

    /**
     * Time out period to listen to greetingPort for replies.
     */
    protected int greetingTimeOut;

    /**
     * The greeting to be sent out identifying the server.
     */
    private DiscGreeting greeting = null;

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

    /**
     * Constructs a DiscoveryGreetingResponder associated to the DiscoveryManager
     *
     * @param dm
     *            DiscoveryManager to which this instance is associated
     */
    public DiscoveryGreetingResponder(DiscoveryManager dm) {
        super(dm);
        this.greetingPort = dm.getGreetingListeningPort();
        this.greetingTimeOut = dm.getGreetingAckTimeOut();
    }


    /**
     * Create a discovery greeting message
     *
     * @param startup -
     *            true: means that is a greeting message at the point of start
     *            up of the discovery service. false: means this is a reply to
     *            a previous greeting message.
     * @return a Discovery Greeting message.
     */
    protected DiscGreeting createDiscGreeting(boolean startup) {
        String theHostAddress;
        try {
            theHostAddress = NetUtils.getLocalAddress();
        } catch (UnknownHostException e) {
            logger.log(BasicLevel.ERROR, "Unknown host", e);
            return null;
        }

        return new DiscGreeting(theHostAddress, this.greetingPort,
                this.jonasName, this.domainName, startup, this.serverId);
    }

    /**
     * Handle the case where the received DiscGreeting indicates that a server
     * with the same name already exists in the domain.
     *
     * @param msg
     *            a discovery greeting received.
     *
     * @throws DuplicateServerNameException
     */
    private void handleReceivedMessage(DiscGreeting msg)
            throws DuplicateServerNameException {

        if (logger.isLoggable(BasicLevel.ERROR)) {
            logger.log(BasicLevel.ERROR, "A server with the given name already"
                    + " exists in the domain, received a rejection message: "
                    + msg);
        }
        stop();

    }


    /* (non-Javadoc)
     * @see java.lang.Runnable#run()
     */
    public void run() {
        if(logger.isLoggable(BasicLevel.DEBUG)){
            logger.log(BasicLevel.DEBUG, "Sending the greeting.");
        }
        // Send the greeting out.
        if (greeting != null) {
            sendNotif(greeting);
        }
    }


    /**
     *
     * Creates a new thread to send the greeting message and listens for
     * rejection messages in response.
     * @throws DuplicateServerNameException - If a server with the same ID
     * is found in the domain.
     */
    public void handleGreeting() throws DuplicateServerNameException {
        // Join the group in order to receive multicast messages
        join();
        // Create the greeting representing a start up.
        greeting = createDiscGreeting(true);

        // Create a unicast socket to receive responses
        try {
            recvUnicastSocket = new DatagramSocket(greetingPort);
        } catch (SocketException e2) {
            logger.log(BasicLevel.ERROR,
                    "DiscComm : Unable to create a Datagram socket", e2);
            // If the port is used throw a service exception.
            throw new ServiceException("Unable to create a datagram socket on port " + greetingPort +
                    " to listen for rejections. Port is still in use.");
        }

        try {
            // Set timeout for reading from the socket. If responses take longer
            // than the timeout set, they will now be ignored.
            recvUnicastSocket.setSoTimeout(greetingTimeOut);

            // Dont wait for messages beyond a pre-defined period of lapsed time.
            long lastTime = greetingTimeOut + System.currentTimeMillis();

            // Create thread which will send the greeting message out.
            Thread sender = new Thread(this, "greeting sender");

            boolean msgSent = false;

            // Listen for a specified amount of time to see if there
            // is a server already present with the ID used.
            while ((notStopped) && System.currentTimeMillis() <= lastTime) {
                // Send the discovery message out in a seperate thread
                // if it has not been sent.
                if(!msgSent){
                    sender.start();
                    msgSent = true;
                }
                // TODO: Should ideally be listening in a seperate thread and sending
                // the discovery message shortly afterwards.
                DatagramPacket datagram = null;
                Object objReceived = null;
                try {
                    datagram = getDatagram(RECEIVE_BUFFER_SIZE);
                    recvUnicastSocket.receive(datagram);
                    objReceived = DiscoveryHelper.bytesToObject(datagram.getData());
                } catch (IOException e) {
                     if (e instanceof SocketTimeoutException) {
                         if (logger.isLoggable(BasicLevel.DEBUG)) {
                             logger.log(BasicLevel.DEBUG,
                                     "No rejections received within the set timeout period: "
                                         + greetingTimeOut
                                         + " ms. Assuming the server ID used is unique in the domain."
                                         + " The timeout period can be changed under jonas.properties:"
                                         + " jonas.service.discovery.greeting.timeout");
                         }
                     } else {
                         logger.log(BasicLevel.DEBUG, "Host received other packet than DataGramSocket packet. That caused following Exception: \n"+e);
                         objReceived = null;
                     }

                }catch (RuntimeException e) {
                    logger.log(BasicLevel.DEBUG, "Host received other packet than DataGramSocket packet. That caused following Exception: \n"+e);
                    objReceived = null;
                }
                if (objReceived != null) {
                    if (objReceived instanceof DiscGreeting) {
                        DiscGreeting greet = (DiscGreeting) objReceived;
                        // Trust the datagram packet instead of the message for address.
                        greet.setSourceAddress(datagram.getAddress().getHostAddress());
                        handleReceivedMessage(greet);
                    }
                }
                datagram = null;
            }

        } catch (SocketException e) {
            logger.log(BasicLevel.ERROR,
                    "DiscoveryGreetingResponder : Socket closed", e);
            notStopped = false;
        } catch (ClassNotFoundException e) {
            logger.log(BasicLevel.ERROR,
                    "DiscoveryGreetingResponder ClassNotFoundException ", e);
        }

    }
    /**
     * Throw the run time exception to state that a server with the same
     * name already exists.
     */
    public void stop() {
        throw new DuplicateServerNameException();
    }

}
