/**
 * 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.comm;
import java.net.UnknownHostException;

import org.jgroups.Address;
import org.jgroups.ExtendedReceiver;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.jonas.discovery.DiscoveryState;
import org.ow2.jonas.discovery.base.DuplicateServerNameException;
import org.ow2.jonas.discovery.base.comm.DiscEvent;
import org.ow2.jonas.discovery.base.comm.DiscMessage;
import org.ow2.jonas.discovery.jgroups.comm.api.DiscComm;
import org.ow2.jonas.discovery.jgroups.comm.exception.StopDiscException;
import org.ow2.jonas.discovery.jgroups.comm.handler.DiscCommGreetingHandler;
import org.ow2.jonas.discovery.jgroups.comm.handler.DiscCommHandlerImpl;
import org.ow2.jonas.discovery.jgroups.utils.JGroupsDiscoveryUtils;
import org.ow2.jonas.lib.util.Log;
import org.ow2.util.cluster.jgroups.IChannel;

/**
 * Assumes general communication in Master instances of JGroups discovery.
 * @author eyindanga
 */
public class DiscCommManager implements DiscComm {

    /**
     * Default sleep value
     */
    private IChannel comChannel = null;
    /**
     * Current logger.
     */
    private Logger logger;
    /**
     * To check if greeting timeout has exceeded.
     */
    boolean greetingTimeOutExceeded = false;
    /**
     * Listening Ip.
     */
    private Address sourceAddr = null;
    /**
     * Time when we start listening to greeting ack.
     */
    private long startTime;
    /**
     * Listening Ip.
     */
    private ExtendedReceiver discMesssageEventHandler = null;
    /**
     * Host type.
     */
    private String discoveryType = null;

    /**
     *  The constructor.
     * @param discoveryType
     */
    public DiscCommManager(final String discoveryType) {
        this.discoveryType = discoveryType;
    }
    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.jgroups.comm.api.DiscComm#run()
     */
    public void run() {

    }
    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.jgroups.comm.api.DiscComm#stop()
     */
    public void stop() {
        // send a notification message of type STOPPING
        DiscEvent msg = null;
        try {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, " My name is " + JGroupsDiscoveryUtils.getInstance().getJonasName() + " Sending a STOPPING DiscEvent.");
            }
            msg = JGroupsDiscoveryUtils.createNotifMessage(DiscoveryState.STOPPING);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, e);
        }
        if (msg != null) {
            try {
                sendNotif(null, msg);
            } catch (Exception e) {
                logger.log(BasicLevel.DEBUG, " Unable to stop discovery \n");
            }
        }
        logger.log(BasicLevel.DEBUG, " STOPPING DiscEvent successfully sent.\n");
        freeMem();
        //Thread.interrupted();
    }
    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.jgroups.comm.api.DiscComm#start()
     */
    @SuppressWarnings("static-access")
    public void start() throws StopDiscException {
        logger = Log.getLogger(Log.JONAS_DISCOVERY_PREFIX);
        //creating the Channel from the stack file.
        comChannel = JGroupsDiscoveryUtils.getInstance().getComChannel();
        if (comChannel == null) {
            logger.log(BasicLevel.ERROR, "Bad initialization of JGroupsDiscoveryUtils");
            throw new StopDiscException("Bad initialization of JGroupsDiscoveryUtils");
        }
        sourceAddr = comChannel.getLocalAddress();
        ExtendedReceiver greetingReceiver = new DiscCommGreetingHandler(sourceAddr);
        comChannel.setReceiver(greetingReceiver);
        try {
            JGroupsDiscoveryUtils.send(null, JGroupsDiscoveryUtils.objectToBytes(JGroupsDiscoveryUtils.createDiscGreeting(true)));
        } catch (UnknownHostException e1) {
            throw new StopDiscException(e1.getMessage());
        } catch (Exception e1) {
            throw new StopDiscException(e1.getMessage());
        }
        try {
            receiveGreetingMessage();
        } catch (DuplicateServerNameException e) {
            freeMem();
            throw new StopDiscException(e.getMessage());
        }
        //send discovery Event
        // Create notification message containing a discovery event with state RUNNING
        DiscEvent discEventMsg = null;
        try {
            if (discoveryType.equals(JGroupsDiscoveryUtils.DISCOVERY_IS_CLUSTERD)) {
                //Discovery is started by clusterd.
                discEventMsg = JGroupsDiscoveryUtils.createNotifMessageForClusterd(DiscoveryState.STARTUP);
            }else {
                discEventMsg = JGroupsDiscoveryUtils.createNotifMessage(DiscoveryState.STARTUP);
            }

        } catch (Exception e) {
            logger.log(BasicLevel.ERROR,
                    "DiscoveryComm:  Unable to create a notification message \n", e);
        }
        if (discEventMsg != null) {
            // Multicast the message
            sendNotif(null, discEventMsg);
        }
        discMesssageEventHandler = new DiscCommHandlerImpl(sourceAddr, discoveryType);
        //handle all the messages
        comChannel.setReceiver(discMesssageEventHandler);
    }

    /** Checks if time out (for receiving a greeting response) exceeded.
     * @return true if time out exceeded.
     */
    private boolean timeOutExceeded() {
        return System.currentTimeMillis() - startTime > JGroupsDiscoveryUtils.getInstance().getGreetingAckTimeOut();
    }


    /**
     * Waits until a duplicate server name is notified by the channel listener,
     * or time out exceeds.
     * @throws DuplicateServerNameException Server is duplicated.
     */
    @SuppressWarnings("static-access")
    public void receiveGreetingMessage() throws DuplicateServerNameException {
        startTime = System.currentTimeMillis();
        while (!timeOutExceeded() && !JGroupsDiscoveryUtils.duplicateExceptionName ) {
            try {
                Thread.currentThread().sleep(2000);
            } catch (InterruptedException e) {
                logger.log(BasicLevel.ERROR, "InterruptedException when handling greeting message \n"+e);
            }
        }
        if(JGroupsDiscoveryUtils.duplicateExceptionName){

            logger.log(BasicLevel.DEBUG, "Duplicate server name exception occurred \n");
            throw new DuplicateServerNameException(JGroupsDiscoveryUtils.getInstance().getServerId(), JGroupsDiscoveryUtils.getInstance().getJonasName(), JGroupsDiscoveryUtils.getInstance().getDomainName());
        }
        //timeOutExceeded without DuplicateNameException
        logger.log(BasicLevel.DEBUG, "Time out exceeded: no DuplicateNameException \n GO on starting discovery service");

    }
    /**
     * Sets high cost memory fields to null
     */
    private void freeMem() {
        logger.log(BasicLevel.DEBUG, " Closing channel.\n");
        if ((comChannel!= null)&&(comChannel.isOpen())) {
            comChannel.close();
        }
        logger.log(BasicLevel.DEBUG, " Channel successfully closed.\n");
        comChannel = null;
        logger = null;
        sourceAddr = null;
    }

    /**
     * sends (multicasts) a Discovery Message to the group.
     * @param msg The message to send.
     */
    public void sendNotif(final Address dest, final DiscMessage msg) {
        logger.log(BasicLevel.DEBUG, "Attempting to multicast a starting discovery notification\n");
        try {
            JGroupsDiscoveryUtils.send(dest, JGroupsDiscoveryUtils.objectToBytes(msg));
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, e);
        }
        logger.log(BasicLevel.DEBUG, " Discovery notification successfully sent\n");
    }

}
