/*
 * Created on 20-apr-2004
 */
package convFramework.traffic;

import java.util.*;

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

/**
 * @author Lompa
 */
public class ReTransmissionManager implements  IMissingAckListener, IAckArrivedListener{
	
	/* campi */
	private TreeMap reTransmissionMap = new TreeMap();
	
	private OutGate outGate = null;
	private TransmissionEventListenerManager eventListenerMan = null;
	
	private int maxReTransmissions = 0;
	private long reTransmissionDelay = 0; 
	
	/**
	 * @param outGate
	 * @param eventListenerMan
	 */
	public ReTransmissionManager(OutGate outGate, TransmissionEventListenerManager eventListenerMan, int maxReTransmissions, long reTransmissionDelay){
		/* inizializzazione campi */
		this.outGate = outGate;
		this.eventListenerMan = eventListenerMan;
		this.maxReTransmissions = maxReTransmissions;
		this.reTransmissionDelay = reTransmissionDelay;
		/* registazione sul gestore degli ascoltatori */
		eventListenerMan.addMissingAckListener(this);
	}
	
	/**
	 * Riceve un pacchetto e ne gestisce le ritrasmissioni, fino all'arrivo di un risposta
	 * @param packet il pacchetto da gestire
	 */
	public synchronized void manage(Packet packet){
		addReTransmission(packet);
	}
	
	public synchronized boolean isBeingReTransmitted(IPacketID packetID){
		IPacketID prova = packetID;
		return reTransmissionMap.containsKey(packetID.encode());
	}
	
	/* GESTIONE DELLA MAPPA DELLE RITRASMISSIONI */
	private synchronized void addReTransmission(Packet packet){
		ReTransmission reTransmission = new ReTransmission(outGate, packet, maxReTransmissions, reTransmissionDelay, eventListenerMan); 
		reTransmissionMap.put(packet.getPacketID().encode(), reTransmission);
	}
	
	private synchronized ReTransmission removeReTransmission(IPacketID packetID){
		ReTransmission reTransmission =  (ReTransmission) reTransmissionMap.get(packetID.encode());
		if (reTransmission != null) reTransmission.cancel();
		reTransmissionMap.remove(packetID.encode());
		return reTransmission;
	}
	
	/* GESTIONE DI UNA RISPOSTA MANCATA */
	/* se una risposta non arriva posso cancellare la ritrasmissione associata */
	/* (non-Javadoc)
	 * @see convFramework.traffic.event.IMissingAckListener#missingResponse(convFramework.traffic.event.MissingAckEvent)
	 */
	public void missingAck(MissingAckEvent event){
		removeReTransmission(event.getTransmittedPacket().getPacketID());
	}
	
	/* GESTIONE DI UNA RISPOSTA ARRIVATA */
	/* se arriva una riposta provo a cancellare una eventuale ritrasmissione associata */
	/* (non-Javadoc)
	 * @see convFramework.traffic.event.IAckArrivedListener#responseArrived(convFramework.traffic.event.AckArrivedEvent)
	 */
	public void ackArrived(AckArrivedEvent event){
		ReTransmission reTransmission = removeReTransmission(event.getReferredPacketID());
		if (reTransmission != null)
			notifyPacketTransmitted(reTransmission.getPacket());
	}
	
	/* trasmissione di un pacchetto */
	private void notifyPacketTransmitted(Packet packet){
		PacketTransmittedEvent event = new PacketTransmittedEvent(this, packet);
		
		IPacketTransmittedListener[] listeners = eventListenerMan.getPacketTransmittedListeners();
		for(int i = 0; i < listeners.length; i++)
			listeners[i].packetTransmitted(event);		
	}
	
	/* CLASSI DI SUPPORTO */
	
	/* classe privata che modella una ritrasmissione */
	private class ReTransmission {
		
		/* campi */
		private int reTransmissionsCountDown = 0;
		private long reTransmissionDelay = 0;
		private Timer timer = null;
		private Packet packet = null;
		private TransmissionEventListenerManager eventListenerManager = null;
		
		/* costruttore */
		public ReTransmission(OutGate outGate, Packet packet, int maxReTransmissions, long reTransmissionDelay, TransmissionEventListenerManager eventListenerManager){
			/* inizializzazione dei campi */
			this.reTransmissionsCountDown = maxReTransmissions; // verrā decrementato subito
			this.reTransmissionDelay = reTransmissionDelay;
			this.packet = packet;
			//this.timer = new Timer(true);
			this.eventListenerManager = eventListenerManager;
			/* schedulazione della prima rtrasmissione */
			scheduleReTransmission();
		}
		
		/* cancellazione della schedulazione corrente */
		public synchronized void cancel(){
			if (timer != null)
				timer.cancel();
			timer = null;
			
		}
		
		/* schedula un nuova ritrasmissione */
		public synchronized void scheduleReTransmission(){
			/* se i countdown  non č a zero schedula una nova ritrasmissione */
			if (reTransmissionsCountDown > 0){
				if (timer == null) timer = new Timer(true);
				timer.schedule(new TransmissionTask(this, outGate, packet, eventListenerManager), reTransmissionDelay);
				/* decrementa il numero delle ritrasmissioni schedulabili */
				reTransmissionsCountDown--;
			}
			/* altrimenti solleva un evento di risposta mancata */
			else {
				notifyMissingResponse(packet);
			}
		}
		
		public synchronized Packet getPacket(){ return this.packet; }
		
		/* metodo di notifica di un evento di risposta mancata */
		private void notifyMissingResponse(Packet packet){
			MissingAckEvent event = new MissingAckEvent(this, packet);
			
			IMissingAckListener[] listeners = eventListenerMan.getMissingAckListeners();
			for(int i = 0; i < listeners.length; i++)
				listeners[i].missingAck(event);		
		}
		
	}
	
	/* questa classe cerca di trasmettere un pacchetto il numero di volte specficato in una ritrasmissione */
	private class TransmissionTask extends TimerTask{
		
		/* campi */
		private Packet packet = null;
		private ReTransmission parent = null;
		private OutGate outGate = null;
		private TransmissionEventListenerManager eventListenerManager = null;
		
		/* costruttore */
		public TransmissionTask(ReTransmission parent, OutGate outGate, Packet packet, TransmissionEventListenerManager eventListenerManager){
			/* inizializzazione campi */
			this.packet = packet;
			this.outGate = outGate;
			this.eventListenerManager = eventListenerManager;
			/* della ritrasmissione va tenuta traccia per rischedulare la trasmissione */
			this.parent = parent;
		}
		
		public void run(){
			try { outGate.send(packet); }
			catch(MarshallingException e){ notifyTransmissionError(packet); }
			catch(TransmissionErrorException e){ notifyTransmissionError(packet); }
			parent.scheduleReTransmission();
		}
		
		/* errore di trasmissione */
		private void notifyTransmissionError(Packet packet){
			TransmissionErrorEvent event = new TransmissionErrorEvent(this, packet);
			
			ITransmissionErrorListener[] listeners = eventListenerMan.getTransmissionErrorListeners();
			for(int i = 0; i < listeners.length; i++)
				listeners[i].transmissionError(event);
		}
		
	}
	
}
