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

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

import convFramework.domain.*;

import java.util.*;

/**
 * @author Lompa
 */
public class JoinRequestClient extends COPTask{
	
	/* campi */
	protected int maxDelegationsNumber = 2;
	protected ILoadFactorCalculator loadFactorCalculator = null;
	protected COPNode receiver = null;
	
	public static final int UNBOUNDED = -1;
	public static final int FORCE = -2;
	
	/**
	 * @param community
	 * @param conv
	 * @param packetMan
	 * @param statusStore
	 * @param convGraveyard
	 * @param waitingTimeout
	 * @param connectionRank
	 * @param maxDelegationsNumber
	 */
	public JoinRequestClient(ConfigurationSet conf,
			COPPacketManager packetMan, StatusManager statusMan, COPEventListenerManager listenerMan,
			ConversationGraveyard convGraveyard, ControlSystem queue, TaskConversersList taskList, 
			boolean isClient, boolean priority) {
		super(conf, packetMan, listenerMan, statusMan, convGraveyard, queue, taskList, isClient, priority);
		this.maxDelegationsNumber = conf.maxDelegationsNumber;
		this.receiver = conf.receiver;
		this.loadFactorCalculator = conf.loadFactorCalculator;
	}
	
	
	/* metodo body */
	protected void body() throws TranslationException, InterruptedException{
		jaCop.Debug.print("\nJOIN REQUEST CLIENT: "+convID.encode());
		
		/* registrazione del client */
		registerConverser(receiver);
		
		/* recupero del numero di fratture di struttura */
		int structureBreaks = 0;
		if (statusMan.existsCommunity(community)){
			structureBreaks = statusMan.getStructureBreaksNumber(community);
		}
		
		/* INVIO RICHIESTA */
		/* costruzione richiesta */
		JoinRequest req = null;
		req = packetMan.newJoinRequest(convID, community);
		if (maxDelegationsNumber == FORCE){
			req.setForce(true);
		}
		req.setRequester(statusMan.getCurrentNode(community));
		req.setRequesterStructureNeighbors(statusMan.getStructureNeighbors(community));
		req.setReceiver(receiver);
		/* invio */
		send(req);
		
		/* ATTESA RISPOSTA */
		boolean answered = false;
		COPPacket ans = null;
		while(!answered){
			/* ricezione */
			ans = this.receive();
			/* controllo del tipo */
			/* caso 1 rifiuto */
			/* ATTENZIONE: UN RIFIUTO NON DOVREBBE PIU' ESSERE POSSIBILE */
			if (ans.getType().equals(Refusal.getTypeDescription())){
				/* casting */
				Refusal refusal = (Refusal) ans;
				/* notifica del rifiuto */
				this.notifyJonRequestRefused(community, refusal.getSender(), refusal.getMessage());
				/* terminazione della conversazione */
				return;
			}
			/* caso 2: delega */
			else if (ans.getType().equals(Delegation.getTypeDescription())){
				/* casting */
				Delegation delegation = (Delegation) ans;
				/* altrimenti controllo il massimo numero di deleghe accettabili */
				if (maxDelegationsNumber > 0 || maxDelegationsNumber == UNBOUNDED){
					/* riduco il numero di richieste accettabili */
					if (maxDelegationsNumber > 0) maxDelegationsNumber--;
					//WARNING dopo un tot di deleghe provo un force
					if (maxDelegationsNumber == 0) maxDelegationsNumber = FORCE;
					/* modifico il servant */
					receiver = delegation.getDelegate();
					/* termino la vecchia conversazione */
					convGraveyard.bury(convID);
					/* modifico l'id della conversazione */
					convID = packetMan.newConversation().getConversationID();
					/* riavvio */
					body();
					/* ritorno */
					return;
				}
				/* altrimenti è come un rifiuto */
				this.notifyJonRequestRefused(community, delegation.getSender(), "too many delegations");
				return;
			}
			/* caso 3: risposta affermativa */
			//else if (ans.getType().equals(Confirmation.getTypeDescription())){
				/* casting */
			//	Confirmation confirmation = (Confirmation) ans;
				/* interruzione ciclo */
			//	answered = true;
			//}
			else if (ans.getType().equals(NewStructure.getTypeDescription())){
				/* interruzione ciclo */
				answered = true;
			}
			/* pacchetto inatteso */
			else {
				return;
			}
		}
		
		/* ATTESA NUOVI VICINI */
		/* ricezione */
		//COPPacket ans = null;
		//ans = this.receiveAndFree();
		/* controllo */
		if (!ans.getType().equals(NewStructure.getTypeDescription())){
			return;
		}
		/* recupero dati */
		COPNode[] yeldedNeighbors = ((NewStructure) ans).getNeighbors();
		
		/* DEBUG */
		//jaCop.Debug.print(convID.encode()+" - STRUTTURA RICEVUTA DA "+ans.getSender().getPort()+":");
		//for(int i = 0; i < yeldedNeighbors.length; i++){
		//	jaCop.Debug.print("nodo: "+yeldedNeighbors[i].getPort());
		//}
		
		//TODO qui si potrebbero disabilitare le notifiche da parte dei nodi di struttura perduti
		
		/* ATTESA DEL MESSAGGIO DI COMMIT */
		/* ricezione */
		COPPacket p2 = receive();
		/* controllo del tipo */
		if (!p2.getType().equals(Confirmation.getTypeDescription())){
			return;
		}
		
		/* nodi giuntati */
		COPNode[] joinedNeighbors = statusMan.getJoinedNeighbors(community);
		/* nuovo punto di giunzione */
		COPNode newJuncture = ans.getSender();
		/* recupero della vecchia struttura */
		COPNode[] oldStructure = statusMan.getStructureNeighbors(community);
		/* calcolo dei nodi della vecchia struttura non riciclati */
		COPNode[] lostNodes = COPNode.difference(oldStructure, yeldedNeighbors);
		COPNode[] ja = { newJuncture };
		lostNodes = COPNode.difference(lostNodes, ja);
		
		/* AGGIORNAMENTI */
		/* aggiunta comunità */
		int connectionRank = ((NewStructure) ans).getConnectionRank();
		if (!statusMan.existsCommunity(community)){
			statusMan.addCommunity(community, loadFactorCalculator, connectionRank);
			this.notifyCommunityJoined(community);
		}
		/* aggiunta punto di giunzione */
		if (!statusMan.isNeighbor(community, newJuncture)){
			statusMan.addNeighbor(community, newJuncture, false, true);
			this.notifyNewNeighbor(community, newJuncture);
		}
		if (statusMan.getJuncturePoint(community) == null ||
			!statusMan.getJuncturePoint(community).isSame(newJuncture)){
			statusMan.setStructure(community, ans.getSender(), true); //BETTER questa riga non dovrebbe servire
			statusMan.setJuncturePoint(community, ans.getSender());
			this.notifyNewJuncture(community, ans.getSender());
		}
		
		/* AGGIORNAMENTO STRUTTURA */
		/* aggiunta della nuova struttura */
		for(int i = 0; i < yeldedNeighbors.length; i++){
			if (!statusMan.isNeighbor(community, yeldedNeighbors[i])){
				statusMan.addNeighbor(community, yeldedNeighbors[i], false, true);
				this.notifyNewNeighbor(community, yeldedNeighbors[i]);
			}
			else {
				statusMan.setStructure(community, yeldedNeighbors[i], true);
			}
		}
		/* eliminazione della vecchia struttura non riciclata */
		/*WARNING in questo modo nel giro di alcuni secondi i nodi di struttura si accorgeranno della caduta
		 * del vicino */
		for(int i = 0; i < lostNodes.length; i++) {
			if (!lostNodes[i].isSame(statusMan.getJuncturePoint(community))){
				/* rimozione */
				statusMan.setStructure(community, lostNodes[i], false);
				statusMan.removeNeighbor(community, lostNodes[i]);
			}
		}
		
		/* SEGANALAZIONE CHE IL NODO HA UNA STRUTTURA INTEGRA */
		statusMan.repairStructure(community, structureBreaks);
		this.signalStructureComplete();
		
		/* ELIMINAZIONE DEGLI EVENTUALI VICINI ACQUISITI DURANTE L'OPERAZIONE DI JOIN DAI NODI
		 * DI STRUTTURA NON RICICLATI */
		COPNode[] neighbors = statusMan.getNeighbors(community);
		for(int i = 0; i < neighbors.length; i++){
			/* cerco ci ndoi che hanno come punto di giunzione un nodo caduto */
			COPNode[] nodeStruct = statusMan.getStructureNeighbors(community, neighbors[i]); 
			if (nodeStruct.length > 0 && COPNode.contains(lostNodes, nodeStruct[0] )){
				/* lirimuovo: si accorgeranno da loro della perdita del vicino */
				statusMan.removeNeighbor(community, neighbors[i]);
			}
		}
		
		/* PREPARAZIONE ALLE NOTIFICHE */
		/* registrazione per i nuovi interlocutori */
		for(int i = 0; i < joinedNeighbors.length; i++)
			registerConverser(joinedNeighbors[i]);
		/* contatore delle risposte */
		int ansCount = 0;
		
		/* NOTIFICA DEL CAMBIO DI STRUTTURA AI NODI GIUNTATI */
		/* eliminazione e notifica */
		if (lostNodes.length > 0){
			for(int i = 0; i < joinedNeighbors.length; i++) {
					/* NOTIFICA */
					/* preparazione pacchetto */
					FaultNotification faultNotification = packetMan.newFaultNotification(convID, community);
					faultNotification.setFallenNodes(lostNodes);
					faultNotification.setReceiver(joinedNeighbors[i]);
					/* invio */
					send(faultNotification);
					/* incremento del contatore */
					ansCount++;
			}
		}
		
		//WARNING non dovrebbe essere necessario attendere le conferme
		/* ATTESA DELLE CONFERME */
		//for(int i = 0; i < ansCount; i++){
		//	COPPacket res = receive();
		//	if (!res.getType().equals(Confirmation.getTypeDescription())){
		//		return; //FIXME vanno corrette tutte le azioni esguite in caso di errore: si dovrebbe ripristinare la situaizone iniziale
		//	}
		//}
		
	}
	
	public static class ConfigurationSet extends COPTask.ConfigurationSet{
		
		private int maxDelegationsNumber = 2;
		private ILoadFactorCalculator loadFactorCalculator = null;
		private COPNode receiver = null;
		
		public ConfigurationSet(String community, IConversationID convID){
			super(community, convID);
		}
		
		/**
		 * @return Returns the maxDelegationsNumber.
		 */
		public int getMaxDelegationsNumber() {
			return maxDelegationsNumber;
		}
		/**
		 * @param maxDelegationsNumber The maxDelegationsNumber to set.
		 */
		public void setMaxDelegationsNumber(int maxDelegationsNumber) {
			this.maxDelegationsNumber = maxDelegationsNumber;
		}
		/**
		 * @return Returns the servant.
		 */
		public COPNode getReceiver() {
			return receiver;
		}
		/**
		 * @param servant The servant to set.
		 */
		public void setReceiver(COPNode receiver) {
			this.receiver = receiver;
		}
		/**
		 * @return Returns the loadFactorCalculator.
		 */
		public ILoadFactorCalculator getLoadFactorCalculator() {
			return loadFactorCalculator;
		}
		/**
		 * @param loadFactorCalculator The loadFactorCalculator to set.
		 */
		public void setLoadFactorCalculator(
				ILoadFactorCalculator loadFactorCalculator) {
			this.loadFactorCalculator = loadFactorCalculator;
		}
	}
}
