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

import java.io.*;
import javax.xml.parsers.*;
import java.net.*;

import org.w3c.dom.*;
import org.xml.sax.*;
import org.apache.xml.serialize.*;

import convFramework.facade.Loader;

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

/**
 * @author Lompa
 */
public class COPXMLEncoder implements ICOPEncoder{
	
	/* campi */
	private DocumentBuilderFactory factory = null;
	private DocumentBuilder builder = null; //TODO questo builder a scope globale deve diventare locale
	
	/**
	 * Crea un nuovo XMLMarshaller basato su DOM che tratta solamente pacchetti standard
	 */
	public COPXMLEncoder(){
		try {
		    factory = DocumentBuilderFactory.newInstance();
		}
		catch (FactoryConfigurationError e) {
		    System.out.println(e.getMessage());
		} 

	}
	
	/**
	 * Effettua il marshalling di un pacchetto standard; nella serializzaazione vonegono inclusi
	 * tutti i dati di un pacchetto standard, meno il mittente ed il destinatario
	 * @param packet il pacchetto
	 * @return il contenuto del pacchetto serializzato
	 */
	public synchronized String encode(COPPacket packet) throws TranslationException{
		try {
			builder = factory.newDocumentBuilder();
		}
		catch (ParserConfigurationException e) {
			System.out.println(e.getMessage());
		}
		return serializeDocument(buildDocument(packet));
	}
	
	/**
	 * Costruisce un documento DOM a partire da un pacchetto
	 * @param packet il pacchetto
	 * @return il documento
	 */
	protected Document buildDocument(COPPacket packet) throws TranslationException{				
		Document doc = null;
		
		if (packet.getType().equals(JoinRequest.getTypeDescription()))
			doc = buildJoinRequest(packet);
		else if (packet.getType().equals(NewNeighbor.getTypeDescription()))
			doc = buildNewNeighbors(packet);
		else if (packet.getType().equals(NewStructure.getTypeDescription()))
			doc = buildNewStructure(packet);
		else if (packet.getType().equals(Confirmation.getTypeDescription()))
			doc = buildConfirmation(packet);
		else if (packet.getType().equals(Delegation.getTypeDescription()))
			doc = buildDelegation(packet);
		else if (packet.getType().equals(Refusal.getTypeDescription()))
			doc = buildRefusal(packet);
		else if (packet.getType().equals(Heartbeat.getTypeDescription()))
			doc = buildHeartbeat(packet);
		else if (packet.getType().equals(FaultNotification.getTypeDescription()))
			doc = buildFaultNotification(packet);
		/* se arrivo fin qui: errore */
		else
			new TranslationException("Invalid packet type");
		
		/* restituzione del documento */
		return doc;
		
	}
	
	private Document buildJoinRequest(COPPacket packet){
		Node currentNode = null;
		
		/* creazione di un nuovo documento */
		Document doc = builder.newDocument();		
		/* casting */
		JoinRequest p = (JoinRequest) packet;
		/* aggiunta dell'elemento di root */
		currentNode = addElement(doc, JoinRequest.getTypeDescription(), doc);
		/* aggiunta del nome della comunità */
		addTextElement(currentNode, "community", p.getCommunity(), doc);
		/* aggiunta del fattore di carico del mittente */
		addSenderAndReceiverData(currentNode, packet, doc);
		/* aggiunta dati del nodo che effettua la richiesta */
		addCOPNode(currentNode, p.getRequester(), doc);
		/* aggiunta flag di force */
		if (p.force()){
			addElement(currentNode, "force", doc);
		}
		/* aggiunta lista dei vicini */
		addCOPNodeList(currentNode, p.getRequesterStructureNeighbors(), doc);
		
		return doc;
	}

	private Document buildNewNeighbors(COPPacket packet){
		Node currentNode = null;
		
		/* creazione di un nuovo documento */
		Document doc = builder.newDocument();		
		/* casting */
		NewNeighbor p = (NewNeighbor) packet;
		/* aggiunta dell'elemento di root */
		currentNode = addElement(doc, NewNeighbor.getTypeDescription(), doc);
		/* aggiunta del nome della comunità */
		addTextElement(currentNode, "community", p.getCommunity(), doc);
		/* aggiunta del fattore di carico del mittente */
		addSenderAndReceiverData(currentNode, packet, doc);
		/* aggiunta del nuovo vicino*/
		addCOPNode(currentNode, p.getNeighbor(), doc);
		/* aggiunta della struttura del vicino */
		addCOPNodeList(currentNode, p.getNeighborStructure(), doc);
		
		return doc;
	}
	
	private Document buildNewStructure(COPPacket packet){
		Node currentNode = null;
		
		/* creazione di un nuovo documento */
		Document doc = builder.newDocument();		
		/* casting */
		NewStructure p = (NewStructure) packet;
		/* aggiunta dell'elemento di root */
		currentNode = addElement(doc, NewStructure.getTypeDescription(), doc);
		/* aggiunta del nome della comunità */
		addTextElement(currentNode, "community", p.getCommunity(), doc);
		/* aggiunta del fattore di carico del mittente */
		addSenderAndReceiverData(currentNode, packet, doc);
		/* aggiunta vicini */
		addCOPNodeList(currentNode, p.getNeighbors(), doc);
		/* aggiunta del grado di connesssione */
		addTextElement(currentNode, "connectionRank", ""+p.getConnectionRank(), doc);
		
		return doc;
	}
	
	private Document buildConfirmation(COPPacket packet){
		Node currentNode = null;
		
		/* creazione di un nuovo documento */
		Document doc = builder.newDocument();		
		/* aggiunta dell'elemento di root */
		currentNode = addElement(doc, Confirmation.getTypeDescription(), doc);
		/* aggiunta del nome della comunità */
		addTextElement(currentNode, "community", packet.getCommunity(), doc);
		/* aggiunta del fattore di carico del mittente */
		addSenderAndReceiverData(currentNode, packet, doc);
		
		return doc;
	}
	
	private Document buildDelegation(COPPacket packet){
		Node currentNode = null;
		
		/* creazione di un nuovo documento */
		Document doc = builder.newDocument();
		/* casting */
		Delegation p = (Delegation) packet;
		/* aggiunta dell'elemento di root */
		currentNode = addElement(doc, Delegation.getTypeDescription(), doc);
		/* aggiunta del nome della comunità */
		addTextElement(currentNode, "community", packet.getCommunity(), doc);
		/* aggiunta del fattore di carico del mittente */
		addSenderAndReceiverData(currentNode, packet, doc);
		/* aggiunta nodo delegato */
		addCOPNode(currentNode, p.getDelegate(), doc);
		
		return doc;
	}
	
	private Document buildRefusal(COPPacket packet){
		Node currentNode = null;
		
		/* creazione di un nuovo documento */
		Document doc = builder.newDocument();		
		/* casting */
		Refusal p = (Refusal) packet;
		/* aggiunta dell'elemento di root */
		currentNode = addElement(doc, Refusal.getTypeDescription(), doc);
		/* aggiunta del nome della comunità */
		addTextElement(currentNode, "community", p.getCommunity(), doc);
		/* aggiunta del fattore di carico del mittente */
		addSenderAndReceiverData(currentNode, packet, doc);
		/* aggiunta del messaggio */
		addTextElement(currentNode, "message", p.getMessage(), doc);
		
		return doc;
	}
	
	private Document buildHeartbeat(COPPacket packet){
		Node currentNode = null;
		
		/* creazione di un nuovo documento */
		Document doc = builder.newDocument();		
		/* aggiunta dell'elemento di root */
		currentNode = addElement(doc, Heartbeat.getTypeDescription(), doc);
		/* aggiunta del nome della comunità */
		addTextElement(currentNode, "community", packet.getCommunity(), doc);
		/* aggiunta del fattore di carico del mittente */
		addSenderAndReceiverData(currentNode, packet, doc);
		
		return doc;
	}
	
	private Document buildFaultNotification(COPPacket packet){
		Node currentNode = null;
		
		/* creazione di un nuovo documento */
		Document doc = builder.newDocument();		
		/* casting */
		FaultNotification p = (FaultNotification) packet;
		/* aggiunta dell'elemento di root */
		currentNode = addElement(doc, FaultNotification.getTypeDescription(), doc);
		/* aggiunta del nome della comunità */
		addTextElement(currentNode, "community", p.getCommunity(), doc);
		/* aggiunta del fattore di carico del mittente */
		addSenderAndReceiverData(currentNode, packet, doc);
		/* aggiunta vicini */
		addCOPNodeList(currentNode, p.getFallenNodes(), doc);
		
		return doc;
	}
	
	private void addSenderAndReceiverData(Node node, COPPacket packet, Document doc){
		addTextElement(node, "senderLoadFactor", ""+packet.getSender().getLoadFactor(), doc);
		addTextElement(node, "receiverLoadFactor", ""+packet.getReceiver().getLoadFactor(), doc);
		if (packet.getReceiver().isStructure())
			addElement(node, "receiverIsStructure", doc);
	}
	
	private void addCOPNode(Node node, COPNode cNode, Document doc){
		Node currentNode = addElement(node, "node", doc);
		addTextElement(currentNode, "address", cNode.getAddress().getHostAddress(), doc);
		addTextElement(currentNode, "port", ""+cNode.getPort(), doc);
		addTextElement(currentNode, "loadFactor", ""+cNode.getLoadFactor(), doc);
		if (cNode.isStructure())
			addElement(currentNode, "structure", doc);
	}
	
	private void addCOPNodeList(Node node, COPNode[] cNodes, Document doc){
		Node currentNode = addElement(node, "nodeList", doc);
		for(int i = 0; i < cNodes.length; i++)
			addCOPNode(currentNode, cNodes[i], doc);
	}
	
	/**
	 * Aggiunge un elemento al nodo indicato
	 * @param targetNode il nod cui aggiungere l'elemento
	 * @param elementName il nome dell'elemento da aggiungere
	 * @param doc il documento in cui creare il nodo
	 * @return l'elemento aggiunto
	 */
	private Node addElement(org.w3c.dom.Node targetNode, String elementName, Document doc){
		return targetNode.appendChild(doc.createElement(elementName));
	}
	
	/**
	 * Aggiunge un nodo di testo al nodo corrente
	 * @param targetNode il nod bersaglio
	 * @param text il testo da aggiungere
	 * @param doc il documenot in cui creare il nodo
	 */
	private void addTextNode(org.w3c.dom.Node targetNode, String text, Document doc){
		targetNode.appendChild(doc.createTextNode(text));
	}
	
	/**
	 * Aggiunge un nodo CDATA al nodo corrente
	 * @param targetNode il nod bersaglio
	 * @param text il testo da aggiungere
	 * @param doc il documenot in cui creare il nodo
	 */
	private void addCDataNode(org.w3c.dom.Node targetNode, String text, Document doc){
		targetNode.appendChild(doc.createCDATASection(text));
	}
	
	/**
	 * Aggiunge un elemento al nodo bersaglio e un nodo di testo al suo unterno
	 * @param targetNode i nodo bersaglio
	 * @param elementName il nome dell'elemento da aggiungere
	 * @param text il testo da inserire come contenuto
	 * @param doc il documento in cui viene creato il tutto
	 */
	private void addTextElement(org.w3c.dom.Node targetNode, String elementName, String text, Document doc){
		/* aggiunta elemento */
		org.w3c.dom.Node currentNode = addElement(targetNode, elementName, doc);
		/* aggiunta valore */
		addTextNode(currentNode, text, doc);
	}
	
	/**
	 * Aggiunge un elemento al nodo bersaglio e un nodo CDATA al suo unterno
	 * @param targetNode i nodo bersaglio
	 * @param elementName il nome dell'elemento da aggiungere
	 * @param text il testo da inserire come contenuto
	 * @param doc il documento in cui viene creato il tutto
	 */
	private void addCDataElement(org.w3c.dom.Node targetNode, String elementName, String text, Document doc){
		/* aggiunta elemento */
		org.w3c.dom.Node currentNode = addElement(targetNode, elementName, doc);
		/* aggiunta valore */
		addCDataNode(currentNode, text, doc);
	}
	
	/**
	 * Converte un Documento DOM in una sequeza di byte
	 * @param document il documento
	 * @return il documento serializzato
	 */
	private String serializeDocument(Document document){
		
		/* impostazioni del formato di utput */
		OutputFormat format = new OutputFormat((Document)document);
		format.setLineSeparator(LineSeparator.Windows);
		format.setIndenting(true);
		format.setLineWidth(0);             
		format.setPreserveSpace(true);
		
		/* costruzione di un serializzatore */
		StringWriter writer = null;
		try {
			writer = new StringWriter(); 
			XMLSerializer serializer = new XMLSerializer (writer, format);
			serializer.asDOMSerializer();
			serializer.serialize(document);
		}
		catch(IOException e){
			System.out.println("Unexpected system error");
			e.printStackTrace();
			System.exit(1);
		}
		
		/* restituzione del risultato */
		return writer.toString();
		
	}		

}
