/*
 * Created on 27-apr-2004
 */
package a3.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 a3.exceptions.*;
import a3.domain.*;

/**
 * @author Lompa
 */
public class A3XMLEncoder implements IA3Encoder{
	
	/* campi */
	private DocumentBuilderFactory factory = null;
	private DocumentBuilder builder = null; //BETTER questo builder a scope globale deve diventare locale
	
	/**
	 * Crea un nuovo XMLMarshaller basato su DOM che tratta solamente pacchetti standard
	 */
	public A3XMLEncoder(){
		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(A3Packet 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(A3Packet packet) throws TranslationException{				
		Document doc = null;
		
		/* gestione A3 */
		if (packet.getType().equals(A3Stop.getTypeDescription()))
			doc = buildA3Stop(packet);
		else if (packet.getType().equals(A3Structure.getTypeDescription()))
			doc = buildA3Structure(packet);
		else if (packet.getType().equals(A3StructureRequest.getTypeDescription()))
			doc = buildA3StructureRequest(packet);
		/* se arrivo fin qui: errore */
		else
			new TranslationException("Invalid packet type");
		
		/* restituzione del documento */
		return doc;
		
	}
	
	private Document buildA3Stop(A3Packet packet){
		Node currentNode = null;
		
		/* creazione di un nuovo documento */
		Document doc = builder.newDocument();		
		/* casting */
		A3Stop p = (A3Stop) packet;
		/* aggiunta dell'elemento di root */
		currentNode = addElement(doc, A3Stop.getTypeDescription(), doc);
		/* aggiunta del nome della comunità */
		addTextElement(currentNode, "community", p.getCommunity(), doc);
		
		return doc;
	}
	
	private Document buildA3Structure(A3Packet packet){
		Node currentNode = null;
		
		/* creazione di un nuovo documento */
		Document doc = builder.newDocument();		
		/* casting */
		A3Structure p = (A3Structure) packet;
		/* aggiunta dell'elemento di root */
		currentNode = addElement(doc, A3Structure.getTypeDescription(), doc);
		/* aggiunta del nome della comunità */
		addTextElement(currentNode, "community", p.getCommunity(), doc);
		/* aggiunta vicini */
		addA3NodeList(currentNode, p.getStructureNeighbors(), doc);
		/* aggiunta del nodo COP del mittente */
		addCOPNode(currentNode, p.getSenderCOPNode(), doc);
		
		return doc;
	}
	
	private Document buildA3StructureRequest(A3Packet packet){
		Node currentNode = null;
		
		/* creazione di un nuovo documento */
		Document doc = builder.newDocument();		
		/* casting */
		A3StructureRequest p = (A3StructureRequest) packet;
		/* aggiunta dell'elemento di root */
		currentNode = addElement(doc, A3StructureRequest.getTypeDescription(), doc);
		/* aggiunta del nome della comunità */
		addTextElement(currentNode, "community", p.getCommunity(), doc);
		/* aggiunta dati del nodo che effettua la richiesta */
		addA3Node(currentNode, p.getRequester(), doc);
		
		return 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 addA3Node(Node node, A3Node cNode, Document doc){
		Node currentNode = addElement(node, "a3node", doc);
		addTextElement(currentNode, "address", cNode.getAddress().getHostAddress(), doc);
		addTextElement(currentNode, "port", ""+cNode.getPort(), doc);
	}
	
	private void addA3NodeList(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();
		
	}		

}
