/*
* Copyright (c) 2007 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
*
*/

package com.nokia.sdt.component.symbian.sourcegen.macrohelp;

import com.nokia.sdt.component.symbian.sourcegen.ResolvedMacro;
import com.nokia.sdt.component.symbian.sourcegen.SourceGenMacroSupport;
import com.nokia.sdt.emf.component.DefineMacroType;
import com.nokia.sdt.emf.component.DocumentRoot;
import com.nokia.sdt.emf.component.MacroArgumentType;
import com.nokia.sdt.emf.component.loader.Loader;
import com.nokia.cpp.internal.api.utils.core.FileUtils;
import com.nokia.cpp.internal.api.utils.core.MessageLocation;
import com.sun.org.apache.html.internal.dom.HTMLDOMImplementationImpl;

import org.eclipse.core.runtime.Path;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.html.HTMLDOMImplementation;
import org.w3c.dom.html.HTMLDocument;

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

/**
 * Run this program with the name of macro *.inc file(s) as
 * arguments and it will parse the contents and emit an
 * HTML file describing each one. 
 * 
 *
 */
public class MacroHelpGenerator {
	private SourceGenMacroSupport macroSupport; 
	private Map<String, List<ResolvedMacro>> macroMap;

	public static void main(String[] args) {
		MacroHelpGenerator generator = new MacroHelpGenerator();
		
		// first, parse them all
		for (String arg : args) {
			try {
				generator.parse(arg);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		// then, emit docs 
		for (String arg : args) {
			try {
				generator.generate(arg);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	public MacroHelpGenerator() {
		macroSupport = new SourceGenMacroSupport(null);
		macroMap = new HashMap<String, List<ResolvedMacro>>();
	}

	private void parse(String fName) throws Exception {
		File incFile = new File(fName);
		File tmpFile = File.createTempFile("tmpMHG", null);
		tmpFile.deleteOnExit();
		String tmpFName = tmpFile.getAbsolutePath();
		String text= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + 
				"<!DOCTYPE componentDefinition [\r\n" +
				"<!ENTITY Macros SYSTEM \"" + incFile.getAbsolutePath() + "\">\r\n" + 
				"] >\r\n" +
				"<componentDefinition xmlns=\"http://www.nokia.com/sdt/emf/component\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\r\n" +
				"<component>\r\n"+
				"<sourceGen>\r\n"+
				"&Macros;\r\n"+
				"</sourceGen>\r\n"+
				"</component>\r\n"+
				"</componentDefinition>\r\n";
		
		FileUtils.writeFileContents(tmpFile, text.toCharArray(), null);
		URI fileURI = URI.createFileURI(tmpFName);
		Loader l = new Loader();
		DocumentRoot root = l.load(fileURI);
		if (root != null) {
			
			MessageLocation messageLocation = 
				new MessageLocation(new Path(tmpFile.getAbsolutePath()));
			
			ArrayList<ResolvedMacro> fileMacros = new ArrayList<ResolvedMacro>();
			macroMap.put(fName, fileMacros);
			for (TreeIterator<EObject> iter = root.eAllContents(); iter.hasNext(); ) {
				EObject obj = iter.next();
				if (obj instanceof DefineMacroType) {
					ResolvedMacro macro = macroSupport.defineMacro(null, messageLocation, (DefineMacroType) obj);
					fileMacros.add(macro);
				}
			}
		}
		else
            throw new IllegalArgumentException("Cannot load " + fName);

	}
	private void generate(String fName) throws Exception {
		HTMLDOMImplementation domImpl = HTMLDOMImplementationImpl.getHTMLDOMImplementation();
		HTMLDocument document = domImpl.createHTMLDocument(
				"Help for Macros in " + new File(fName).getName());
		
		Element tocParent = document.createElement("p");
		document.getBody().appendChild(tocParent);
		Element tocTitle = document.createElement("h1");
		tocTitle.setTextContent("Table of contents");
		tocParent.appendChild(tocTitle);
		
		Element helpParent = document.createElement("p");
		document.getBody().appendChild(helpParent);

		for (ResolvedMacro macro : macroMap.get(fName)) {
			generateMacroHelp(document,
					tocParent,
					helpParent,
					macro);
			tocParent.appendChild(document.createTextNode("\n"));
			helpParent.appendChild(document.createTextNode("\n"));
		}
			
		DOMSource domSource = new DOMSource(document);
		TransformerFactory transFactory = TransformerFactory.newInstance();
		String outFileName = fName + ".html";
		FileOutputStream fileOutputStream = new FileOutputStream(
				outFileName);
		Result fileResult = new StreamResult(fileOutputStream);
		Transformer transformer = transFactory.newTransformer();
		//transformer.setParameter(Result.PI_DISABLE_OUTPUT_ESCAPING, "yes");
		try {
			//transFactory.newTransformer().transform(domSource, fileResult);
			transformer.transform(domSource, fileResult);
			System.out.println("Wrote " + outFileName);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		fileOutputStream.close();
	}
	

	private void generateMacroHelp(
			HTMLDocument document,
			Element tocParent,
			Element helpParent, 
			ResolvedMacro macro) {
		
		String id = macro.macro.getId();
		
		// add entry to table of contents
		Element tocEntry = document.createElement("p");
		
		Element tocAddress = document.createElement("a");
		tocAddress.setAttribute("href", "#" + id);
		
		tocAddress.setTextContent(id);
		tocEntry.appendChild(tocAddress);
		
		tocParent.appendChild(tocEntry);
		
		// add entry itself
		
		Element helpAnchor = document.createElement("a");
		helpAnchor.setAttribute("name", id);
		helpParent.appendChild(helpAnchor);
		
		Element helpEntry = document.createElement("h2");
		helpEntry.setTextContent(id);
		helpParent.appendChild(helpEntry);
		
		generateMacroDescription(document, helpParent, macro);
	}

	private void generateMacroDescription(HTMLDocument document,
			Element helpEntry, ResolvedMacro macro) {
		DefineMacroType dmt = macro.macro;
		String help = dmt.getHelp();
		
		if (help == null) {
			Element para = document.createElement("p");
			para.setTextContent("This macro is undocumented (add a 'help' attribute)");
			helpEntry.appendChild(para);
		}
		else {
			helpEntry.appendChild(encodeHTML(document, help));
		}
		
		Element header = document.createElement("h3");
		header.setTextContent("Arguments");
		helpEntry.appendChild(header);
		
		//Element ul = document.createElement("ul");
		generateMacroArgumentsHelp(document, helpEntry, macro);
		//helpEntry.appendChild(ul);
	}

	private void generateMacroArgumentsHelp(HTMLDocument document,
			Element ul, ResolvedMacro macro) {

		// organize arguments by their origin: 
		// these are already ordered as <imported arguments>
		// then <custom arguments>.  We will make a linked map that
		// places custom arguments first followed by imported arguments.
		
		Map<ResolvedMacro, List<MacroArgumentType>> originsToArguments;
		originsToArguments = new LinkedHashMap<ResolvedMacro, List<MacroArgumentType>>();

		// first, custom arguments
		List<MacroArgumentType> args = new ArrayList<MacroArgumentType>();
		originsToArguments.put(macro, args);
		
		for (MacroArgumentType argument : macro.arguments.values()) {
			ResolvedMacro fromMacro = macro.argumentOrigins.get(argument.getName());
			if (fromMacro == macro) {
				args.add(argument);
			}
		}

		// now, imported argument groups
		for (MacroArgumentType argument : macro.arguments.values()) {
			ResolvedMacro fromMacro = macro.argumentOrigins.get(argument.getName());
			args = originsToArguments.get(fromMacro);
			if (args == null) {
				args = new ArrayList<MacroArgumentType>();
				originsToArguments.put(fromMacro, args);
			}
			args.add(argument);
		}
		
		// now emit the groups of arguments from outer to inner
		for (Map.Entry<ResolvedMacro, List<MacroArgumentType>> entry : originsToArguments.entrySet()) {
			emitArgumentGroup(document, ul,
					entry.getKey() != macro,
					entry.getKey(), entry.getValue());
			
		}
	}
	
	private void emitArgumentGroup(HTMLDocument document,
			Element parent,
			boolean isInherited,
			ResolvedMacro fromMacro,
			List<MacroArgumentType> arguments) {
		
		if (isInherited) {
			Element info = document.createElement("p");
			info.setTextContent("Inherited from ");
			Element a = document.createElement("a");
			a.setAttribute("href", "#" + fromMacro.macro.getId());
			a.setTextContent(fromMacro.macro.getId());
			info.appendChild(a);
			info.appendChild(document.createTextNode(":"));
			parent.appendChild(info);
		}
		Element table = document.createElement("table");
		table.setAttribute("cellpadding", "3");
		parent.appendChild(table);
		
		table.setAttribute("border", "1");
		Element th = document.createElement("th");
		th.setTextContent("Argument");
		table.appendChild(th);
		
		th = document.createElement("th");
		th.setTextContent("Req'd?");
		table.appendChild(th);
		
		th = document.createElement("th");
		th.setTextContent("Default");
		table.appendChild(th);
		
		th = document.createElement("th");
		th.setTextContent("Description");
		table.appendChild(th);
		
		// sort arguments by optionality
		Collections.sort(arguments,
				new Comparator<MacroArgumentType>() {

					public int compare(MacroArgumentType o1,
							MacroArgumentType o2) {
						if (o1.isOptional() && !o2.isOptional())
							return 1;
						if (!o1.isOptional() && o2.isOptional())
							return -1;
						return 0;
					}
			
		});
		for (MacroArgumentType argument : arguments) {
			generateMacroArgumentHelp(document, table, fromMacro, argument);
		}

		
	}
	private void generateMacroArgumentHelp(HTMLDocument document,
			Element table, ResolvedMacro macro, MacroArgumentType argument) {
		Element tr = document.createElement("tr");
		table.appendChild(tr);

		Element td;
		td = document.createElement("td");
		td.setTextContent(argument.getName());
		tr.appendChild(td);

		td = document.createElement("td");
		td.setTextContent(argument.isOptional() ? "no" : "yes");
		tr.appendChild(td);

		td = document.createElement("td");
		if (argument.getDefault() != null && argument.getDefault().length() > 0)
			td.setTextContent(argument.getDefault());
		else
			td.setTextContent("\u00A0");
		tr.appendChild(td);

		td = document.createElement("td");
		if (argument.getHelp() != null && argument.getHelp().length() > 0)
			td.appendChild(encodeHTML(document, argument.getHelp()));
		else
			td.setTextContent("\u00A0");
		tr.appendChild(td);
	}
	
	//static Pattern PARAGRAPH_PATTERN = Pattern.compile("(.*)(\r?\n)\\s*(\r?\n)", Pattern.MULTILINE);
	
	private Node encodeHTML(HTMLDocument document, String help) {
		Node el = document.createTextNode("");
		boolean hadParagraphs = false;
		int idx = 0;
		while (idx < help.length()) {
			int para = help.indexOf("$p$", idx);
			if (para < 0)
				para = help.length();
			if (!hadParagraphs)
				el = document.createElement("p");
			Element p = document.createElement("p");
			hadParagraphs = true;
			p.setTextContent(help.substring(idx, para));
			idx = para + 3;
			el.appendChild(p);
		}
		if (!hadParagraphs) {
			el.setTextContent(help);
		}
		return el;
	}

}
