/*
 * 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 l'unmarshalling di un pacchetto standard. Puņ essere estesa per gestire altri tipi di
 * pacchetto; per farlo non va ridefinito il metodo unmarshall(byte[]), ma vanno estesi i due metodi protetti
 * getDocument(byte[]) e buildPacket(Document, Packet) 
 * @author Lompa
 */
public class XMLUnMarshaller implements IUnMarshaller {
	
	/* campi */
	private DocumentBuilder builder = null;
	private IPacketIDFactory packetIDFactory = null;
	private IConversationIDFactory conversationIDFactory = null;
	
	/**
	 * Crea un nuovo A3XMLDecoder basato su DOM che tratta solamente pacchetti standard
	 */
	public XMLUnMarshaller(IPacketIDFactory packetIDFactory, IConversationIDFactory convIDFactory){
		try {
		    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		    builder = factory.newDocumentBuilder();
		    this.packetIDFactory = packetIDFactory;
		    this.conversationIDFactory = convIDFactory; 
		}
		catch (FactoryConfigurationError e) {
		    System.out.println(e.getMessage());
		} 
		catch (ParserConfigurationException e) {
			System.out.println(e.getMessage());
		}
	}	
	
	
	/* (non-Javadoc)
	 * @see convFramework.traffic.IUnMarshaller#unMarshall(byte[], convFramework.domain.Packet)
	 */
	public Packet unMarshall(byte[] content) throws MarshallingException{
		return buildPacket(getDocument(content));
	}
	
	/**
	 * Recupera un documento DOM a partire da una sequenza di byte
	 * @param content la sequenza di byte
	 * @return il documento
	 * @throws SAXException se si verifica un errore di parsing
	 */
	protected Document getDocument(byte[] sequence) throws MarshallingException{
		Document doc = null;
		try {
			String s = new String(sequence);
			ByteArrayInputStream inStream = new ByteArrayInputStream(sequence);
			doc =  builder.parse(inStream);
		}
		catch(IOException e){
			System.out.println("Unexpected system error");
			e.printStackTrace();
			System.exit(1);
		}
		catch(SAXException e){
			throw new MarshallingException(e.getMessage());
		}
		return doc;
	}
	
	/**
	 * Costruisce un pacchetto standard a partire da un documento DOM; il document puņ contenere anche elementi
	 * non previsti per un pacchetto standrd, quelo che č essenziale č che gli elementi standard obbligatori
	 * siano tutti presenti e nell'ordine opportuno  
	 * @param doc il documenot DOM
	 * @return il pacchetto deserializzato
	 * @throws TranslationException in caso di errori
	 */
	protected Packet buildPacket(Document doc) throws MarshallingException{
		
		org.w3c.dom.Node currentNode = null;
		org.w3c.dom.Node contextNode = null;
		
		boolean isContentPacket = false;
		
		/* controlli */
		currentNode = doc.getDocumentElement();
		if (currentNode.getNodeName().equals("contentPacket"))
			isContentPacket = true;
		else if (currentNode.getNodeName().equals("autoAckPacket")){}
		else
			throw new MarshallingException("Wrong packet type. Found: "+currentNode.getNodeName()+", expected: \"CFKPacket\"");
		
		/* preparazione accumulatore per i dati */
		DataStore store = new DataStore();
		
		/* ricerca elemento conversationData */
		contextNode = currentNode = findElement(currentNode, "conversationData");
		
		/* ricerca conversation id */
		currentNode = findElement(contextNode, "conversationId");
		if (currentNode == null)
			throw new MarshallingException("Cannot find conversation id");
		else
			store.conversationID = this.conversationIDFactory.decode(extractText(currentNode));
		
		/* ricerca  packet id */
		currentNode = findElement(contextNode, "packetId");
		if (currentNode == null)
			throw new MarshallingException("Cannot find packet id");
		else
			store.packetID = this.packetIDFactory.decode(extractText(currentNode));
		
		/* ricerca  referred packet */
		currentNode = findElement(contextNode, "acknowledgedPacket");
		if (currentNode != null)
			store.referredPacket = this.packetIDFactory.decode(extractText(currentNode));
		
		/* ricerca  previous packet */
		currentNode = findElement(contextNode, "previousPacket");
		if (currentNode != null)
			store.previousPacket = this.packetIDFactory.decode(extractText(currentNode));
		
		/* ricerca  answerRequired */
		if (findElement(contextNode, "answerRequired") != null)
			store.answerRequired = true;
		
		/* ricerca  isAnswer */
		if (findElement(contextNode, "isAck") != null)
			store.isAnswer = true;
	
		/* ricerca  isSequence */
		if (findElement(contextNode, "isSequence") != null)
			store.isSequence = true;
		
		/* ricerca  requiresAutoAck */
		if (findElement(contextNode, "requiresAutoAck") != null)
			store.requiresAutoAck = true;
		
		/* restituzione di un pacchetto */
		/* istanziazione */
		Packet packet = null;
		if (isContentPacket){
			packet = new ContentPacket(store.packetID, store.conversationID);
		}
		else {
			packet = new AutoAckPacket(store.packetID, store.conversationID);
		}
		/* inizializzazione campi generali */
		packet.setAcknowledgedPacket(store.referredPacket);
		packet.setPreviousPacket(store.previousPacket);
		packet.setAckRequired(store.answerRequired);
		packet.setAck(store.isAnswer);
		packet.setSequence(store.isSequence);
		packet.setAutoAckRequired(store.requiresAutoAck);
		/* inizializzazione contenuto specifico */
		if (isContentPacket){
			contextNode = contextNode.getParentNode();
			currentNode = findElement(contextNode, "content");
			String content = extractText(currentNode);
			((ContentPacket) packet).setContent(content != null ? content : "");
		}
		
		return packet;
	}
	
	protected org.w3c.dom.Node findElement(org.w3c.dom.Node currentNode, String elementName){
		return findElement(currentNode, elementName, 0);
	}
	
	protected org.w3c.dom.Node findElement(org.w3c.dom.Node currentNode, String elementName, int startPos){
		NodeList children = currentNode.getChildNodes();
		for(int i = startPos; i< children.getLength(); i++){
			if (children.item(i).getNodeName().equals(elementName))
				return children.item(i);
		}
		return null;
	}
	
	protected String extractText(org.w3c.dom.Node targetElement){
		org.w3c.dom.Node currentElement = targetElement.getFirstChild();
		if (currentElement != null)
			return currentElement.getNodeValue();
		else
			return null;
	}
	
	private class DataStore{
		
		public IConversationID conversationID = null;
		public IPacketID packetID = null;
		public IPacketID referredPacket = null;
		public IPacketID previousPacket = null;
		public boolean answerRequired = false;
		public boolean isAnswer = false;
		public boolean isSequence = false;
		public boolean requiresAutoAck = false;
		
	}
	
	public static void main(String args[]){
		try {
			
			FileReader fr = new FileReader("c:/tempOutput/output.xml");
			BufferedReader br = new BufferedReader(fr);
			
			String buffer = "";
			while(br.ready())
				buffer += br.readLine();
			
			BasicConversationIDFactory convIDFac = new BasicConversationIDFactory();
			BasicPacketIDFactory packetIDFac = new BasicPacketIDFactory();
			
			XMLUnMarshaller um = new XMLUnMarshaller(packetIDFac, convIDFac);
			
			Packet packet = um.unMarshall(buffer.getBytes());
			
			System.out.println("tutto ok");
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}
	
}
