package sfs.cluster.lobby.monitor;

import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import sfs.cluster.lobby.DataStore;

/**
 * This is a simple home-brewed cluster monitoring class
 * It monitors all the cluster nodes (lobby and game nodes) performing tasks:
 * 
 * 1. see if the network is available
 * 2. see if the server instances are up and running
 *
 * It could be expanded by adding on or more levels of control. For example it could
 * attempt to talk to a specific extension in order to check custom values, parameters etc...
 * 
 * NOTE:
 * Terracotta provides JMX Events that notify the addition and removal of one or more cluster nodes.
 * You may want to implement those events as well for a complete control over the cluster nodes.
 */
public class ClusterMonitor implements Runnable, IMonitorEventHandler
{
	// monitoring frequency
	static final long TIMER_INTERVAL = 2000;
	
	// max. allowed ping time between nodes in the local network
	static final int REQUIRED_PING_RESPONSE = 20; // milliseconds
	boolean monitorRunning = true;
	Executor threadPool;
	
	public ClusterMonitor()
    {
		showSignature();
		
		threadPool = Executors.newCachedThreadPool();
		threadPool.execute(this);
		
		System.out.println("Monitor running...");
    }
	
	public void run()
	{
		monitorRunning = true;
		int step = 0;
		
		try
		{
			while (monitorRunning)
			{
				System.out.println("Monitoring: " + step++);
				System.out.println("--------------------------------");
				for (String nodeName : DataStore.instance().nodeUserCounts.keySet())
				{
					System.out.println(" -> " + nodeName);
				}
				
				monitorTheCluster();
				
				Thread.sleep(TIMER_INTERVAL);
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	/**
	 * Starts a new monitor agent for each node to monitor.
	 * The agents are handled by the configurable Thread pool
	 * 
	 * @throws Exception
	 */
	public void monitorTheCluster() throws Exception
	{
		Set<String> availableNodes = DataStore.instance().nodeUserCounts.keySet();
		
		for (String nodeName : availableNodes)
		{
			// Check if cluster node is UP
			int colonPos = nodeName.indexOf(':');
			
			String ipAdr = nodeName.substring(0, colonPos);
			int port = Integer.parseInt(nodeName.substring(colonPos + 1, nodeName.length()));
			
			threadPool.execute(new NodeMonitorAgent(this, ipAdr, port));
		}
	}
	
	private void showSignature()
	{
		System.out.println(":::::::::::::::::::::::::::::::::");
		System.out.println(": SFS Cluster Monitor           :");
		System.out.println(": Prototype 1.1.0               :");
		System.out.println(":::::::::::::::::::::::::::::::::\n");
	}
		
	
	//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	// Handle notifications from agents 
	//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	
	/**
	 * Handle a notification by one of the running agents
	 * If the event indicates a node failure we take immediate action and remove the node from the cluster
	 * 
	 * @param event	a monitor event
	 */
	public void handleMonitorEvent(MonitorEvent event)
	{
		String nodeId = (String) event.getParams().get("nodeId");
		
		if (event.getEventName().equals(MonitorEvent.NODE_PING_FAILED))
		{
			System.out.println("{{ SEVERE }} Node: " + nodeId + " seems down (no ping)!");
			removeNodeFromCluster(nodeId);
		}
		else if (event.getEventName().equals(MonitorEvent.NODE_CONNECTION_FAILED))
		{
			System.out.println("{{ SEVERE }} Service at " + nodeId + " is not available!");
			removeNodeFromCluster(nodeId);
		}
	}
	
	/**
	 * Remove all the data related to the died node.
	 * 
	 * @param nodeId	the node id
	 */
	private void removeNodeFromCluster(String nodeId)
	{
		DataStore.instance().removeNode(nodeId);
	}
	
	/**
	 * Main
	 */
	public static void main(String[] args)
    {
	    new ClusterMonitor();
    }
}
