/**
 * Unmarshaller.java
 * Created May 15, 2006
 * Copyright (C) Tandberg Television 2006
 */
package com.tandbergtv.workflow.message.util;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.tandbergtv.workflow.comm.HTTPDevice;
import com.tandbergtv.workflow.comm.IDestination;
import com.tandbergtv.workflow.comm.IDevice;
import com.tandbergtv.workflow.comm.ISource;
import com.tandbergtv.workflow.comm.TCPDevice;
import com.tandbergtv.workflow.message.IMessageKey;
import com.tandbergtv.workflow.message.IMessageUID;
import com.tandbergtv.workflow.message.MessageKeyImpl;
import com.tandbergtv.workflow.message.MessageUIDImpl;
import com.tandbergtv.workflow.message.WPCLCommand;
import com.tandbergtv.workflow.message.WorkflowMessage;
import com.tandbergtv.workflow.message.WorkflowPayload;
import com.tandbergtv.workflow.message.WorkflowMessage.MessageType;

/**
 * Unmarshals from document to workflow message
 * 
 * @author Sahil Verma, Chandan Gudla, Vlada Jakobac
 */
public class Unmarshaller
{
	private static Logger logger = Logger.getLogger(Unmarshaller.class);
	/**
	 * unmarshals from the dom document and returns the workflow message
	 *  
	 * @param document
	 * @return The Unmarshalled WorkflowMessage object
	 * @throws MarshalException
	 */
	public static WorkflowMessage unmarshal(Document document) throws MarshalException
	{
		WorkflowMessage message = null;

		try
		{
			// create the wfs message with uid, type and key
			Element root = document.getDocumentElement();
			String uid = root.getAttribute(WFSMessageConstants.UID);
			String type = root.getAttribute(WFSMessageConstants.TYPE);
			String key = root.getAttribute(WFSMessageConstants.REQUEST_KEY);
			
			message = createWorkflowMessage(uid, type, key);
			
			unmarshalCommand(document, message);

			// set the properties (source & response destination)
			unmarshalProperties(document, message);

			// set the parameter list (workflow payload)
			unmarshalMultiLevelParameters(document, message);
		}
		catch (Exception e)
		{
			throw new MarshalException("Failed to unmarshal the document", e);
		}

		return message;
	}

	/*
	 * unmarshals the parameter list in the dom document and populates the
	 * workflow payload
	 */
	private static void unmarshalMultiLevelParameters(Document document, WorkflowMessage message)
		throws XPathExpressionException
	{
		XPath xpath = XPathFactory.newInstance().newXPath();
		
		Element parameterList = 
			(Element)xpath.evaluate(WFSMessageConstants.PARAMETERS_XPATH, document, XPathConstants.NODE);
		
		if (parameterList != null && parameterList.getChildNodes().getLength() > 0)
		{
			WorkflowPayload payload = message.getPayload();
			AbstractTree tree = payload.getPayload();
			NodeList parameters = parameterList.getChildNodes();
			
			for (int i = 0; i < parameters.getLength(); i++)
			{
				Node node = parameters.item(i);
				
				if (node instanceof Element)
				{
					Element e = (Element) node;
					String tag = e.getTagName();
					
					if (tag.equals(WFSMessageConstants.PARAMETER))
					{
						unmarshallScalar(e, (Tree) tree);
					}
					else if (tag.equals(WFSMessageConstants.LIST))
					{
						unmarshallTree(e, (Tree) tree);
					}
				}
			}
		}
	}

	private static void unmarshallTree(Element element, Tree tree)
	{
		String elementKey = element.getAttribute(WFSMessageConstants.LIST_NAME);
		AbstractTree absTree = new Tree(elementKey);
		tree.add(absTree);

		NodeList listItems = element.getChildNodes();
		for (int i = 0; i < listItems.getLength(); i++)
		{
			Node node = listItems.item(i);
			
			if (node instanceof Element)
			{
				Element listItem = (Element) node;
				String tag = listItem.getTagName();
				
				if (tag.equals(WFSMessageConstants.PARAMETER))
				{
					unmarshallScalar(listItem, (Tree) absTree);
				}
				else if (tag.equals(WFSMessageConstants.LIST_ITEM))
				{
					String listItemKey = listItem.getAttribute(WFSMessageConstants.LIST_ITEM_NAME);
					AbstractTree listItemTree = new Tree(listItemKey);
					
					absTree.add(listItemTree);
					
					NodeList listItemParams = listItem.getChildNodes();
					
					for (int j = 0; j < listItemParams.getLength(); j++)
					{
						Node paramNode = listItemParams.item(j);
						
						if (paramNode instanceof Element)
						{
							Element parameter = (Element) paramNode;
							String paramTag = parameter.getTagName();
							
							if (paramTag.equals(WFSMessageConstants.PARAMETER))
							{
								unmarshallScalar(parameter, (Tree) listItemTree);
							}
							else if (paramTag.equals(WFSMessageConstants.LIST))
							{
								unmarshallTree(parameter, (Tree) listItemTree);
							}
						}
					}
				}
			}
		}
	}

	private static void unmarshallScalar(Element parameter, Tree tree)
	{
		String parameterKey = parameter.getAttribute(WFSMessageConstants.PARAMETER_NAME);
		Element value = (Element) parameter.getElementsByTagName(WFSMessageConstants.PARAMETER_VALUE).item(0);
		String parameterValue = value.getTextContent();
		AbstractTree scalar = new Tree(parameterKey);
		
		scalar.add(new Scalar(parameterValue));
		
		tree.add(scalar);
	}

	/*
	 * unmarshals the properties (source & response destination) from the dom
	 * doucment and populates into the wfs message
	 */
	private static void unmarshalProperties(Document document, WorkflowMessage message) throws XPathExpressionException
	{
		XPath xpath = XPathFactory.newInstance().newXPath();
		Element e = (Element)xpath.evaluate(WFSMessageConstants.SOURCE_XPATH, document, XPathConstants.NODE);
		
		if (e != null && e.hasChildNodes())
		{
			NodeList nodes = e.getChildNodes();
			String name = e.getAttribute(WFSMessageConstants.SOURCE_NAME);
			logger.debug("Source name = " + name);
			for (int i = 0; i < nodes.getLength(); i++)
			{
				if (nodes.item(i) instanceof Element)
				{
					Element deviceElement = (Element) nodes.item(i);
					logger.debug("Device Element = " + deviceElement.getNodeName());
					ISource source = (ISource) createDevice(name, deviceElement);
					message.setSource(source);
					break;
				}
			}
		
		}
		
		e = (Element) xpath.evaluate(WFSMessageConstants.RESPONSE_DESTINATION_XPATH, document, XPathConstants.NODE);
		
		if (e != null && e.hasChildNodes())
		{
			NodeList nodes = e.getChildNodes();
			for (int i = 0; i < nodes.getLength(); i++)
			{
				if (nodes.item(i) instanceof Element)
				{
					Element deviceElement = (Element) nodes.item(i);
					IDestination responseDestination = (IDestination) createDevice(null, deviceElement);
					message.setResponseDestination(responseDestination);
					break;
				}
			}
		}
	}

	/*
	 * creates the device (http or tcp) for the source or repsonse destination
	 * by unmarshalling the dom element
	 */
	private static IDevice createDevice(String name, Element deviceElement)
	{
		IDevice device = null;

		if (deviceElement.getTagName() == WFSMessageConstants.URL)
		{
			String url = deviceElement.getTextContent();
			device = new HTTPDevice(url, name);
		}
		else if (deviceElement.getTagName() == WFSMessageConstants.HOST)
		{
			String ip = deviceElement.getAttributes().getNamedItem(WFSMessageConstants.HOST_IP).getTextContent();
			String portString = deviceElement.getAttributes().getNamedItem(WFSMessageConstants.HOST_PORT).getTextContent();
			int port = (portString == null) ? 0 : Integer.parseInt(portString);

			device = new TCPDevice(ip, port);
		}

		return device;
	}

	/*
	 * creates the work flow message from the uid, type, and key
	 */
	private static WorkflowMessage createWorkflowMessage(String uid, String type, String key)
	{
		IMessageUID messageUID = new MessageUIDImpl(uid);
		IMessageKey messageKey = new MessageKeyImpl(key);
		MessageType messageType = MessageType.valueOf(type);
		return new WorkflowMessage(messageUID, messageKey, messageType);
	}
	
	/* Unmarshals the WPCL command */
	private static void unmarshalCommand(Document document, WorkflowMessage message) throws Exception
	{
		XPath xpath = XPathFactory.newInstance().newXPath();
		Element e = (Element)xpath.evaluate(WFSMessageConstants.COMMAND_XPATH, document, XPathConstants.NODE);
		
		if (e == null)
			return;
		
		WPCLCommand command = new WPCLCommand(e.getAttribute("Name"));
		
		for (int i = 0; i < e.getChildNodes().getLength(); i++)
		{
			Node node = e.getChildNodes().item(i);
			
			if (!(node instanceof Element))
				continue;
			
			Element child = (Element)node;
			String name = child.getAttribute(WFSMessageConstants.PARAMETER_NAME);
			String value = child.getAttribute(WFSMessageConstants.PARAMETER_VALUE);
			
			command.addParameter(name, value);
		}
		
		message.setCommand(command);
	}
}
