package jaCop.heartbeat;

import jaCop.traffic.*;
import jaCop.domain.*;
import jaCop.nodeStatus.*;
import jaCop.exceptions.*; 

import java.util.*;

import convFramework.domain.*;

/**
 * @author Lompa
 */
public class CommunityMonitor {
	
	/* campi */
	private COPPacketManager packetMan = null;
	private String community = null;
	private Vector controllers = new Vector();
	private long heartbeatTimeout = 30000;
	
	/**
	 * @param packetMan
	 * @param statusStore
	 * @param community
	 */
	public CommunityMonitor(String community, COPPacketManager packetMan, long heartbeatTimeout) {
		this.packetMan = packetMan;
		this.community = community;
		this.heartbeatTimeout = heartbeatTimeout;
	}
	
	/* metodi per l'accesso ai campi *******************************************************************/
	
	public synchronized String getCommunity(){ return this.community; }
	
	/**
	 * Inizia a controllare un nuovo nodo, se non lo sta già facendo
	 * @param node il nodo
	 */
	public synchronized void control(COPNode node){
		/* controllo */
		Controller c = null;
		if (!existsController(node)){
			/* aggiunta del controller */
			c = addController(node);
		}
		else {
			c = getController(node);
		}
		/* schedulazione heartbeat */
		c.scheduleHeartbeat();
	}
	
	/**
	 * Smette di controllare un nodo, se lo sta facendo
	 * @param node il nodo
	 */
	public synchronized void stopControl(COPNode node){
		/* controllo */
		if (existsController(node)){
			/* recupero del controller */
			Controller c = getController(node);
			/* cancellazione del timer */
			c.stopHeartbeat();
			/* rimozione del nodo e del controllore */
			removeController(node);
		}
	}
	
	/**
	 * Verifica se un nodo è controllato
	 * @param node il nodo
	 * @return true se il nodo è tra quelli controllati
	 */
	public synchronized boolean isControlled(COPNode node){
		return existsController(node);
	}
	
	/**
	 * Smette di controllare tutti i nodi
	 */
	public synchronized void removeAllControlledNodes(){
		while(!controllers.isEmpty()){
			/* recupero del controller */
			Controller c = (Controller) controllers.firstElement();
			/* cancellazione del timer */
			c.stopHeartbeat();
			/* rimozione del nodo e del controllore */
			controllers.remove(c);
		}
	}
	
	private Node convertToNode(COPNode node){
		Node dNode = null;
		try { dNode = new UDPNode(node.getAddress(), node.getPort()); }
		catch(convFramework.exceptions.BadInvocationArgumentException e){
			throw new JaCOPError(e.getMessage());
		}
		return dNode;
	}
	

	/* metodi per facilitare l'accesso ai dati *********************************************************/
	/**
	 * Aggiorna il fattore di carico di un nodo controllato. Se il nodo non è controllato non
	 * fa nulla e resttuisce false, altrimenti restituisce true se il nodo viene modificato.
	 * @param node il nodo il cui fattore di carico potrebbe essere cambiato
	 * @return un flag che indica se il fattore di carico è variato o no
	 */	
	public synchronized boolean update(COPNode node){
		if (isControlled(node)){
			Controller c = getController(node);
			if (c.getLoadFactor() != node.getLoadFactor()){
				c.setLoadFactor(node.getLoadFactor());
				return true;
			}
			else
				return false;
		}
		else
			return false;
	}
	
	/* metodi per la gestione del vettore dei controller ***********************************************/
	
	private boolean existsController(Node node){
		for(int i = 0; i < controllers.size(); i++){
			Controller c = (Controller) controllers.get(i);
			if (c.getNode().getAddress().equals(node.getAddress()) && c.getNode().getPort() == node.getPort()){
				return true;
			}
		}
		return false;
	}
	
	private Controller getController(Node node){
		for(int i = 0; i < controllers.size(); i++){
			Controller c = (Controller) controllers.get(i);
			if (c.getNode().getAddress().equals(node.getAddress()) && c.getNode().getPort() == node.getPort()){
				return c;
			}
		}
		return null;
	}
	
	private void removeController(Node node){
		for(int i = 0; i < controllers.size(); i++){
			Controller c = (Controller) controllers.get(i);
			if (c.getNode().getAddress().equals(node.getAddress()) && c.getNode().getPort() == node.getPort()){
				controllers.remove(c);
			}
		}
	}
	
	private Controller addController(COPNode node){
		Controller c = new Controller(node, heartbeatTimeout, community, packetMan);
		controllers.add(c);
		return c;
	}
	
	/* classi private **********************************************************************************/
	/* classe privata con informazioni di controllo  */
	private static class Controller {
		
		/* campi */
		private Timer heartbeatTimer = new Timer(true);
		private long heartbeatTimeout = 30000;
		private float loadfFactor = -1;
		private String community = null;
		private COPNode node = null;
		private TimerTask currentTask = null;
		
		private COPPacketManager packetMan = null;
		
		/**
		 * @param node
		 * @param heartbeatTimeout
		 * @param community
		 * @param packetMan
		 */
		public Controller(COPNode node, long heartbeatTimeout, String community,
				COPPacketManager packetMan) {
			this.node = node;
			this.loadfFactor = node.getLoadFactor();
			this.heartbeatTimeout = heartbeatTimeout;
			this.community = community;
			this.packetMan = packetMan;
		}		
		
		/**
		 * 
		 */
		public synchronized void scheduleHeartbeat(){
			if (this.currentTask != null) {
				this.currentTask.cancel();
				this.currentTask = new HeartbeatTask(packetMan, getNode(), this);
				this.heartbeatTimer.schedule(this.currentTask, heartbeatTimeout);
			}
			else {
				/*WARNING dovrei innescare subito un heartbeat per aggiornare immediatamente il carico dei nodi
				 * ma per sempificarmi la vita ed i protocolli non lo faccio in questo modo posso far sì
				 * che un nodo risponda solo agli heartbeat dei vicini senza complcare troppo i protocolli */ 
				this.currentTask = new HeartbeatTask(packetMan, getNode(), this);
				this.heartbeatTimer.schedule(this.currentTask, heartbeatTimeout);				
			}
		}
		
		/**
		 * 
		 */
		public synchronized void stopHeartbeat(){
			this.currentTask.cancel();
			this.heartbeatTimer.cancel();
		}
		
		/**
		 * @return
		 */
		public synchronized float getLoadFactor(){
			return this.loadfFactor;
		}
		
		/**
		 * @param loadFactor
		 */
		public synchronized void setLoadFactor(float loadFactor){
			this.loadfFactor = loadFactor;
		}
		
		/**
		 * @return
		 */
		public synchronized String getCommunity(){
			return this.community;
		}
		
		/**
		 * @return
		 */
		public synchronized COPNode getNode(){
			try {
				boolean structure = node.isStructure();
				return new COPNode(node.getAddress(), node.getPort(), this.loadfFactor, structure);
			}
			catch(convFramework.exceptions.BadInvocationArgumentException e){
				throw new JaCOPError(e.getMessage());
			}
		}
	}
	
	/* task di heartbeat: invia un pacchetto d heartbeat e si autorischedula */
	private static class HeartbeatTask extends TimerTask {
		
		/* campi */
		private COPPacketManager packetMan = null;
		private COPNode node = null;
		private Controller parent = null;
		
		/* costruttore */
		public HeartbeatTask(COPPacketManager packetMan, COPNode node, Controller parent){
			this.packetMan = packetMan;
			this.node = node;
			this.parent = parent;
		}
		
		/* (non-Javadoc)
		 * @see java.lang.Runnable#run()
		 */
		public void run() {
			/* nuova conversazione */
			IConversation conv = packetMan.newConversation();
			/* costruzione di un pacchetto di heartbeat */
			Heartbeat packet = packetMan.newHeartbeat(conv.getConversationID(), parent.getCommunity());
			packet.setReceiver(node);
			/* invio pacchetto */
			try { packetMan.send(packet); }
			catch(TranslationException e){
				throw new JaCOPError(e.getMessage());
			}
			
			/* rimozione della conversazione */
			packetMan.removeConversation(conv.getConversationID());
		}
	}
	
}
