/**
 * 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.DEBUG, "My name is " + hostName + ". Unable to get multicast address because \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.DEBUG, "My name is " + hostName + ". Unable to get multicast address because \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");
                        try {
                            JGroupsDiscoveryUtils.send(senderAddress, JGroupsDiscoveryUtils.objectToBytes(mess));
                        } catch (Exception e) {
                            logger.log(BasicLevel.DEBUG, " Unable to send message\n");
                        }

                    } 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");
                        try {
                            JGroupsDiscoveryUtils.send(senderAddress, (DiscMessage) objReceived);
                        } catch (Exception e) {
                            logger.log(BasicLevel.DEBUG, " Unable to send message\n");
                        }
                    } 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");
                        try {
                            JGroupsDiscoveryUtils.send(senderAddress, (DiscMessage) objReceived);
                        } catch (Exception e) {
                            logger.log(BasicLevel.DEBUG, " Unable to send message\n");
                        }

                    } 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(final DiscEvent discEvent, final 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 member 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);
        }

    }

}
