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

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

import convFramework.domain.*;

import java.util.*;

/**
 * @author Lompa
 */
public class JoinRequestServer extends COPTask{
	
	/* questi campi esistono solo per passared dei parametri al metodo di terminazione */
	private boolean parOpSuccessfull = false;
	private COPNode parRequester = null;
	private COPNode[] parYeldedNeighbors = null;
	private COPPacket parRequesterNotification = null;
	
	/**
	 * @param community
	 * @param conv
	 * @param packetMan
	 * @param statusStore
	 * @param convGraveyard
	 * @param waitingTimeout
	 * @param connectionRank
	 * @param maxDelegationsNumber
	 */
	public JoinRequestServer(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);
	}
	
	/* metodo body */
	protected void body() throws InterruptedException, TranslationException{
		/* RICEZIONE RICHIESTA */
		jaCop.Debug.print("\nJOIN REQUEST SERVER: "+convID.encode());
		//jaCop.Debug.print("struttura: " + (statusMan.hasCompleteStructure(community) ? "completa\n" : "rotta\n"));		
		
		//System.out.println("SERVER RICHIESTA DI JOIN");
		/* ricezione pacchetto */
		COPPacket packet = receive();
		/* controllo tipo */
		if (!packet.getType().equals(JoinRequest.getTypeDescription())){
			return;
		}
		/* casting */
		JoinRequest req = (JoinRequest) packet;
		/* recupero dati */
		COPNode requester = req.getRequester();
		parRequester = requester;
		boolean force = req.force();
		/* recupero dei vicini di struttura e giuntati */
		COPNode[] reqStructure = req.getRequesterStructureNeighbors();
		
		/* REGISTRAZIONE */
		registerConverser(requester);
		
		/* SE IL NODO NON E' LA TESTA E NON HA PUNTO DI GIUNZIONE SI METTE IN ATTESA */
		//jaCop.Debug.print("attesa che la struttura sia completa");
		//jaCop.Debug.print("struttura: " + (statusMan.hasCompleteStructure(community) ? "completa" : "rotta"));
		waitForStructureComplete();
		//jaCop.Debug.print("struttura: " + (statusMan.hasCompleteStructure(community) ? "completa" : "rotta"));
		
		//System.out.println("richiesta ricevuta: " + req.getRequester().getPort() + " sender: " + req.getSender().getPort() + " lf: " + req.getSender().getLoadFactor());
		
		/* controllo che la comunità sia esistente */
		if (!statusMan.existsCommunity(community)){
			/* RIFIUTO */
			/* preparazione rifiuto */
			Refusal refusal = packetMan.newRefusal(convID, community);
			refusal.setMessage("unexisting community");
			refusal.setReceiver(requester);
			/* invio */
			send(refusal);
			/* terminazione */
			return;
		}
		
		/* RISPOSTA */
		boolean existsBetter = false;
		COPNode firstNeighbor = null;
		/* controllo se esiste un vicino con fattore di carico minore */
		COPNode[] currentNeighbors =  COPNode.sort(statusMan.getNeighbors(community));
		for(int i = 0; i < currentNeighbors.length; i++){
			if (!currentNeighbors[i].isSame(requester) && !existsBetter){
				existsBetter = currentNeighbors[i].getLoadFactor() < statusMan.getCurrentLoadFactor(community);
				firstNeighbor = currentNeighbors[i];
			}
			//BETTER usando un break fa un po' prima, probabilmente
		}
		if (existsBetter && !force){
			/* DELEGA */
			/*  preparazione notifica di delega*/
			Delegation delegation = packetMan.newDelegation(convID, community);
			delegation.setDelegate(firstNeighbor);
			delegation.setReceiver(requester);
			/* invio */
			send(delegation);
			//System.out.println("delega inviata: " + delegation.getDelegate().getPort());
			/* terminazione */
			return;
		}
		/* se non ci sono vicini con fattore di carico minore la richesta viene accettata */
		/* CONFERMA */
		/* preparazione conferma */
		//Confirmation confirmation = packetMan.newConfirmation(convID, community);
		//confirmation.setReceiver(requester);
		//confirmation.setFollowed(true);
		/* invio */
		//send(confirmation);
		
		/* CALCOLO DEI NODI DI STRUTTURA DA PASSARE */
		COPNode[] yeldedStructure = getStructureToYeld(requester, reqStructure);
		parYeldedNeighbors = yeldedStructure;
		
		/* NOTIFICA DEL RICHIEDENTE */
		/* preparazione pacchetto */
		NewStructure reqNotification = packetMan.newNewStructure(convID, community);
		reqNotification.setNeighbors(yeldedStructure);
		reqNotification.setConnectionRank(statusMan.getConnectionRank(community));
		reqNotification.setReceiver(requester);
		//reqNotification.follows(confirmation.getPacketID());
		parRequesterNotification = reqNotification;
		/* invio pacchetto */
		send(reqNotification);
		
		/* DEBUG */
		//jaCop.Debug.print(convID.encode()+" - STRUTTURA CORRENTE: "+requester.getPort());
		//COPNode[] cStruct = statusMan.getStructureNeighbors(community);
		//for(int i = 0; i < cStruct.length; i++){
		//	jaCop.Debug.print("nodo: "+cStruct[i].getPort());
		//}	
		//jaCop.Debug.print(convID.encode()+" - STRUTTURA INVIATA A "+requester.getPort()+":");
		//for(int i = 0; i < yeldedStructure.length; i++){
		//	jaCop.Debug.print("nodo: "+yeldedStructure[i].getPort());
		//}		
		
		/* PREPARAZIONE PER LE NOTIFICHE */
		/* registrazione per i nuovi interlocutori */
		for(int i = 0; i < yeldedStructure.length; i++)
			registerConverser(yeldedStructure[i]);
		/* contatore delle risposte */
		int ansCount = 0;
		
		/* NOTIFICA DEI NODI SCELTI */
		for(int i = 0; i< yeldedStructure.length; i++){
				/* INVIO NOTIFICA */
				/* determinazione della struttura da notificare */
				COPNode[] nodeStruct =  getNodesToBeNotified(statusMan.getCurrentNode(community), yeldedStructure);
				/* preparazione del pacchetto */
				NewNeighbor notification = packetMan.newNewNeighbors(convID, community);
				notification.setNeighborStructure(nodeStruct);
				notification.setNeighbor(requester);
				notification.setReceiver(yeldedStructure[i]);
				/* invio */
				send(notification);
				/* incremento del contatore */
				ansCount++;
		}
		
		/* 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 eseguite in caso di errore: si dovrebbe ripristinare la situaizone iniziale
			}
		}
		
		/* CONTROLLO CHE LA STRUTTURA INVIATA SIA ANCORA VALIDA */
		COPNode[] structPool = buildNodePool(requester);
		parOpSuccessfull = (COPNode.intersection(yeldedStructure, structPool).length == yeldedStructure.length);
		
		/* INVIO DEI MESSAGI DI CONFIRMA O DI ABORT */
		if (parOpSuccessfull){
			Confirmation commit = null;
			/* conferma a tutti i nodi di struttura scelti */
			for(int i = 0; i < yeldedStructure.length; i++){
				commit = packetMan.newConfirmation(convID, community);
				commit.setReceiver(yeldedStructure[i]);
				send(commit);
			}
			/* conferma al richiedente */
			commit = packetMan.newConfirmation(convID, community);
			commit.setReceiver(requester);
			commit.follows(reqNotification.getPacketID());
			send(commit);
		}
		else {
			Refusal abort = null;
			/* abort a tutti i nodi di struttura scelti */
			for(int i = 0; i < yeldedStructure.length; i++){
				abort = packetMan.newRefusal(convID, community);
				abort.setReceiver(yeldedStructure[i]);
				abort.setMessage("operation failed");
				send(abort);
			}
			/* abort al richiedente */
			abort = packetMan.newRefusal(convID, community);
			abort.setReceiver(requester);
			abort.setMessage("operation failed");
			abort.follows(reqNotification.getPacketID());
			send(abort);
			/* terminazione del client */
			return;
		}	
		
		/* AGGIORNAMENTI () */
		/* AGGIUNTA DEL VICINO */
		/* aggiunta di un nuovo vicino */
		if (!statusMan.isNeighbor(community, requester)){
			statusMan.addNeighbor(community, requester, true, false);
			/* NOTIFICHE */
			this.notifyNewNeighbor(community, requester);
		}
		else if (!statusMan.isJoined(community, requester)){
			statusMan.setJoined(community, requester, true);
			statusMan.setStructure(community, requester, false);
		}
		/* AGGIUNTA DELLA STRUTTURA DEL VICINO */
		statusMan.setStructureNeighbors(community, requester, yeldedStructure);
		
	}
	
	/* metodi privati di utilità *****************************************************************************/
	private COPNode get(COPNode[] nodes, COPNode node){
		for(int i = 0; i< nodes.length; i++)
			if (nodes[i].isSame(node))
				return nodes[i];
		
		return null;
	}
	
	protected void terminate(){
		/* se l'operazionenon è terminata con successo invio un pacchettodi abort */
		if ((!parOpSuccessfull || isInterrupted()) && parRequesterNotification != null ){
			/* non è necessario, ma evita perdite di tempo */
			Refusal abort = null;
			/* abort a tutti i nodi di struttura scelti */
			for(int i = 0; i < parYeldedNeighbors.length; i++){
				abort = packetMan.newRefusal(convID, community);
				abort.setReceiver(parYeldedNeighbors[i]);
				abort.setMessage("operation failed");
				send(abort);
			}
			/* abort al richiedente */
			abort = packetMan.newRefusal(convID, community);
			abort.setReceiver(parRequester);
			abort.setMessage("operation failed");
			abort.follows(parRequesterNotification.getPacketID());
			send(abort);
		}
		/* terminazione */
		super.terminate();
	}
	
	
	private COPNode[] getNodesToBeNotified(COPNode newJuncture, COPNode[] yeldedStructure){
		Vector buff = new Vector();
		//WARNING: il primo nodo è sempre il punto di giunzione di giunzione
		buff.add(newJuncture);
		for(int k = 0; k < yeldedStructure.length; k++){
				buff.add(yeldedStructure[k]);
		}
		return (COPNode[]) buff.toArray(new COPNode[buff.size()]);
	}	
	
	/* costruisce il pool di nodi per un nodo target */
	private COPNode[] buildNodePool(COPNode target){		
		/* crea una copia dell'insieme dei vicini */
		COPNode[] tempPool = statusMan.getNeighbors(community);;
		/* buffer per i nodi dipendenti dal target da eliminare a questo passaggio */
		COPNode[] currentDependentNodes = { target };
		/* inizialmente solo il target */
		/* inizial il ciclo */
		boolean noMoreDependentNodes = false;
		while(!noMoreDependentNodes){
			/* buffer per i nodi da elimnare al passaggio successivo */
			Vector newDependentNodes = new Vector();
			/* ciclo */
			for(int i = 0; i < tempPool.length; i++){
				if (tempPool[i] != null){
					if (COPNode.contains(currentDependentNodes, tempPool[i])){
						tempPool[i] = null;
					}
					else {
						COPNode[] nodeStructure = statusMan.getStructureNeighbors(community, tempPool[i]);
						if (COPNode.intersection(currentDependentNodes, nodeStructure).length > 0){
							newDependentNodes.add(tempPool[i]);
						}
					}
				}
			}
			
			if (newDependentNodes.size() == 0)
				noMoreDependentNodes = true;
			else
				currentDependentNodes = (COPNode[]) newDependentNodes.toArray(new COPNode[newDependentNodes.size()]);
		}
		
		/* preparazione del risultato */
		Vector buff = new Vector();
		for(int i = 0; i < tempPool.length; i++){
			if (tempPool[i] != null) buff.add(tempPool[i]);
		}
		
		/* restituzione */
		return (COPNode[]) buff.toArray(new COPNode[buff.size()]);
		
	}
	
	public COPNode[] getStructureToYeld(COPNode requester, COPNode[] reqStructure){
		/* preparazione buffer per la struttura */
		Vector buff = new Vector();
		/* calcolo del pool base dei nodi utilizzabili come struttura */
		COPNode[] basePool = buildNodePool(requester);
		/* calcolo della dimensione della struttura da passare e dei nodi di struttura riciclabili */
		int connectionRank = statusMan.getConnectionRank(community);
		int structureCount = connectionRank - 1;
		for(int i = 0; i < basePool.length; i++)
			if (COPNode.contains(reqStructure, basePool[i])) {
				buff.add(basePool[i]);
			}
		/* scrematura del pool, vanno tolti tutti i vicini del richiedente */
		COPNode[] structurePool = COPNode.difference(basePool, reqStructure);
		/* ordinamento */
		structurePool = COPNode.sort(structurePool);
		/* scelta dei nodi da passare */
		for(int i = 0; i < structurePool.length && buff.size() < structureCount; i++){
			buff.add(structurePool[i]);
		}
		/* conversione */
		COPNode[] yeldedStructure = (COPNode[]) buff.toArray(new COPNode[buff.size()]);
		
		return yeldedStructure;
	}
	
	public static class ConfigurationSet extends COPTask.ConfigurationSet{
		
		public ConfigurationSet(String community, IConversationID convID){
			super(community, convID);
		}
	}
	
}
