package com.tandbergtv.workflow.executionhandler.handlers;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * @author Raj Prakash
 *
 * Reads the WorkOrderCreatorAction's configuration file from WatchPoint Application lib directory.
 * Singleton class.
 * Caches configuration data in memory.
 * 
 * Sample Configuration:
 * <properties>
 * 		<constant>
 * 			<name>uid</name>
 * 			<value>180101</value>
 * 		</constant>
 *		<constant>
 *			<name>selector</name>
 *			<value>DefaultTemplateSelector</value>
 *		</constant>
 *
 * 		<variable>
 * 			<name>Provider_ID</name>
 * 			<mappedName>providerId</mappedName>
 * 		</variable>
 * 		<variable>
 * 			<name>metadataFilePath</name>
 * 		</variable>
 * </properties>
 * 
 */

public class WorkOrderCreatorConfigReader {
	private static final String VARIABLE = "variable";
	private static final String VARIABLE_NAME = "name";
	private static final String VARIABLE_MAPPED_NAME = "mappedName";
	private static final String CONSTANT = "constant";
	private static final String CONSTANT_NAME = "name";
	private static final String CONSTANT_VALUE = "value";
	
	private static WorkOrderCreatorConfigReader _instance;
	
	private Map<String, Configuration> fileNameToConfigMap = new HashMap<String, Configuration>();
	
	private WorkOrderCreatorConfigReader() { }
	
	/**
	 * Gets singleton instance of this class.
	 * 
	 * @return singleton instance of this class.
	 */
	public synchronized static WorkOrderCreatorConfigReader getInstance() {
		if(_instance == null)
			_instance = new WorkOrderCreatorConfigReader();
		return _instance;
	}
	
	/**
	 * Given the config file name, gets all the constants defined in the file.
	 * 
	 * @param configFileName	config file name to get the constants from.
	 * @return					map of constant name values.
	 * @throws Exception		if any IO or xml parsing -related exceptions.
	 */
	public Map<String, String> getConstants(String configFileName) throws Exception {
		updateCacheIfNecessary(configFileName);
		return fileNameToConfigMap.get(configFileName).constants;
	}
	
	/**
	 * Given the config file name, gets all the variables defined in the file.
	 * 
	 * @param configFileName	config file name to get the constants from.
	 * @return					map of variable name to mapped-name.
	 * @throws Exception		if any IO or xml parsing -related exceptions.
	 */
	public Map<String, String> getVariables(String configFileName) throws Exception {
		updateCacheIfNecessary(configFileName);
		return fileNameToConfigMap.get(configFileName).variables;
	}

	/**
	 * If the file data is not found in memory, reads and caches in memory.
	 */
	private void updateCacheIfNecessary(String configFileName) throws Exception {
		if(!fileNameToConfigMap.containsKey(configFileName)) {
			fileNameToConfigMap.put(configFileName, readConfigFile(configFileName));
		}
	}
	
	/**
	 * Reads the given config file from WatchPoint Application lib directory and returns a Configuration object.
	 */
	private Configuration readConfigFile(String configFileName) throws IOException, SAXException, ParserConfigurationException {
		Configuration conf = new Configuration();
		
		InputStream configFileInputStream = null;
		try {
			configFileInputStream = this.getClass().getClassLoader().getResourceAsStream(configFileName);
			
			Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(configFileInputStream);
			NodeList variableNodeList = document.getDocumentElement().getElementsByTagName(VARIABLE);
			for(int i=0; i<variableNodeList.getLength(); ++i) {
				Node variableNode = variableNodeList.item(i);
				String variableName = getTextContentOfChildNode(variableNode, VARIABLE_NAME);
				String variableMappedName = getTextContentOfChildNode(variableNode, VARIABLE_MAPPED_NAME);
				conf.addVariable(variableName, variableMappedName);
			}
			
			NodeList constantNodeList = document.getDocumentElement().getElementsByTagName(CONSTANT);
			for(int i=0; i<constantNodeList.getLength(); ++i) {
				Node constantNode = constantNodeList.item(i);
				String constantName = getTextContentOfChildNode(constantNode, CONSTANT_NAME);
				String constantValue = getTextContentOfChildNode(constantNode, CONSTANT_VALUE);
				conf.addConstant(constantName, constantValue); 
			}
		}
		finally {
			try {
				if(configFileInputStream != null)
					configFileInputStream.close();
			} catch(Exception e) {}
		}
		
		return conf;
	}
	
	/**
	 * For the given node (parentNode), looks all the child nodes, if a child with the
	 * given name (childNodeName) is found, returns the trimmed text content of the child node.
	 * If no child is found, returns null.
	 */
	private String getTextContentOfChildNode(Node parentNode, String childNodeName) {
		NodeList childNodeList = parentNode.getChildNodes();
		if(childNodeList != null) {
			for(int i=0; i<childNodeList.getLength(); ++i) {
				Node childNode = childNodeList.item(i);
				if(childNode.getNodeName().equals(childNodeName)) {
					return childNode.getTextContent().trim();
				}
			}
		}
		return null;
	}
	
	/*
	 * Static Nested Class that holds the configuration data read from the file. 
	 */
	private static class Configuration {
		Map<String, String> constants;
		Map<String, String> variables;
		
		void addVariable(String variableName, String variableMappedName) {
			if(variables == null) {
				variables = new HashMap<String, String>();
			}
			variables.put(variableName, variableMappedName);
		}
		
		void addConstant(String name, String value) {
			if(constants == null) {
				constants = new HashMap<String, String>();
			}
			constants.put(name, value);
		}
	}

}
