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

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

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

import jaCop.domain.*;
import jaCop.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 COPXMLDecoder implements ICOPDecoder{
	
	/* campi */
	private DocumentBuilderFactory factory = null;
	private DocumentBuilder builder = null;
	
	/**
	 * Crea un nuovo A3XMLDecoder basato su DOM che tratta solamente pacchetti standard
	 */
	public COPXMLDecoder(){
		try {
		    factory = DocumentBuilderFactory.newInstance();
		}
		catch (FactoryConfigurationError e) {
		    System.out.println(e.getMessage());
		} 
	}	
	
	
	/* (non-Javadoc)
	 * @see convFramework.traffic.IUnMarshaller#unMarshall(byte[], convFramework.domain.Packet)
	 */
	public synchronized COPPacketTranslator.COPPacketData decode(String content) throws TranslationException{
		try {
			builder = factory.newDocumentBuilder();
		}
		catch (ParserConfigurationException e) {
			System.out.println(e.getMessage());
		}
		return getData(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(String content) throws TranslationException{
		Document doc = null;
		try {
			byte[] sequence = content.getBytes();
			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 TranslationException(e.getMessage());
		}
		return doc;
	}
	
	/**
	 * Estrae dal contenuto di un pacchetto tutte le informazioni previste dal protocollo COP; queste potranno essere
	 * poi utilizzate per la costruzione di un A3Packet
	 * @param doc il documenot DOM
	 * @return il pacchetto deserializzato
	 * @throws TranslationException in caso di errori
	 */
	protected COPPacketTranslator.COPPacketData getData(Document doc) throws TranslationException{
		
		COPPacketTranslator.COPPacketData store = new COPPacketTranslator.COPPacketData();
		Node contextNode = null;
		Node currentNode = null;

		/* campo tipo */
		contextNode = doc.getDocumentElement();
		store.setType(contextNode.getNodeName());
		
		/* comunità */
		if ((currentNode = findElement(contextNode, "community")) != null){
			store.setCommunity(extractText(currentNode));
		}
		
		/* fattore di carico del mittente */
		if ((currentNode = findElement(contextNode, "senderLoadFactor")) != null){
			String sLoadFactor = extractText(currentNode);
			store.setSenderLoadFactor(Float.parseFloat(sLoadFactor));
		}
		
		/* fattore di carico del mittente */
		if ((currentNode = findElement(contextNode, "receiverLoadFactor")) != null){
			String sRecLoadFactor = extractText(currentNode);
			store.setReceiverLoadFactor(Float.parseFloat(sRecLoadFactor));
		}
		
		/* accettazione forzata */
		if ((currentNode = findElement(contextNode, "force")) != null){
			store.setForce(true);
		}
		
		/* messaggio */
		if ((currentNode = findElement(contextNode, "message")) != null){
			store.setMesssage(extractText(currentNode));
		}
		
		/* grado di connessione */
		if ((currentNode = findElement(contextNode, "connectionRank")) != null){
			try {
			store.setConnectionRank(Integer.parseInt(extractText(currentNode)));
			}
			catch(NumberFormatException e){
				throw new TranslationException("Connection rank must be an integer");
			}
		}
		
		/* nodo */
		if ((currentNode = findElement(contextNode, "node")) != null){
			store.setNode(extractNode(currentNode));	
		}
		
		/* lista di nodi */
		if ((currentNode = findElement(contextNode, "nodeList")) != null){
			store.setNodeList(extractNodeList(currentNode));	
		}
		
		/* dati di struttura del ricevente */
		if ((currentNode = findElement(contextNode, "receiverIsStructure")) != null){
			store.setReceiverIsStructure(true);
		}
			
		return store;
	}
	
	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;
	}
	
	/**
	 * Il nodo su cui viene invocato deve essere un "node"
	 * @param currentNode
	 * @param elementName
	 * @param startPos
	 * @return
	 */
	protected COPNode extractNode(Node currentNode) throws TranslationException{
		try {
			String sAddress = extractText(findElement(currentNode, "address"));
			String sPort = extractText(findElement(currentNode, "port"));
			String sLoadFactor = extractText(findElement(currentNode, "loadFactor"));
			boolean isStructure = (findElement(currentNode, "structure") != null);
			
			InetAddress address = InetAddress.getByName(sAddress);
			int port = Integer.parseInt(sPort);
			float loadFactor = Float.parseFloat(sLoadFactor);
			
			COPNode cNode = null;
			if (isStructure){
				cNode = new COPNode(address, port, loadFactor, true);
			}
			else {
				cNode = new COPNode(address, port, loadFactor, false);
			}
			
			return cNode;
		}
		catch(UnknownHostException e){
			throw new TranslationException("Unknown node name in packet");
		}
		catch(convFramework.exceptions.BadInvocationArgumentException e){
			throw new TranslationException("bad port number in packet");
		}
	}
	
	/**
	 * Il nodo su cui lo si invoca deve essere una nodeList
	 * @param currentNode
	 * @param elementName
	 * @param startPos
	 * @return
	 */
	protected COPNode[] extractNodeList(Node currentNode) throws TranslationException{
		Vector cNodes = new Vector();
		NodeList children = currentNode.getChildNodes();
		for(int i = 0; i< children.getLength(); i++){
			cNodes.add(extractNode(children.item(i)));
		}
		return (COPNode[]) cNodes.toArray(new COPNode[cNodes.size()]);
	}
	
	protected String extractText(org.w3c.dom.Node targetElement){
		org.w3c.dom.Node currentElement = targetElement.getFirstChild();
		if (currentElement != null)
			return currentElement.getNodeValue();
		else
			return null;
	}
	
}
