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

/**
 * Classe che effettua il marshalling di un pacchetto standard. Puņ essere estesa per gestire altri tipi di
 * pacchetto; per farlo non va ridefinito il metodo marshall(Packet), ma vanno estesi i due metodi protetti
 * buildDocument(Packet) e serializeDocument(Document) 
 * @author Lompa
 */
public class XMLMarshaller implements IMarshaller{
	
	/* campi */
	private DocumentBuilder builder = null;
	
	/**
	 * Crea un nuovo XMLMarshaller basato su DOM che tratta solamente pacchetti standard
	 */
	public XMLMarshaller(){
		try {
		    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		    builder = factory.newDocumentBuilder();
		}
		catch (FactoryConfigurationError e) {
		    System.out.println(e.getMessage());
		} 
		catch (ParserConfigurationException 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 byte[] marshall(Packet packet) throws MarshallingException{
		return serializeDocument(buildDocument(packet));
	}
	
	/**
	 * Costruisce un documento DOM a partire da un pacchetto
	 * @param packet il pacchetto
	 * @return il documento
	 */
	protected Document buildDocument(Packet packet) throws MarshallingException{
		
		org.w3c.dom.Node currentNode = null;
		org.w3c.dom.Node newNode = null;
		
		boolean isContentPacket = false;
		
		/* creazione di un nuovo documento */
		Document doc = builder.newDocument();
		currentNode = doc;
		
		/* aggiunta dell'elemento di root */
		if (packet.getType().equals(ContentPacket.getTypeDescription())){
			currentNode = addElement(currentNode, "contentPacket", doc);
			isContentPacket = true;
		}
		else if (packet.getType().equals(AutoAckPacket.getTypeDescription())){
			currentNode = addElement(currentNode, "autoAckPacket", doc);
		}
		else
			throw new MarshallingException("Invalid packet type; expected \"contenPacket\" or \"autoAckpacket\"");
		
		/* aggiunta infomazioni di conversazione */
		currentNode = addElement(currentNode, "conversationData", doc);
		
		/* aggiunta id di conversazione */
		addTextElement(currentNode, "conversationId", packet.getConversationID().encode(), doc);
		
		/* aggiunta id del pacchetto */
		addTextElement(currentNode, "packetId", packet.getPacketID().encode(), doc);
		
		/* aggiunta id del pacchetto di riferimento */
		if (packet.getAcknowledgedPacket() != null){
			addTextElement(currentNode, "acknowledgedPacket", packet.getAcknowledgedPacket().encode(), doc);
		}
		
		/* aggiunta id del pacchetto di precedente */
		if (packet.getPreviousPacket() != null){
			addTextElement(currentNode, "previousPacket", packet.getPreviousPacket().encode(), doc);
		}
		
		/* aggiunta flag di richiesta di risposta */
		if (packet.isAckRequired()){
			addElement(currentNode, "answerRequired", doc);
		}
		
		/* aggiunta flag di risposa */
		if (packet.isAck()){
			addElement(currentNode, "isAck", doc);
		}
		
		/* aggiunta flag di sequenza */
		if (packet.isSequence()){
			addElement(currentNode, "isSequence", doc);
		}
		
		/* aggiunta flag di sequenza */
		if (packet.isAutoAckRequired()){
			addElement(currentNode, "requiresAutoAck", doc);
		}
		
		/* aggiunta contenuto */
		if (isContentPacket){
			ContentPacket cPacket = (ContentPacket) packet;
			currentNode = currentNode.getParentNode();
			addCDataElement(currentNode, "content",cPacket.getContent() != null ? cPacket.getContent() : "", doc);
		}
		
		/* restituzione del documento */
		return 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
	 */
	protected org.w3c.dom.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
	 */
	protected 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
	 */
	protected 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
	 */
	protected 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
	 */
	protected 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
	 */
	protected byte[] 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 */
		ByteArrayOutputStream outStream = null;
		try {
			outStream = new ByteArrayOutputStream(); 
			XMLSerializer serializer = new XMLSerializer (outStream, format);
			serializer.asDOMSerializer();
			serializer.serialize(document);
		}
		catch(IOException e){
			System.out.println("Unexpected system error");
			e.printStackTrace();
			System.exit(1);
		}
		
		/* restituzione del risultato */
		return outStream.toByteArray();
		
	}
	
	//TODO main di prova
	public static void main(String[] args){
		
		try {
			XMLMarshaller m = new XMLMarshaller();
			
			InetAddress address = InetAddress.getByName("127.0.0.1");
			convFramework.domain.Node currentNode = new UDPNode(address, 3337);
			
			BasicConversationIDFactory convIDFac = new BasicConversationIDFactory();
			BasicPacketIDFactory packetIDFac = new BasicPacketIDFactory();
			
			ContentPacket packet = new ContentPacket(packetIDFac.newPacketID(currentNode), convIDFac.newConversationID(currentNode));
			
			packet.setAcknowledgedPacket(packetIDFac.newPacketID(currentNode));
			packet.setAckRequired(true);
			packet.setAck(true);
			packet.setContent("prova");
			
			FileOutputStream fos = new FileOutputStream("c:/tempOutput/output.xml");
			
			fos.write(m.marshall(packet));
			
			System.out.println("tutto ok");
		}
		catch(Exception e){
			e.printStackTrace();
		}
		
	}
	
	
	
}
