/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007 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:DiscCommHandlerImpl.java 12003 2007-11-16 17:27:24Z eyindanga $
 * --------------------------------------------------------------------------
 */
/**
 * Handles all discovery communication but Discovery greetings in JGroups discovery.
 * @author eyindanga
 */
package org.ow2.jonas.discovery.jgroups.comm.handler;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.UnknownHostException;

import org.jgroups.Address;
import org.jgroups.ChannelClosedException;
import org.jgroups.Message;
import org.jgroups.View;
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.comm.ClusterdDiscoveryEvent;
import org.ow2.jonas.discovery.base.comm.DiscEvent;
import org.ow2.jonas.discovery.base.comm.DiscGreeting;
import org.ow2.jonas.discovery.base.comm.DiscMessage;
import org.ow2.jonas.discovery.jgroups.comm.api.DiscCommReceiver;
import org.ow2.jonas.discovery.jgroups.utils.Data;
import org.ow2.jonas.discovery.jgroups.utils.JGroupsDiscoveryUtils;
import org.ow2.jonas.lib.management.domain.DomainMonitor;
import org.ow2.jonas.lib.util.Log;

/**
 * @author eyindanga
 *
 */
public class DiscCommHandlerImpl implements DiscCommReceiver {
    /**
     *The logger.
     */
    protected Logger logger = Log.getLogger(Log.JONAS_DISCOVERY_PREFIX);
    /**
     * The domain monitor. It will handle <code>DiscEvent</code> for discovery master.
     */
    private DomainMonitor domainMonitor = null;
    /**
     * Discovery address(host address).
     */
    private Address hostAddress = null;
    /**
     * A discovery message.
     */
    private DiscMessage discMsg = null;
    /**
     * Host type: MASTER, SLAVE, CLUSTERD.
     */
    public String discoveryType = JGroupsDiscoveryUtils.DISCOVERY_IS_SLAVE;

    /**
     * The host name.
     */
    protected String hostName = null;

    /**
     * The host name.
     */
    protected String multicastAddress = null;

    /**Contructor.
     * @param hostAddress IP address of discovery.
     * @param discoveryType Host type.
     */
    public DiscCommHandlerImpl(final Address hostAddress, final String discoveryType) {
        this.hostAddress = hostAddress;
        domainMonitor = DomainMonitor.getInstance();
        this.discoveryType = discoveryType;
        multicastAddress = null;
        hostName = JGroupsDiscoveryUtils.getInstance().getJonasName();
        try {
            multicastAddress = JGroupsDiscoveryUtils.getMulticastAddress().toString();
        } catch (Exception e1) {
            logger.log(BasicLevel.ERROR, "My name is"+ hostName+" I was unable to get multicast address due to following error \n"+e1);
        }
    }
    /**
     * @param hostAddress Discovery IP.
     */
    public DiscCommHandlerImpl(final Address hostAddress) {
        this.hostAddress = hostAddress;
        multicastAddress = null;
        hostName = JGroupsDiscoveryUtils.getInstance().getJonasName();
        try {
            multicastAddress = JGroupsDiscoveryUtils.getMulticastAddress().toString();
        } catch (Exception e1) {
            logger.log(BasicLevel.ERROR, "My name is"+ hostName+" I was unable to get multicast address due to following error \n"+e1);
        }
    }
    /* (non-Javadoc)
     * @see org.jgroups.MessageListener#getState()
     */
    public byte[] getState() {
        return null;
    }

    /* (non-Javadoc)
     * @see org.jgroups.MessageListener#receive(org.jgroups.Message)
     */
    public void receive(final Message msg) {
        Object objReceived = null;
        Data data = null;

        Address senderAddress = null;
        try {
            senderAddress = msg.getSrc();
        }catch(Exception e)	{
            logger.log(BasicLevel.DEBUG, "Exception occured in DiscCommHandlerImpl. Unable to get sender for received message."+e);
        }
        try {
            data = (Data) msg.getObject();
            objReceived = JGroupsDiscoveryUtils.bytesToObject(data);
            if (objReceived != null) {
                if (discoveryType.equals(JGroupsDiscoveryUtils.DISCOVERY_IS_MASTER)) {
                    //discovery is started by master host
                    if(objReceived instanceof ClusterdDiscoveryEvent) {
                        ClusterdDiscoveryEvent event = (ClusterdDiscoveryEvent) objReceived;
                        logger.log(BasicLevel.DEBUG, " My name is " + hostName + ".I'm MASTER. A cluster daemon named " + event.getServerName() + " hosted on " +msg.getSrc() + " is telling me its state is: "+event.getState());
                        logger.log(BasicLevel.DEBUG, " Notifying the domain monitor. \n");
                        domainMonitor.discoveryNotificationForClusterd(event);
                    }else if(objReceived instanceof DiscEvent) {
                        DiscEvent event = (DiscEvent) objReceived;
                        logger.log(BasicLevel.DEBUG, " My name is " + hostName + ".I'm MASTER. A server named " + event.getServerName() + " hosted on " +msg.getSrc() + " is telling me its state is: "+event.getState());
                        logger.log(BasicLevel.DEBUG, " Notifying the domain monitor. \n");
                        domainMonitor.discoveryNotification(event);

                    }else if (objReceived instanceof DiscGreeting) {
                        //received a message from a master
                        logger.log(BasicLevel.DEBUG, " My name is " + hostName + ". I'm MASTER. Handling a discovery greeting from " +msg.getSrc() + " \n");
                        handleGreeting((DiscGreeting) objReceived, senderAddress);
                    } else if (objReceived instanceof DiscMessage) {
                        DiscMessage mess = (DiscMessage) objReceived;
                        logger.log(BasicLevel.DEBUG, " My name is " + hostName + ". I'm MASTER. Handling a discovery message from " +msg.getSrc() + " \n");
                        JGroupsDiscoveryUtils.send(senderAddress, JGroupsDiscoveryUtils.objectToBytes(mess));
                    } else {
                        //received unknown message
                        logger.log(BasicLevel.DEBUG, " My name is " + hostName + ". I'm MASTER. Received an unknown message from " +msg.getSrc() + " \n");
                    }

                } else if (discoveryType.equals(JGroupsDiscoveryUtils.DISCOVERY_IS_SLAVE)) {
                    //discovery is started by slave host
                    if(objReceived instanceof DiscEvent) {
                        logger.log(BasicLevel.DEBUG, " My name is " + hostName + ".I'm a SLAVE. \n");
                        handleEvent((DiscEvent) objReceived, senderAddress);

                    }else if (objReceived instanceof DiscGreeting) {
                        //received a message from a master
                        logger.log(BasicLevel.DEBUG, " My name is " + hostName + ". I'm SLAVE. Handling a discovery greeting from " +msg.getSrc() + " \n");
                        handleGreeting((DiscGreeting) objReceived, senderAddress);
                    } else if (objReceived instanceof DiscMessage) {
                        logger.log(BasicLevel.DEBUG, " My name is " + hostName + ". I'm SLAVE. Handling a discovery message from " +msg.getSrc() + " \n");
                        JGroupsDiscoveryUtils.send(senderAddress, (DiscMessage) objReceived);
                    } else {
                        //received unknown message
                        logger.log(BasicLevel.DEBUG, " My name is " + hostName + ". I'm SLAVE. Received an unknown message from " +msg.getSrc() + " \n");
                    }
                }else if (discoveryType.equals(JGroupsDiscoveryUtils.DISCOVERY_IS_CLUSTERD)) {
                    //discovery is started by clusterd
                    if(objReceived instanceof DiscEvent) {
                        DiscEvent event = (DiscEvent) objReceived;
                        logger.log(BasicLevel.DEBUG, " My name is " + hostName + ".I'm CLUSTER DAEMON. A server named " + event.getServerName() + " hosted on " +msg.getSrc() + " is telling me its state is: "+event.getState());
                        handleEvent((DiscEvent) objReceived, senderAddress);
                    }else if (objReceived instanceof DiscGreeting) {
                        //received a message from a master
                        logger.log(BasicLevel.DEBUG, " My name is " + hostName + ". I'm CLUSTER DAEMON. Handling a disovery greeting from " +msg.getSrc() + " \n");
                        handleGreeting((DiscGreeting) objReceived, senderAddress);
                    } else if (objReceived instanceof DiscMessage) {
                        logger.log(BasicLevel.DEBUG, " My name is " + hostName + ". I'm CLUSTER DAEMON. Handling a disovery message from " +msg.getSrc() + " \n");
                        JGroupsDiscoveryUtils.send(senderAddress, (DiscMessage) objReceived);
                    } else {
                        //received unknown message
                        logger.log(BasicLevel.DEBUG, JGroupsDiscoveryUtils.DISCOVERY_IS_SLAVE+" "+this.hostAddress+" Received unknown message type from host "+msg.getSrc());
                    }

                }else {
                    logger.log(BasicLevel.DEBUG, "Unknown discovery type "+this.hostAddress+" Received message from host "+msg.getSrc());
                }
            }else {
                logger.log(BasicLevel.DEBUG, "Object received from"+msg.getSrc()+" Must not be null");
            }
        } catch (IOException e) {
            logger.log(BasicLevel.DEBUG, " IOException occured in DiscCommReceiver. Unable to cast data received on network into Object. Received packet content must not be the expected one\n"+e);
        } catch (ClassNotFoundException e) {
            logger.log(BasicLevel.DEBUG, " ClassNotFoundException occured in DiscCommReceiver. Unable to cast data received on network into Object.\n"+e);
        } catch (Exception e) {
            logger.log(BasicLevel.DEBUG, " Following exception occurred \n"+e);
        }



    }

    /**
     * Handles <code>DiscEvent</code> for non master hosts.
     * @param discEvent
     */
    private void handleEvent(DiscEvent discEvent, Address add2Reply) {
        if (discEvent.getState().equalsIgnoreCase(DiscoveryState.STARTUP) && discEvent.isDiscoveryMaster()) {
            logger.log(BasicLevel.DEBUG, " Handling discovery event received from starting master hosted on "+discEvent.getSourceAddress()+ " on port "+discEvent.getSourcePort()+"\n");
            //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.RUNNING);
                }else {
                    discEventMsg = JGroupsDiscoveryUtils.createNotifMessage(DiscoveryState.RUNNING);
                }

            } catch (Exception e) {
                logger.log(BasicLevel.ERROR,
                        "DiscoveryComm:  Unable to create a notification message \n", e);
            }
            if (discEventMsg != null) {
                // Multicast the message
                sendNotif(add2Reply, discEventMsg);
            }

        }

    }
    /**
     * Sends a Discovery Message to the group.
     * @param dest
     * @param msg
     */
    public void sendNotif(final Address dest, final DiscMessage msg) {
        try {
            JGroupsDiscoveryUtils.send(dest, JGroupsDiscoveryUtils.objectToBytes(msg));
        } catch (Exception e) {
            logger.log(BasicLevel.DEBUG, e);
        }
        // TODO Auto-generated method stub
    }
    /* (non-Javadoc)
     * @see org.jgroups.MessageListener#setState(byte[])
     */
    public void setState(final byte[] arg0) {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.jgroups.MembershipListener#block()
     */
    public void block() {
        // TODO Auto-generated method stub

    }

    /**
     * Defines actions to perform when a group emeber is suspected.
     * @param arg0
     */
    public void suspect(final Address arg0) {
        if (discMsg == null) {
            discMsg = new DiscMessage(JGroupsDiscoveryUtils.getInstance().getDiscIp(), JGroupsDiscoveryUtils.getInstance().getDiscPort());
        }
        try {
            logger.log(BasicLevel.DEBUG, " Sending 'are-you-alive' message to suspect member " + arg0);
            JGroupsDiscoveryUtils.send(arg0, JGroupsDiscoveryUtils.objectToBytes(discMsg));
        }
        catch (Exception e) {
            logger.log(BasicLevel.DEBUG, " Unable To send 'are-you-alive' message to " + arg0, e);
        }
    }

    /* (non-Javadoc)
     * @see org.jgroups.MembershipListener#viewAccepted(org.jgroups.View)
     */
    public void viewAccepted(final View arg0) {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.jgroups.ExtendedMessageListener#getState(java.lang.String)
     */
    public byte[] getState(final String arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    /* (non-Javadoc)
     * @see org.jgroups.ExtendedMessageListener#getState(java.io.OutputStream)
     */
    public void getState(final OutputStream arg0) {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.jgroups.ExtendedMessageListener#getState(java.lang.String, java.io.OutputStream)
     */
    public void getState(final String arg0, final OutputStream arg1) {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.jgroups.ExtendedMessageListener#setState(java.io.InputStream)
     */
    public void setState(final InputStream arg0) {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.jgroups.ExtendedMessageListener#setState(java.lang.String, byte[])
     */
    public void setState(final String arg0, final byte[] arg1) {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.jgroups.ExtendedMessageListener#setState(java.lang.String, java.io.InputStream)
     */
    public void setState(final String arg0, final InputStream arg1) {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.jgroups.ExtendedMembershipListener#unblock()
     */
    public void unblock() {
        // TODO Auto-generated method stub

    }

    /**
     * @return the hostAddress
     */
    public Address getHostAddress() {
        return hostAddress;
    }

    /**
     * @param hostAddress the hostAddress to set
     */
    public void setHostAddress(final Address hostAddress) {
        this.hostAddress = hostAddress;
    }
    /**
     * Executed when Discovery greeting is received
     * @param message
     * @param add2Reply the host to reply
     */
    private void handleGreeting(final DiscGreeting message, final Address add2Reply) {
        logger.log(BasicLevel.DEBUG, " My name is " + hostName + ". I'm " +JGroupsDiscoveryUtils.getInstance().getDiscType() +". Handling discovery greeting from a server named " + message.getServerName() + " hosted on " +message.getSourceAddress() + "\n");
        try {
            if (message.getState().equals(DiscoveryState.STARTUP)) {
                if (message.getDomainName().equals(JGroupsDiscoveryUtils.getInstance().getDomainName())&& message.getServerName().equals(JGroupsDiscoveryUtils.getInstance().getJonasName())) {
                    logger.log(BasicLevel.DEBUG, " My name is " + hostName + ". I will send DuplicateServerNameException to " + message.getSourceAddress() + " \n");
                    DiscGreeting duplicateNameNotif = JGroupsDiscoveryUtils.createDiscGreeting(false);
                    JGroupsDiscoveryUtils.send(add2Reply, JGroupsDiscoveryUtils.objectToBytes(duplicateNameNotif));
                }
            }else if(message.getState().equals(DiscoveryState.DUPLICATE_NAME)) {
                logger.log(BasicLevel.DEBUG, " Received duplicate name notification from "+message.getSourceAddress()+" Named:  "+message.getServerName()+" on domain:"+message.getDomainName()+" with serverId: "+message.getServerId() +"\n");
                logger.log(BasicLevel.DEBUG, " But Time out for (receiving)greeting messages has already expired \n");
            }
        } catch (ChannelClosedException e) {
            logger.log(BasicLevel.DEBUG, " Unable to send Greeting response to host "+message.getSourceAddress()+". JGroups Channel is closed\n"+e);
        } catch (UnknownHostException e) {
            logger.log(BasicLevel.DEBUG, " Unable to send Greeting response to host "+message.getSourceAddress()+". Host is unknown \n"+e);
        } catch (Exception e) {
            logger.log(BasicLevel.DEBUG, " Unable to send Greeting response to host "+message.getSourceAddress()+". Following exception occurred \n"+e);
        }

    }

}
