/*
 * Created on 4-mag-2004
 */
package jaCop.protocol;

import java.util.*;

import convFramework.domain.*;

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

/**
 * @author Lompa
 */
public abstract class COPTask implements Runnable {
	
	/* campi */
	protected COPPacketManager packetMan = null;
	protected StatusManager statusMan = null;
	protected ConversationGraveyard convGraveyard = null;
	protected IConversationID convID = null;
	protected String community = null;
	protected COPEventListenerManager listenerMan = null;
	private ControlSystem controlSystem = null;
	private TaskConversersList taskList = null;
	
	/* informazioni sul task */
	private boolean isClient = false;
	private boolean priority = false;
	private boolean currentNPTask = false;
	
	/* timer per l'autoterminazione */
	private Timer timer = new Timer(true);
	private TimerTask currentTerminator = null;
	protected long waitingTimeout = 40000;
	private Thread enclosingThread = null;
	
	/* costruttore ************************************************************************************/
	/**
	 * @param conv
	 * @param packetMan
	 * @param statusStore
	 * @param convGraveyard
	 */
	public COPTask(ConfigurationSet conf, COPPacketManager packetMan, COPEventListenerManager listenerMan, 
			StatusManager statusMan, ConversationGraveyard convGraveyard, ControlSystem queue, TaskConversersList taskList,
			boolean isClient, boolean priority) {
		this.community = conf.community;
		this.convID = conf.convID;
		this.waitingTimeout = conf.waitingTimeout;
		
		this.packetMan = packetMan;
		this.statusMan = statusMan;
		this.convGraveyard = convGraveyard;
		this.listenerMan = listenerMan;
		this.controlSystem = queue;
		this.taskList = taskList;
		
		this.enclosingThread = new Thread(this);
		
		this.isClient = isClient;
		this.priority = priority;
	}
	
	/* metodi per l'accesso ai campi *******************************************************************/
	/**
	 * @return la conversazione
	 */
	public IConversationID getConv() {
		return convID;
	}
	
	public String getCommunity(){
		return this.community;
	}
	
	public boolean isInterrupted(){
		return this.enclosingThread.isInterrupted();
	}
	
	public boolean isAlive(){
		return this.enclosingThread.isAlive();
	}
	
	public void interrupt(){
		this.enclosingThread.interrupt();
	}
	
	/* metodi per l'invio e la ricezione di messaggi */

	protected COPPacket receive()
			throws TranslationException, InterruptedException {
		/* rilascio del controllo */
		controlSystem.releaseSystemControl(this);
		/* schedulazione del task di terminazione */
		currentTerminator = new TerminatorTask(this.enclosingThread);
		timer.schedule(currentTerminator, waitingTimeout);
		/* se il thread è stato interrotto forzo la terminazione del client */
		if (isInterrupted()){
			throw new InterruptedException("Delayed interruption");
		}
		/* ricezione */
		COPPacket packet = packetMan.receive(convID);
		jaCop.Debug.print(convID.encode()+" << "+packet.getType()+" da "+packet.getSender().getPort());
		/* se la ricezione va a buon fine nei tempi stabiliti si cancella il terminatore */
		currentTerminator.cancel();
		/* ripresa del controllo */	
		controlSystem.takeSystemControl(this);
		/*restituzione del pacchetto*/
		return packet;
	}
	/**
	 * @param packet
	 * @throws TranslationException
	 */
	/* metodo aggiunto solo per ragioni di omogeneità */
	protected void send(COPPacket packet) {
		try {
			packetMan.send(packet);
			jaCop.Debug.print(packet.getConvID().encode()+" >> "+packet.getType()+" a "+packet.getReceiver().getPort());
		}
		catch(TranslationException e){
			throw new JaCOPError(e.getMessage());
		}
	}
	
	protected void registerConverser(COPNode converser){
		taskList.addRegistration(this, converser, community);
	}
	
	protected void clearTaskRegistrations(){
		taskList.clearClient(this);
	}
	
	/* metodi di utilità ****************************************************************************/
	
	protected void waitForStructureComplete() throws InterruptedException{
		while(statusMan.existsCommunity(community) && !statusMan.hasCompleteStructure(community)){
			/* il task cede la priorità */
			currentNPTask = false;
			/* ...cede il controllo */
			controlSystem.releaseSystemControl(this);
			/* ...attende una seganlazione di struttura completa */
			controlSystem.waitForStructureComplete();
			/* ...cerca di riottenere il controllo */
			controlSystem.takeSystemControl(this);
		}	
	}
	
	protected void signalStructureComplete() {
		if (statusMan.hasCompleteStructure(community))
			controlSystem.signalStructureComplete();
	}
	
	/* metodo run ***********************************************************************************/
	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	public void run(){
		try {
			/* registrazione */
			controlSystem.registerTask(this);
			/* inizio sezione critica */
			controlSystem.takeSystemControl(this);
			/* se il task non è prioritario e riesce a prendere il controllo diventa il task NP corrente */
			//if (!this.isPriority()) this.currentNPTask = true;
			/* esecuzione del corpo */
			body();
			/* fine sezione critica */
			terminate();
		}
		catch(InterruptedException e){
			terminate();
		}
		catch(Exception e){
			e.printStackTrace();
			terminate();
		}
	}
	
	/* metodo body, da implementare ******************************************************************/
	/**
	 * @throws Exception
	 */
	/* ogni eccezione non catturata termina la conversazione corrente */
	protected abstract void body() throws Exception;
	
	/* metodo start */
	/**
	 * 
	 */
	public void start() {
		enclosingThread.start();
	}
	
	/* metodi per la notifica di eventi ***************************************************************/
	public void notifyCommunityJoined(String community){
		CommunityJoinedEvent event = new CommunityJoinedEvent(this, community);
		ICommunityJoinedListener[] listeners = this.listenerMan.getCommunityJoinedListeners();
		for(int i = 0; i< listeners.length; i++)
			listeners[i].communityJoined(event);
	}
	
	public void notifyCommunityLeft(String community){
		CommunityLeftEvent event = new CommunityLeftEvent(this, community);
		ICommunityLeftListener[] listeners = this.listenerMan.getCommunityLeftListeners();
		for(int i = 0; i< listeners.length; i++)
			listeners[i].communityLeft(event);
	}
	
	public void notifyJonRequestRefused(String community, COPNode refuser, String message){
		JoinRequestRefusedEvent event = new JoinRequestRefusedEvent(this, community, refuser, message);
		IJoinRequestRefusedListener[] listeners = this.listenerMan.getJoinRequestRefusedListeners();
		for(int i = 0; i< listeners.length; i++)
			listeners[i].joinRequestRefused(event);
	}
	
	public void notifyNewNeighbor(String community, COPNode neighbor){
		NewNeighborEvent event = new NewNeighborEvent(this, community, neighbor);
		INewNeighborListener[] listeners = this.listenerMan.getNewNeighborListeners();
		for(int i = 0; i< listeners.length; i++)
			listeners[i].newNeighbor(event);
	}
	
	public void notifyNewJuncture(String community, COPNode newJuncture){
		NewJunctureEvent event = new NewJunctureEvent(this, community, newJuncture);
		INewJunctureListener[] listeners = this.listenerMan.getJunctureChangedListeners();
		for(int i = 0; i< listeners.length; i++)
			listeners[i].newJuncture(event);
	}
	
	/* metodo di terminazione */
	protected synchronized void terminate(){
		/* termino la conversazione */
		convGraveyard.bury(convID);
		packetMan.removeConversation(convID);
		/* sospendo un evetuale thread terminatore */
		if (currentTerminator != null) currentTerminator.cancel();
		/* elimino il task dal registro degli interlocutori */
		clearTaskRegistrations();
		/* elimino il task da registro dei task attivi */
		controlSystem.unRegisterTask(this);
		jaCop.Debug.print("CONVERSAZIONE TERMINATA: "+convID.encode());
		//if (statusMan.existsCommunity(community))
		//	jaCop.Debug.print("struttura: " + (statusMan.hasCompleteStructure(community) ? "completa\n" : "rotta\n"));
		/* libero il controllo */
		controlSystem.releaseSystemControl(this);
	}
	
	/* task terminatore: interrompe il thread se è in attesa di un pacchetto */
	private static class TerminatorTask extends TimerTask {
		
		private Thread target = null;
		
		public TerminatorTask(Thread target){
			this.target = target;
		}
		
		public void run(){
			target.interrupt();
		}
	}
	
	public static class ConfigurationSet {
		
		private String community = null;
		private IConversationID convID = null;
		private long waitingTimeout = 30000;
		
		public ConfigurationSet(String community, IConversationID convID){
			this.community = community;
			this.convID = convID;
		}
		
		/**
		 * @return Returns the waitingTimeout.
		 */
		public long getWaitingTimeout() {
			return waitingTimeout;
		}
		/**
		 * @param waitingTimeout The waitingTimeout to set.
		 */
		public void setWaitingTimeout(long waitingTimeout) {
			this.waitingTimeout = waitingTimeout;
		}
	}

	/**
	 * @return Returns the isClient.
	 */
	public boolean isClient() {
		return isClient;
	}
	/**
	 * @return Returns the priority.
	 */
	public boolean isPriority() {
		return priority;
	}
	/**
	 * @return Returns the currentNPTask.
	 */
	public boolean isCurrentNPTask() {
		return currentNPTask;
	}
	/**
	 * @param currentNPTask The currentNPTask to set.
	 */
	public void setCurrentNPTask(boolean currentNPTask) {
		this.currentNPTask = currentNPTask;
	}
}
	
	

