/**
 * 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.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.BlackboardBody;
import eu.dnetlib.common.profile.IndexBody;
import eu.dnetlib.common.profile.IndexConfiguration;
import eu.dnetlib.common.profile.IndexServiceBody;
import eu.dnetlib.common.profile.IndexServiceStatus;
import eu.dnetlib.common.profile.IndexStatus;
import eu.dnetlib.common.profile.Profile;
import eu.dnetlib.common.profile.ProfileHeader;
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 {
	
	protected static final Logger log = Logger.getLogger(ProfileUnmarshaller.class);
	
	public static final String FIELDS_DELIMITER = ",";
	
	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);
		}

	}

	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;
		} 
	}
	
	public static Profile unmarshallIndexServiceResource(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(unmarshallIndexServiceBody((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;
		} 
	}
	
	private 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;
	}
	
	private static BlackboardBody unmarshallGenericServiceBody(Element bodyElement) {
		if (bodyElement==null)
			return null;
		BlackboardBody profileBody = new BlackboardBody();
		profileBody.setBlackboard(unmarshallBlackboard(getSingleElement(bodyElement, 
				"BLACKBOARD")));
		return profileBody;
	}
	
	private static IndexServiceBody unmarshallIndexServiceBody(Element bodyElement) {
		if (bodyElement==null)
			return null;
		IndexServiceBody profileBody = new IndexServiceBody();
		profileBody.setIndexServiceStatus(unmarshallIndexServiceStatus(
				getSingleElement(bodyElement, "STATUS")));
		profileBody.setBlackboard(unmarshallBlackboard(getSingleElement(bodyElement, 
				"BLACKBOARD")));
		return profileBody;
	}
	
	private static IndexServiceStatus unmarshallIndexServiceStatus(Element statusElement) {
		if (statusElement==null)
			return null;
		IndexServiceStatus indexServiceStatus = new IndexServiceStatus();
		String statusValue = getElementContent(getSingleElement(statusElement, "READ_ONLY"));
		if (statusValue!=null) {
			try {
				indexServiceStatus.setReadOnlyStatus(Boolean.valueOf(statusValue));
			} catch (Exception e) {
				log.error("Exception occured when parsing boolean element: " + statusValue +
						", read-only status will not be set!", e);
			}
		}
		return indexServiceStatus;
	}
	
	private 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;
	}
	
	private static List<Message> unmarshallMessages(NodeList messageNodes) {
		if (messageNodes==null || messageNodes.getLength()==0)
			return null;
		List<Message> messages = new ArrayList<Message>();
		for (int i=0; i<messageNodes.getLength(); i++) {
			Message currentMessage = unmarshallMessage((Element) messageNodes.item(i));
			if (currentMessage!=null)
				messages.add(currentMessage);
		}
		return messages;
	}
	
	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;	
	}
	
	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;
	}
	
	private static Parameter unmarshallParameter(Element paramElement) {
		if (paramElement==null)
			return null;
		return new Parameter(paramElement.getAttribute("name"),
				paramElement.getAttribute("value"));
		
	}
	
	private static BlackboardLastAction unmarshallLastAction(Element lastActionElement) {
		if (lastActionElement==null)
			return null;
		return new BlackboardLastAction(lastActionElement.getAttribute("date"),
				getElementContent(lastActionElement));
	}
	//indexDSResourceUnmarshallers:
	public static Profile unmarshallIndexResource(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(unmarshallIndexBody((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;
		} 
	}
	
	private static IndexBody unmarshallIndexBody(Element bodyElement) {
		if (bodyElement==null)
			return null;
		IndexBody profileBody = new IndexBody();
		profileBody.setConfiguration(unmarshallIndexConfiguration(getSingleElement(bodyElement, 
				"CONFIGURATION")));
		profileBody.setStatus(unmarshallIndexStatus(getSingleElement(bodyElement, 
				"STATUS")));
		return profileBody;
	}
	
	private static IndexConfiguration unmarshallIndexConfiguration(Element element) {
		if (element==null)
			return null;
		IndexConfiguration configuration = new IndexConfiguration();
		
		Element metaElement = getSingleElement(element, "METADATA_FORMAT");
		if (metaElement!=null)
			configuration.setMetaDataFormat(getElementContent(metaElement));
		
		Element metaFormatInterpretElement = getSingleElement(element, "METADATA_FORMAT_INTERPRETATION");
		if (metaFormatInterpretElement!=null)
			configuration.setMetaDataFormatInterpretation(getElementContent(metaFormatInterpretElement));
		
		Element metaFormatLayoutElement = getSingleElement(element, "METADATA_FORMAT_LAYOUT");
		if (metaFormatLayoutElement!=null)
			configuration.setMetaDataFormatLayout(getElementContent(metaFormatLayoutElement));
		
		Element sizeElement = getSingleElement(element, "INDEX_SIZE");
		if (sizeElement!=null) {
			String sizeElementContent = getElementContent(sizeElement);
			if (sizeElementContent!=null && sizeElementContent.length()>0) {
				try {
				configuration.setSize(Integer.parseInt(sizeElementContent));
				} catch (Exception e) {
					log.error("Invalid size value: "+sizeElementContent,e);
				}
			}
		}
		
		Element staleElement = getSingleElement(element, "INDEX_STALE");
		if (staleElement!=null) {
			String staleElementContent = getElementContent(staleElement);
			if (staleElementContent!=null && staleElementContent.length()>0) {
				try {
				configuration.setInvalid(Boolean.parseBoolean(staleElementContent));
				} catch (Exception e) {
					log.error("Invalid index stale value: "+staleElementContent, e);
				}
			}
		}
		
		return configuration;
	}
	
	private static IndexStatus unmarshallIndexStatus(Element element) {
		if (element==null)
			return null;
		IndexStatus status = new IndexStatus();
		Element lastUpdateElement = getSingleElement(element, "INDEX_LAST_UPDATE");
		if (lastUpdateElement!=null) {
			status.setLastUpdate(getElementContent(lastUpdateElement));
		}
		return status;
	}
	//common unmarshallers
	
	private 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);
	}
	
	private static String getSingleElementValueAttr(Element root, String elementName) {
		Element el = getSingleElement(root, elementName);
		if (el!=null)
			return el.getAttribute("value");
		else
			return null;
	}

	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;
	}
	
	private static String getElementContent(Element element) {
		if (element==null)
			return null;
		if (element.getFirstChild()!=null)
			return element.getFirstChild().getTextContent();
		else
			return null;
		
	}
}
