/**
 * Copyright 2008-2009 DRIVER PROJECT (ICM UW)
 * Original author: Marek Horst
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package eu.dnetlib.common.profile.utils;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import eu.dnetlib.common.profile.blackboard.IBlackboardMessage;
import eu.dnetlib.common.profile.BlackboardBody;
import eu.dnetlib.common.profile.Profile;
import eu.dnetlib.common.profile.ProfileHeader;
import eu.dnetlib.common.profile.Protocol;
import eu.dnetlib.common.profile.blackboard.Blackboard;
import eu.dnetlib.common.profile.blackboard.BlackboardLastAction;
import eu.dnetlib.common.profile.blackboard.Message;
import eu.dnetlib.common.profile.blackboard.Parameter;


/**
 * Class used for converting profile string content into object data representation.
 * 
 * @author mhorst
 */
public class ProfileUnmarshaller {
	
	/** The Constant log. */
	protected static final Logger log = Logger.getLogger(ProfileUnmarshaller.class);
	
	/** The Constant FIELDS_DELIMITER. */
	public static final String FIELDS_DELIMITER = ",";
	
	/** The db. */
	public static DocumentBuilder db = null;

	static {
        DocumentBuilderFactory factory =
            DocumentBuilderFactory.newInstance();
        factory.setIgnoringComments(true);
        factory.setValidating(false);
        try {
			db = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			log.error("Problem occured when creating document builder!",e);
		}

	}

	/**
	 * Unmarshall generic service resource.
	 * 
	 * @param profileContent the profile content
	 * 
	 * @return the profile
	 */
	public static Profile unmarshallGenericServiceResource(String profileContent) {
		try {
			if (profileContent==null)
				return null;
			Document doc = db.parse(new InputSource(new StringReader(profileContent)));
			Element root = doc.getDocumentElement(); 
			if (root==null || !root.getNodeName().equals("RESOURCE_PROFILE")) {
				log.info("Couldn't find RESOURCE_PROFILE as a root element!");
				return null;
			}
			NodeList headerList = root.getElementsByTagName("HEADER");
			if (headerList==null || headerList.getLength()!=1) {
				log.error("Couldn't find one HEADER element within profile!");
				return null;
			}
			NodeList bodyList = root.getElementsByTagName("BODY");
			if (bodyList==null || bodyList.getLength()!=1) {
				log.error("Couldn't find one BODY element within profile!");
				return null;
			}
			Profile profile = new Profile();
			profile.setHeader(unmarshallHeader((Element) headerList.item(0)));
			profile.setBody(unmarshallGenericServiceBody((Element) bodyList.item(0)));
			
			return profile;
		} catch (SAXException e) {
			log.error("Exception occured when getting document element!",e);
			return null;
		} catch (IOException e) {
			log.error("Exception occured when getting document element!",e);
			return null;
		} 
	}
	
	/**
	 * Generates xml representation of ProfileHeader object.
	 * @param profileHeader
	 * @return
	 */
	public static String generateProfileHeader(ProfileHeader profileHeader) {
		if (profileHeader == null)
			return null;
		StringBuffer strBuff = new StringBuffer("<HEADER>");
		strBuff.append("<RESOURCE_IDENTIFIER value=\""
				+ profileHeader.getResourceIdentifier() + "\"/>");
		strBuff.append("<RESOURCE_TYPE value=\""
				+ profileHeader.getResourceType() + "\"/>");
		strBuff.append("<RESOURCE_KIND value=\""
				+ profileHeader.getResourceKind() + "\"/>");
		if (profileHeader.getResourceURI() != null)
			strBuff.append("<RESOURCE_URI value=\""
					+ profileHeader.getResourceURI() + "\"/>");
		else
			strBuff.append("<RESOURCE_URI value=\"\"/>");
		
		if (profileHeader.getDateOfCreation() != null)
			strBuff.append("<DATE_OF_CREATION value=\""
					+ profileHeader.getDateOfCreation() + "\"/>");
		else
			strBuff.append("<DATE_OF_CREATION value=\"\"/>");

		if (profileHeader.getParentId() != null)
			strBuff.append("<PARENT_ID value=\"" + profileHeader.getParentId()
					+ "\" type=\"" +  profileHeader.getParentType()
					+ "\"/>");
		
		if (profileHeader.getProtocols()!=null && profileHeader.getProtocols().size()>0) {
			strBuff.append("<PROTOCOLS>");
			Iterator<Protocol> itProtocol = profileHeader.getProtocols().iterator();
			while (itProtocol.hasNext()) {
				Protocol currentProtocol = itProtocol.next();
				strBuff.append("<PROTOCOL address=\"");
				strBuff.append(currentProtocol.getAddress());
				strBuff.append("\" name=\"");
				strBuff.append(currentProtocol.getName());
				strBuff.append("\"/>");
			}
			strBuff.append("</PROTOCOLS>");
		}
		
		strBuff.append("</HEADER>");
		return strBuff.toString();
	}
	
	/**
	 * Generates simple profile with blackboard body part.
	 * Returns xml profile representation.
	 * @param profileHeader
	 * @param blackboard
	 * @return xml profile representation
	 */
	public static String generateBBProfile(ProfileHeader profileHeader, Blackboard blackboard) {
		return generateBBProfile(profileHeader, blackboard, null);
	}
	
	/**
	 * Generates simple profile with blackboard body part and additional body part passed as String param.
	 * Returns xml profile representation.
	 * @param profileHeader
	 * @param blackboard
	 * @param additionalBodyPart
	 * @return xml profile representation
	 */
	public static String generateBBProfile(ProfileHeader profileHeader, Blackboard blackboard,
			String additionalBodyPart) {
		StringBuffer strBuff = new StringBuffer("<RESOURCE_PROFILE>");
		String profileHeaderPartStr = generateProfileHeader(profileHeader);
		if (profileHeaderPartStr!=null)
			strBuff.append(profileHeaderPartStr);
		strBuff.append("<BODY>");
		String blackboardPartStr = generateBlackboardPartOfProfile(blackboard);
		if (blackboardPartStr!=null)
			strBuff.append(blackboardPartStr);
		if (additionalBodyPart!=null)
			strBuff.append(additionalBodyPart);
		strBuff.append("</BODY>");
		strBuff.append("</RESOURCE_PROFILE>");
		return strBuff.toString();
	}
	
	/**
	 * Generates Message part of Blackboard.
	 * Returns xml representation of Message object.
	 * @param messages
	 * @return xml representation of Message object
	 */
	public static String generateMessagePartOfBlackBoard(List<IBlackboardMessage> messages) {
		if (messages==null)
			return null;
		StringBuffer strBuff = new StringBuffer();
		Iterator<IBlackboardMessage> it = messages.iterator();
		while (it.hasNext()) {
			IBlackboardMessage message = it.next();
			strBuff.append("<MESSAGE date=\"");
			strBuff.append(message.getDate());
			strBuff.append("\" id=\"");
			strBuff.append(message.getId());
			strBuff.append("\">");
			strBuff.append("<ACTION>");
			strBuff.append(message.getAction());
			strBuff.append("</ACTION>");
			if (message.getParamList()!=null) {
				Iterator<Parameter> itParam = message.getParamList().iterator();
				while (itParam.hasNext()) {
					Parameter currentParam = itParam.next();
					strBuff.append("<PARAMETER value=\"");
					strBuff.append(currentParam.getValue());
					strBuff.append("\" name=\"");
					strBuff.append(currentParam.getName());
					strBuff.append("\"/>");
				}
			}
			strBuff.append("<ACTION_STATUS>");
			strBuff.append(message.getActionStatus());
			strBuff.append("</ACTION_STATUS>");
			strBuff.append("</MESSAGE>");
		}
		return strBuff.toString();
	}
	
	/**
	 * Generates Message part of Blackboard.
	 * Returns xml representation of Message object.
	 * @param message
	 * @return xml representation of Message object
	 */
	public static String generateMessagePartOfBlackBoard(IBlackboardMessage message) {
		if (message==null)
			return null;
		StringBuffer strBuff = new StringBuffer();
		strBuff.append("<MESSAGE date=\"");
		strBuff.append(message.getDate());
		strBuff.append("\" id=\"");
		strBuff.append(message.getId());
		strBuff.append("\">");
		strBuff.append("<ACTION>");
		strBuff.append(message.getAction());
		strBuff.append("</ACTION>");
		if (message.getParamList()!=null) {
			Iterator<Parameter> itParam = message.getParamList().iterator();
			while (itParam.hasNext()) {
				Parameter currentParam = itParam.next();
				strBuff.append("<PARAMETER value=\"");
				strBuff.append(currentParam.getValue());
				strBuff.append("\" name=\"");
				strBuff.append(currentParam.getName());
				strBuff.append("\"/>");
			}
		}
		strBuff.append("<ACTION_STATUS>");
		strBuff.append(message.getActionStatus());
		strBuff.append("</ACTION_STATUS>");
		strBuff.append("</MESSAGE>");
		return strBuff.toString();
	}
	
	/**
	 * Generates xml representation of blackboard object.
	 * @param blackboard
	 * @return xml representation of blackboard object
	 */
	public static String generateBlackboardPartOfProfile(Blackboard blackboard) {
		if (blackboard==null)
			return null;
		StringBuffer strBuff = new StringBuffer("<BLACKBOARD>");
		if (blackboard.getLastRequest()!=null) {
			strBuff.append("<LAST_REQUEST date=\"");
			strBuff.append(blackboard.getLastRequest().getDate());
			strBuff.append("\">");
			strBuff.append(blackboard.getLastRequest().getValue());
			strBuff.append("</LAST_REQUEST>");
		}
		if (blackboard.getLastResponse()!=null) {
			strBuff.append("<LAST_RESPONSE date=\"");
			strBuff.append(blackboard.getLastResponse().getDate());
			strBuff.append("\">");
			strBuff.append(blackboard.getLastResponse().getValue());
			strBuff.append("</LAST_RESPONSE>");
		}
		String messagePartString = generateMessagePartOfBlackBoard(blackboard.getMessages());
		if (messagePartString!=null) {
			strBuff.append(messagePartString);
		}
		strBuff.append("</BLACKBOARD>");
		return strBuff.toString();
	}

		
	/**
	 * Unmarshall header.
	 * 
	 * @param headerElement the header element
	 * 
	 * @return the profile header
	 */
	public static ProfileHeader unmarshallHeader(Element headerElement) {
		if (headerElement==null)
			return null;
		String resourceIdentifier = getSingleElementValueAttr(headerElement, "RESOURCE_IDENTIFIER");
		String resourceType = getSingleElementValueAttr(headerElement, "RESOURCE_TYPE");
		String resourceKind = getSingleElementValueAttr(headerElement, "RESOURCE_KIND");
		ProfileHeader profileHeader = new ProfileHeader(resourceIdentifier,resourceType
				, resourceKind);
		profileHeader.setResourceURI(getSingleElementValueAttr(headerElement, "RESOURCE_URI"));
		profileHeader.setDateOfCreation(getSingleElementValueAttr(headerElement, "DATE_OF_CREATION"));
		profileHeader.setParentId(getSingleElementValueAttr(headerElement, "PARENT_ID"));
		profileHeader.setParentType(getSingleElementAttribute(headerElement, "PARENT_ID", "type"));
//		TODO dodac obsluge protocols, 
		return profileHeader;
	}
	
	/**
	 * Unmarshall generic service body.
	 * 
	 * @param bodyElement the body element
	 * 
	 * @return the blackboard body
	 */
	private static BlackboardBody unmarshallGenericServiceBody(Element bodyElement) {
		if (bodyElement==null)
			return null;
		BlackboardBody profileBody = new BlackboardBody();
		profileBody.setBlackboard(unmarshallBlackboard(getSingleElement(bodyElement, 
				"BLACKBOARD")));
		return profileBody;
	}
	
	/**
	 * Unmarshall blackboard.
	 * 
	 * @param blackboardElement the blackboard element
	 * 
	 * @return the blackboard
	 */
	public static Blackboard unmarshallBlackboard(Element blackboardElement) {
		if (blackboardElement==null)
			return null;
		Blackboard blackboard = new Blackboard();
		blackboard.setLastRequest(unmarshallLastAction(getSingleElement(blackboardElement, 
				"LAST_REQUEST")));
		blackboard.setLastResponse(unmarshallLastAction(getSingleElement(blackboardElement, 
		"LAST_RESPONSE")));
		blackboard.setMessages(unmarshallMessages(
				blackboardElement.getElementsByTagName("MESSAGE")));
		return blackboard;
	}
	
	/**
	 * Unmarshall messages.
	 * 
	 * @param messageNodes the message nodes
	 * 
	 * @return the list< message>
	 */
	private static List<IBlackboardMessage> unmarshallMessages(NodeList messageNodes) {
		if (messageNodes==null || messageNodes.getLength()==0)
			return null;
		List<IBlackboardMessage> messages = new ArrayList<IBlackboardMessage>();
		for (int i=0; i<messageNodes.getLength(); i++) {
			Message currentMessage = unmarshallMessage((Element) messageNodes.item(i));
			if (currentMessage!=null)
				messages.add(currentMessage);
		}
		return messages;
	}
	
	/**
	 * Unmarshall message.
	 * 
	 * @param messageElement the message element
	 * 
	 * @return the message
	 */
	private static Message unmarshallMessage(Element messageElement) {
		if (messageElement==null)
			return null;
		Message message = new Message();
		message.setDate(messageElement.getAttribute("date"));
		message.setId(messageElement.getAttribute("id"));
		String actionValue = getElementContent(getSingleElement(messageElement, "ACTION"));
		try {
			if (actionValue!=null)
				message.setAction(Message.Action.valueOf(actionValue));
		} catch (IllegalArgumentException e) {
			log.error("Couldn't set action, invalid action value: "+actionValue);
		}
		String actionStatusValue = getElementContent(getSingleElement(messageElement, "ACTION_STATUS"));
		try {
			if (actionStatusValue!=null)
				message.setActionStatus(Message.ActionStatus.valueOf(actionStatusValue));
		} catch (IllegalArgumentException e) {
			log.error("Couldn't set action status, invalid action status value: "+actionStatusValue);
		}
		message.setParamList(unmarshallParameters(
				messageElement.getElementsByTagName("PARAMETER")));
		return message;	
	}
	
	/**
	 * Unmarshall parameters.
	 * 
	 * @param paramNodes the param nodes
	 * 
	 * @return the list< parameter>
	 */
	private static List<Parameter> unmarshallParameters(NodeList paramNodes) {
		if (paramNodes==null || paramNodes.getLength()==0)
			return null;
		List<Parameter> params = new ArrayList<Parameter>();
		for (int i=0; i<paramNodes.getLength(); i++) {
			Parameter currentParam = unmarshallParameter((Element) paramNodes.item(i));
			if (currentParam!=null)
				params.add(currentParam);
		}
		return params;
	}
	
	/**
	 * Unmarshall parameter.
	 * 
	 * @param paramElement the param element
	 * 
	 * @return the parameter
	 */
	private static Parameter unmarshallParameter(Element paramElement) {
		if (paramElement==null)
			return null;
		return new Parameter(paramElement.getAttribute("name"),
				paramElement.getAttribute("value"));
		
	}
	
	/**
	 * Unmarshall last action.
	 * 
	 * @param lastActionElement the last action element
	 * 
	 * @return the blackboard last action
	 */
	private static BlackboardLastAction unmarshallLastAction(Element lastActionElement) {
		if (lastActionElement==null)
			return null;
		return new BlackboardLastAction(lastActionElement.getAttribute("date"),
				getElementContent(lastActionElement));
	}
	
	//common unmarshallers
	/**
	 * Gets the single element.
	 * 
	 * @param root the root
	 * @param elementName the element name
	 * 
	 * @return the single element
	 */
	public static Element getSingleElement(Element root, String elementName) {
		if (root==null || elementName==null)
			return null;
		NodeList nodeList = root.getElementsByTagName(elementName);
		if (nodeList==null || nodeList.getLength()==0)
			return null;
		return (Element) nodeList.item(0);
	}
	
	/**
	 * Gets the single element value attr.
	 * 
	 * @param root the root
	 * @param elementName the element name
	 * 
	 * @return the single element value attr
	 */
	private static String getSingleElementValueAttr(Element root, String elementName) {
		Element el = getSingleElement(root, elementName);
		if (el!=null)
			return el.getAttribute("value");
		else
			return null;
	}

	/**
	 * Gets the single element attribute.
	 * 
	 * @param root the root
	 * @param elementName the element name
	 * @param attrName the attr name
	 * 
	 * @return the single element attribute
	 */
	private static String getSingleElementAttribute(Element root, String elementName, 
			String attrName) {
		if (attrName==null)
			return null;
		Element el = getSingleElement(root, elementName);
		if (el!=null)
			return el.getAttribute(attrName);
		else
			return null;
	}
	
	/**
	 * Gets the element content.
	 * 
	 * @param element the element
	 * 
	 * @return the element content
	 */
	public static String getElementContent(Element element) {
		if (element==null)
			return null;
		if (element.getFirstChild()!=null)
			return element.getFirstChild().getTextContent();
		else
			return null;
		
	}
}
