/*
 * Created on Sep 18, 2008 (C) Copyright TANDBERG Television Ltd.
 */

package com.tandbergtv.watchpoint.pmm.web.title;

import static javax.xml.xpath.XPathConstants.BOOLEAN;
import static javax.xml.xpath.XPathConstants.NODESET;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.tandbergtv.watchpoint.pmm.title.conf.ISpecificationManager;
import com.tandbergtv.watchpoint.pmm.title.conf.Specification;
import com.tandbergtv.workflow.core.service.ServiceRegistry;
import com.tandbergtv.workflow.web.table.Column;
import com.tandbergtv.workflow.web.table.ITableExtensionLoader;

/**
 * Extension Loader that expects the extension configuration to be provided by the specification.
 * 
 * @author Vijay Silva
 */
public class TitleTableExtensionLoader implements ITableExtensionLoader {

	/* The logger */
	private static final Logger logger = Logger.getLogger(TitleTableExtensionLoader.class);

	/* The Specification Name runtime property */
	private static final String SPECIFICATION_NAME = "specificationName";

	/* The External Title runtime property */
	private static final String EXTERNAL_TITLE = "externalTitles";

	/* The XPath Expression prefix to find a table extension node */
	private static final String EXTENSION_XPATH_PREFIX = "/tableconfiguration/table-extension[@id='";

	/* The XPath Expression suffix to find a table extension node */
	private static final String EXTENSION_XPATH_SUFFIX = "']";

	/* The XPath expression relative to the table extension node to get the column nodes */
	private static final String COLUMN_XPATH = "column";

	/* The prefix to append to each progress item name */
	private static final String PROGRESSITEM_PREFIX = "progress";

	/* The URL to use for internal Titles */
	private static final String INTERNAL_TITLE_URL = "editTitle.do?method=edit&amp;id=";

	/* The property to append for the internal Title URL */
	private static final String INTERNAL_TITLE_URL_PROPERTY = "id";

	/* The URL to use for external Titles */
	private static final String EXTERNAL_TITLE_URL = "viewTitle.do?method=viewExternal&amp;";

	/* The property to append for the external Title URL */
	private static final String EXTERNAL_TITLE_URL_PROPERTY = "urlQueryString";

	/* The URL to use for Progress Items */
	private static final String PROGRESS_URL = "/portal/#Workflow.Work Orders.Search.Id=";

	/**
	 * @see com.tandbergtv.workflow.web.table.ITableExtensionLoader#loadExtension(java.lang.String,
	 *      java.util.Map)
	 */
	public List<Column> loadExtension(String extensionKey, Map<String, String> properties) {
		/* Get the specification */
		String specificationName = properties.get(SPECIFICATION_NAME);
		Specification specification = this.getSpecification(specificationName);

		/* Verify that a specification is selected */
		if (specification == null) {
			logger.warn("Failed to determine specification[" + specificationName + "] for which to"
					+ " load table extension[" + extensionKey + "], ignoring extension.");
			return new ArrayList<Column>();
		}

		/* Build the columns for the table extension */
		return this.buildColumns(specification, extensionKey, properties);
	}

	/* Get the Specification given the name using the appropriate service in service registry */
	private Specification getSpecification(String specificationName) {
		if (specificationName == null)
			return null;

		ServiceRegistry registry = ServiceRegistry.getDefault();
		ISpecificationManager specManager = registry.lookup(ISpecificationManager.class);
		return specManager.getSpecificationByName(specificationName);
	}

	/* Build the Columns that will replace the Table Extension */
	private List<Column> buildColumns(Specification specification, String extensionKey,
			Map<String, String> properties) {
		List<Column> columns = new ArrayList<Column>();

		String specName = specification.getName();
		boolean externalTitle = Boolean.parseBoolean(properties.get(EXTERNAL_TITLE));
		try {
			/* Load the table extension configuration */
			Document document = this.loadTableExtensionConfiguration(specification);

			/* Find the appropriate extension node */
			XPath xpath = XPathFactory.newInstance().newXPath();
			Node extensionNode = this.getExtensionNode(document, extensionKey, specName, xpath);
			if (extensionNode == null)
				return columns;

			NodeList nodes = (NodeList) xpath.evaluate(COLUMN_XPATH, extensionNode, NODESET);
			int nodeCount = (nodes != null) ? nodes.getLength() : 0;
			for (int i = 0; i < nodeCount; i++) {
				Node columnNode = nodes.item(i);
				Column column = this.buildColumn(columnNode, specification, externalTitle, xpath);
				columns.add(column);
			}
		} catch (Exception e) {
			logger.warn("Failed to build the columns for table extension[" + extensionKey
					+ "], ignoring extension.", e);
			return new ArrayList<Column>();
		}

		return columns;
	}

	/*
	 * Load the Table Configuration XML Document for the given specification
	 */
	private Document loadTableExtensionConfiguration(Specification specification)
			throws SAXException, IOException, ParserConfigurationException {
		InputStream inStream = null;
		String specName = specification.getName();
		try {
			inStream = specification.getTableExtensionConfiguration();
			DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			return builder.parse(inStream);
		} catch (Exception e) {
			throw new RuntimeException("Failed to load the table extension configuration "
					+ "for specification: " + specName, e);
		} finally {
			if (inStream != null) {
				try {
					inStream.close();
				} catch (Exception e) {
					logger.warn("Failed to close table configuration XML file for Specification: "
							+ specName, e);
				}
			}
		}
	}

	/* Get the Table Extension node from the table configuration XML document for a specification */
	private Node getExtensionNode(Document document, String extensionKey, String specificationName,
			XPath xpath) throws XPathExpressionException {
		String expression = EXTENSION_XPATH_PREFIX + extensionKey + EXTENSION_XPATH_SUFFIX;
		NodeList nodes = (NodeList) xpath.evaluate(expression, document, NODESET);

		int nodeCount = (nodes != null) ? nodes.getLength() : 0;
		if (nodeCount == 0) {
			logger.warn("Failed to find any table extension configuration for " + "specification["
					+ specificationName + "], " + "extension[" + extensionKey
					+ "], ignoring extension.");
			return null;
		} else if (nodeCount > 1) {
			logger.warn("Failed to find a unique table extension configuration for "
					+ "specification[" + specificationName + "], extension[" + extensionKey
					+ "], ignoring extension.");
			return null;
		}

		return nodes.item(0);
	}

	/* Build the Column given the appropriate column node */
	private Column buildColumn(Node columnNode, Specification specification, boolean externalTitle,
			XPath xpath) throws XPathExpressionException {
		String name = xpath.evaluate("name", columnNode);
		String width = xpath.evaluate("width", columnNode);
		String progressItemName = xpath.evaluate("progressitem-name", columnNode);
		String titleSection = xpath.evaluate("title-section", columnNode);
		String metadataName = xpath.evaluate("metadata-name", columnNode);
		boolean isURL = (Boolean) xpath.evaluate("@isURL", columnNode, BOOLEAN);
		boolean isProgressItem = (Boolean) xpath.evaluate("count(progressitem-name) > 0",
				columnNode, BOOLEAN);

		/* i18n */
		if (specification.getBundle() != null) {
			ResourceBundle bundle = specification.getBundle();
			
			if (bundle.containsKey(name))
				name = bundle.getString(name);
		}
		
		Column column = new Column();
		column.setColumnAttribute("ColumnName", name);
		column.setColumnAttribute("Width", width);
		if (isProgressItem) {
			column.setColumnAttribute("ParmValue", PROGRESSITEM_PREFIX + progressItemName);
			column.setColumnAttribute("Url", PROGRESS_URL);
		} else {
			column.setColumnAttribute("ParmValue", metadataName);
			column.setColumnAttribute("TitleSection", titleSection);

			/* Determine if sorting is possible and add sorting information */
			/*
			 * Currently only root level metadata is sortable. Also the metdata
			 * should not be multi-valued else the result will contain multiple
			 * rows
			 */
			if (isRootTitleMetadata(titleSection, metadataName, specification)) {
				column.setColumnAttribute("SortingColumnName", metadataName);
				column.setColumnAttribute("SortingProperty", "metadata");
			}

			if (isURL) {
				String url = externalTitle ? EXTERNAL_TITLE_URL : INTERNAL_TITLE_URL;
				String urlProperty = externalTitle ? EXTERNAL_TITLE_URL_PROPERTY
						: INTERNAL_TITLE_URL_PROPERTY;
				column.setColumnAttribute("Url", url);
				column.setColumnAttribute("UrlProperty", urlProperty);
			}
		}

		return column;
	}

	/* Determine if the metadata is associated with the root title section */
	private boolean isRootTitleMetadata(String titleSection, String metadataName,
			Specification specification) {
		return specification.getRootTitleConf().getName().equals(titleSection);
	}
}
