/*
 * Created on 28-apr-2004
 */
package jaCop.traffic;

import convFramework.domain.*;
import convFramework.exceptions.*;

import jaCop.domain.*;
import jaCop.exceptions.*;

/**
 * @author Lompa
 */
public class COPPacketTranslator {
	
	/* campi */
	private ICOPEncoder encoder = null;
	private ICOPDecoder decoder = null;
	
	/**
	 * @param encoder
	 * @param decoder
	 */
	public COPPacketTranslator(ICOPEncoder encoder, ICOPDecoder decoder){
		this.encoder = encoder;
		this.decoder = decoder;
	}
	
	/**
	 * @param packet
	 * @return
	 * @throws TranslationException
	 */
	public synchronized ContentPacket translate(COPPacket packet) throws TranslationException{
		
		/* creazione pacchetto, packeID e conversationID */
		IPacketID packetID = packet.getPacketID();
		IConversationID convID = packet.getConvID(); 
		
		ContentPacket outPacket = new ContentPacket(packetID, convID);
		
		/* mittente */
		try {
			UDPNode sender = new UDPNode(packet.getSender().getAddress(), packet.getSender().getPort());
			outPacket.setSender(sender);
			
			/* destinatario */
			if (packet.getReceiver() == null)
				throw new TranslationException("Packet without servant");
				
			UDPNode receiver = new UDPNode(packet.getReceiver().getAddress(), packet.getReceiver().getPort());
			outPacket.setReceiver(receiver);
		}
		catch(convFramework.exceptions.BadInvocationArgumentException e){
			throw new TranslationException(e.getMessage());
		}
		
		/* dati di sequenza */
		outPacket.setSequence(packet.isSequence());
		outPacket.setPreviousPacket(packet.getPreviousPacket());
		
		/* dati di richiesta risposta */
		if (!packet.getType().equals(Heartbeat.getTypeDescription()))
			outPacket.setAutoAckRequired(true);
		else {
			Heartbeat hb = (Heartbeat) packet;
			if (hb.isAckRequired())
				outPacket.setAckRequired(true);
			else {
				outPacket.setAcknowledgedPacket(hb.getAckedPacket());
				outPacket.setAck(true);
			}
		}
		
		/* contenuto */
		outPacket.setContent(encoder.encode(packet));
		
		return outPacket;
	}
	
	/**
	 * @param packet
	 * @return
	 */
	public synchronized COPPacket translate(ContentPacket packet) throws TranslationException{
		
		/* recupero dei dati */
		COPPacketData store = this.decoder.decode(packet.getContent());
		
		/* controlli */
		if (store.community == null) throw new TranslationException("Invalid packet, missing community");
		if (store.senderLoadFactor == -1) throw new TranslationException("Invalid packet, missing sender load factor");
		if (store.receiverLoadFactor == -1) throw new TranslationException("Invalid packet, missing servant load factor");
		
		/* recupero dati di conversazione */
		COPNode sender = null;
		COPNode receiver = null;
		try {
			/* mittente */
			Node pSender = packet.getSender();
			sender = new COPNode(pSender.getAddress(), pSender.getPort(), store.senderLoadFactor, false);
			/* destinatario */
			Node pReceiver = packet.getReceiver();
			boolean receiverIsStructure = store.isReceiverIsStructure();
			receiver = new COPNode(pReceiver.getAddress(), pReceiver.getPort(), store.receiverLoadFactor, receiverIsStructure);
		}
		catch(convFramework.exceptions.BadInvocationArgumentException e){
			throw new TranslationException("Invalid packet: sender or servant port out of range");
		}
		IPacketID packetID = packet.getPacketID();
		IConversationID convID = packet.getConversationID();
		IPacketID previousPacket = packet.getPreviousPacket();
		boolean hasFollowing = false;
		if (previousPacket == null && packet.isSequence())
			hasFollowing = true;
		
		/* crezione pacchetto ed inizilizzazione specifica */
		COPPacket outPacket = null; 
		if (store.type.equals(JoinRequest.getTypeDescription()))
			outPacket = initJoinRequest(new JoinRequest(store.community, sender, convID, packetID), store);
		else if (store.type.equals(NewNeighbor.getTypeDescription()))
			outPacket = initNewNeighbor(new NewNeighbor(store.community, sender, convID, packetID), store);
		else if (store.type.equals(NewStructure.getTypeDescription()))
			outPacket = initNewStructure(new NewStructure(store.community, sender, convID, packetID), store);
		else if (store.type.equals(Confirmation.getTypeDescription()))
			outPacket = new Confirmation(store.community, sender, convID, packetID);
		else if (store.type.equals(Delegation.getTypeDescription()))
			outPacket = initDelegation(new Delegation(store.community, sender, convID, packetID), store);
		else if (store.type.equals(Refusal.getTypeDescription()))
			outPacket = initRefusal(new Refusal(store.community, sender, convID, packetID), store);
		else if (store.type.equals(Heartbeat.getTypeDescription()))
			outPacket = new Heartbeat(store.community, sender, convID, packetID);
		else if (store.type.equals(FaultNotification.getTypeDescription()))
			outPacket = initFaultNotification(new FaultNotification(store.community, sender, convID, packetID), store);
		else
			throw new TranslationException("Invalid packet: unknown packet type");
		
		/* inizializzazione generica */
		outPacket.setReceiver(receiver);
		outPacket.follows(previousPacket);
		outPacket.setFollowed(hasFollowing);
		
		return outPacket;
		
	}
	
	private JoinRequest initJoinRequest(JoinRequest packet, COPPacketData store) throws TranslationException{
		/* controlli */
		if (store.node == null)
			throw new TranslationException("Invalid join request: requester not specified");
		if (store.nodeList == null)
			throw new TranslationException("Invalid join request: no neighbors list specified");
		
		/* inizializzazione */
		packet.setRequester(store.node);
		packet.setRequesterStructureNeighbors(store.nodeList);
		packet.setForce(store.force);
		
		/* restituzione */
		return packet;
	}
	
	private NewNeighbor initNewNeighbor(NewNeighbor packet, COPPacketData store) throws TranslationException{
		/* controlli */
		if (store.node == null)
			throw new TranslationException("Invalid new neighbor notification: no neighbor");
		if (store.nodeList == null)
			throw new TranslationException("Invalid new neighbor notification: no neighbor structure");
		
		/* inizializzazione */
		packet.setNeighborStructure(store.nodeList);
		packet.setNeighbor(store.getNode());
		
		/* restituzione */
		return packet;
	}
	
	private NewStructure initNewStructure(NewStructure packet, COPPacketData store) throws TranslationException{
		/* controlli */
		if (store.nodeList == null)
			throw new TranslationException("Invalid new neighbor notification: no neighbors list specified");
		
		/* inizializzazione */
		packet.setNeighbors(store.nodeList);
		packet.setConnectionRank(store.getConnectionRank());
		
		/* restituzione */
		return packet;
	}
	
	private Refusal initRefusal(Refusal packet, COPPacketData store) throws TranslationException{
		/* controlli */
		if (store.message == null)
			throw new TranslationException("Invalid refusal: messaage not specified");
		
		/* inizializzazione */
		packet.setMessage(store.message);
		
		/* restituzione */
		return packet;
	}
	
	private FaultNotification initFaultNotification(FaultNotification packet, COPPacketData store) throws TranslationException{
		/* controlli */
		if (store.nodeList == null)
			throw new TranslationException("Invalid fault notification: fallen nodes not specified");
		
		/* inizializzazione */
		packet.setFallenNodes(store.nodeList);
		
		/* restituzione */
		return packet;
	}
	
	private Delegation initDelegation(Delegation packet, COPPacketData store) throws TranslationException{
		/* controlli */
		if (store.node == null)
			throw new TranslationException("Invalid delegation: fallen node not specified");
		
		/* inizializzazione */
		packet.setDelegate(store.getNode());
		
		/* restituzione */
		return packet;
	}
	
	/**
	 * @author Lompa
	 */
	public static class COPPacketData {
		
		private String type = null;
		private String community = null;
		private float senderLoadFactor = -1;
		private float receiverLoadFactor = -1;
		private boolean receiverIsStructure = false;
		private COPNode node = null;
		private COPNode[] nodeList = null;
		private String message = null;
		private boolean force = false;
		private int connectionRank = 2;
		
		/**
		 * 
		 */
		public COPPacketData(){}
		/**
		 * @return Returns the community.
		 */
		public String getCommunity() {
			return community;
		}
		/**
		 * @param community The community to set.
		 */
		public void setCommunity(String community) {
			this.community = community;
		}
		/**
		 * @return Returns the messsage.
		 */
		public String getMesssage() {
			return message;
		}
		/**
		 * @param messsage The messsage to set.
		 */
		public void setMesssage(String messsage) {
			this.message = messsage;
		}
		/**
		 * @return Returns the node.
		 */
		public COPNode getNode() {
			return node;
		}
		/**
		 * @param node The node to set.
		 */
		public void setNode(COPNode node) {
			this.node = node;
		}
		/**
		 * @return Returns the nodeList.
		 */
		public COPNode[] getNodeList() {
			return nodeList;
		}
		/**
		 * @param nodeList The nodeList to set.
		 */
		public void setNodeList(COPNode[] nodeList) {
			this.nodeList = nodeList;
		}
		/**
		 * @return Returns the senderLoadFactor.
		 */
		public float getSenderLoadFactor() {
			return senderLoadFactor;
		}
		/**
		 * @param senderLoadFactor The senderLoadFactor to set.
		 */
		public void setSenderLoadFactor(float senderLoadFactor) {
			this.senderLoadFactor = senderLoadFactor;
		}
		/**
		 * @return Returns the type.
		 */
		public String getType() {
			return type;
		}
		/**
		 * @param type The type to set.
		 */
		public void setType(String type) {
			this.type = type;
		}
		/**
		 * @return Returns the receiverLoadFactor.
		 */
		public float getReceiverLoadFactor() {
			return receiverLoadFactor;
		}
		/**
		 * @param receiverLoadFactor The receiverLoadFactor to set.
		 */
		public void setReceiverLoadFactor(float receiverLoadFactor) {
			this.receiverLoadFactor = receiverLoadFactor;
		}
		/**
		 * @return Returns the force.
		 */
		public boolean force() {
			return force;
		}
		/**
		 * @param force The force to set.
		 */
		public void setForce(boolean force) {
			this.force = force;
		}
		/**
		 * @return Returns the receiverIsStructure.
		 */
		public boolean isReceiverIsStructure() {
			return receiverIsStructure;
		}
		/**
		 * @param receiverIsStructure The receiverIsStructure to set.
		 */
		public void setReceiverIsStructure(boolean receiverIsStructure) {
			this.receiverIsStructure = receiverIsStructure;
		}
		/**
		 * @return Returns the connectionRank.
		 */
		public int getConnectionRank() {
			return connectionRank;
		}
		/**
		 * @param connectionRank The connectionRank to set.
		 */
		public void setConnectionRank(int connectionRank) {
			this.connectionRank = connectionRank;
		}
	}
	
}
