/*
 * Created on Apr 15, 2009
 * 
 * (C) Copyright TANDBERG Television Ltd.
 */

package spec;

import static javax.xml.xpath.XPathConstants.NODESET;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * @author Vijay Silva
 */
public class DefinitionFileBuilder {

	/**
     * 
     */
	public DefinitionFileBuilder() {
	}

	/**
	 * Updates the definition document, rebuilding the XPath for all variables, and reordering the
	 * attributes for the variables
	 * 
	 * @param filePath The definition file path
	 * @param outputFilePath The output file path for the modified definition
	 */
	public void updateDefinitionDocument(File definitionFile, File outputFile) {
		Document document = this.loadXMLDocument(definitionFile);
		XPath xpath = XPathFactory.newInstance().newXPath();

		/* Find all top level variables and update */
		NodeList nodes = getNodes("//title/metadata/variable", xpath, document);
		int nodeCount = (nodes != null) ? nodes.getLength() : 0;
		for (int index = 0; index < nodeCount; index++) {
			updateVariableElement(xpath, (Element) nodes.item(index), "/tns:Fields");
		}

		/* Write out output file */
		this.saveXMLDocument(document, outputFile);
	}

	/**
	 * Write out the resource bundle with all keys read from the definition file
	 * 
	 * @param filePath The definition file path
	 * @param bundlePath The current resource bundle file path
	 * @param outputFilePath The output file path for the updated resource bundle
	 */
	public void generateResourceBundle(File definitionFile, File inputBundle, File outputBundle) {
		Document document = this.loadXMLDocument(definitionFile);
		XPath xpath = XPathFactory.newInstance().newXPath();

		/* Find all the title section names */
		List<String> titleNames = getValues("//title/alias", xpath, document);

		/* Find all the display names in the document */
		List<String> variableDisplayNames = getValues("//variable/@displayName", xpath, document);
		Collections.sort(variableDisplayNames);

		/* Write out the updated bundle */
		writeBundleFile(outputBundle, inputBundle, titleNames, variableDisplayNames);
	}

	// ========================================================================
	// ===================== DEFINITION FILE UPDATE
	// ========================================================================

	/* Update the variable element */
	private void updateVariableElement(XPath xpath, Element variableElement, String value) {
		String currentValue = value + "/";
		String attributeFlag = variableElement.getAttribute("attribute");
		if (attributeFlag != null && attributeFlag.equals("true")) {
			currentValue += "@";
		} else {
			currentValue += "tns:";
		}

		currentValue += variableElement.getAttribute("name");

		/* Add attributes in the correct order */
		String[] attributes = { "name", "displayName", "xpath", "xsi:type", "showCollapsed",
		        "attribute", "longFormat", "displayPattern", "key", "required", "filePath",
		        "searchField", "externalSearchField", "min", "max" };
		for (String attributeName : attributes) {
			if (variableElement.hasAttribute(attributeName) || attributeName.equals("xpath")) {
				String attributeValue = (attributeName.equals("xpath")) ? currentValue
				        : variableElement.getAttribute(attributeName);
				variableElement.removeAttribute(attributeName);
				variableElement.setAttribute(attributeName, attributeValue);
			}
		}

		/* Do the children */
		NodeList children = getNodes("variable", xpath, variableElement);
		int childCount = (children != null) ? children.getLength() : 0;
		for (int index = 0; index < childCount; index++) {
			updateVariableElement(xpath, (Element) children.item(index), currentValue);
		}
	}

	// ========================================================================
	// ===================== RESOURCE BUNDLE UPDATE
	// ========================================================================

	private void writeBundleFile(File outputBundle, File inputBundle, List<String> titleNames,
	        List<String> variableNames) {
		try {
			FileWriter writer = new FileWriter(outputBundle);
			Properties properties = this.loadProperties(inputBundle);

			/* Write the file contents */
			try {
				String newline = System.getProperty("line.separator");

				writer.write("#######################################");
				writer.write(newline);
				writer.write("## TitleConf Section Names");
				writer.write(newline);
				writer.write("#######################################");
				writer.write(newline);

				for (String title : titleNames) {
					String value = properties.getProperty(title, "");
					writer.write(title);
					writer.write("=");
					writer.write(value);
					writer.write(newline);
				}

				/* Write the variable names */
				writer.write(newline);
				writer.write(newline);

				writer.write("#######################################");
				writer.write(newline);
				writer.write("## Metadata Names (Sorted)");
				writer.write(newline);
				writer.write("#######################################");
				writer.write(newline);

				for (String variable : variableNames) {
					String value = properties.getProperty(variable, "");
					writer.write(variable);
					writer.write("=");
					writer.write(value);
					writer.write(newline);
				}

			} finally {
				try {
					writer.close();
				} catch (Exception e) {
					System.out.print("Failure closing file, ignoring.");
					e.printStackTrace();
				}
			}
		} catch (Exception e) {
			throw new RuntimeException("Failed to write resource file.", e);
		}
	}

	/* Load the properties file */
	private Properties loadProperties(File file) {
		Properties properties = new Properties();
		if (file != null) {
			try {
				FileInputStream stream = new FileInputStream(file);
				try {
					properties.load(stream);
				} finally {
					try {
						stream.close();
					} catch (Exception e) {
						e.printStackTrace();
						System.out.print("Failed to close properties file, proceeding.");
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
				System.out.print("Failed to load properties file, proceeding.");
			}
		}

		return properties;
	}

	// ========================================================================
	// ===================== XML STUFF
	// ========================================================================

	/* Load XML document from file */
	private Document loadXMLDocument(File file) {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			return factory.newDocumentBuilder().parse(file);
		} catch (Exception e) {
			throw new RuntimeException("Failed to load the document.", e);
		}
	}

	/* Save XML document to file */
	private void saveXMLDocument(Document document, File file) {
		try {
			Transformer transformer = TransformerFactory.newInstance().newTransformer();
			transformer.transform(new DOMSource(document), new StreamResult(file));
		} catch (Exception e) {
			throw new RuntimeException("Failed to save the document.", e);
		}
	}

	/* Get all the values for matching nodes given an xpath expression */
	private List<String> getValues(String expression, XPath xpath, Node target) {
		List<String> values = new ArrayList<String>();
		NodeList nodes = getNodes(expression, xpath, target);
		int nodeCount = (nodes != null) ? nodes.getLength() : 0;
		for (int index = 0; index < nodeCount; index++) {
			String value = nodes.item(index).getTextContent();
			if (value != null && value.length() > 0 && !values.contains(value))
				values.add(value);
		}

		return values;
	}

	/* Gets the nodes */
	private NodeList getNodes(String expression, XPath xpath, Node node) {
		try {
			return (NodeList) xpath.evaluate(expression, node, NODESET);
		} catch (Exception e) {
			throw new RuntimeException("Failed to get the metadata nodes.", e);
		}
	}

	// ========================================================================
	// ===================== MAIN
	// ========================================================================

	public static void main(String args[]) {
		DefinitionFileBuilder gen = new DefinitionFileBuilder();

		try {
			String inputFolder = "D:\\eclipse\\workspaces\\dev\\com.tandbergtv.watchpoint.title\\plugins\\com.tandbergtv.metadata.cablelabsvod11\\resources";
			String defnFileName = "definition.xml";
			String bundleFileName = "cablelabsvod11.properties";
			String outputPrefix = "modified_";

			File definitionFile = new File(inputFolder, defnFileName);
			File outputDefinitionFile = new File(inputFolder, outputPrefix + defnFileName);
			File bundleFile = null;
			File outputBundleFile = null;
			if (bundleFileName == null) {
				outputBundleFile = new File(inputFolder, outputPrefix + "bundle.properties");
			} else {
				bundleFile = new File(inputFolder, bundleFileName);
				outputBundleFile = new File(inputFolder, outputPrefix + bundleFileName);
			}

			gen.updateDefinitionDocument(definitionFile, outputDefinitionFile);
			gen.generateResourceBundle(definitionFile, bundleFile, outputBundleFile);
			System.out.println("Successfully completed building definition.");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
