/*******************************************************************************
 * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Exadel, Inc. and Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package org.jboss.tools.jsf.vpe.richfaces.template;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringBufferInputStream;
import java.util.HashMap;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.eclipse.core.resources.IFile;
import org.jboss.tools.jsf.vpe.richfaces.HtmlComponentUtil;
import org.jboss.tools.jsf.vpe.richfaces.RichFacesTemplatesActivator;
import org.jboss.tools.vpe.editor.VpeSourceDomBuilder;
import org.jboss.tools.vpe.editor.context.VpePageContext;
import org.jboss.tools.vpe.editor.template.VpeAbstractTemplate;
import org.jboss.tools.vpe.editor.template.VpeCreationData;
import org.jboss.tools.vpe.editor.template.VpeCreatorUtil;
import org.mozilla.interfaces.nsIDOMDocument;
import org.mozilla.interfaces.nsIDOMElement;
import org.mozilla.interfaces.nsIDOMText;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;

import com.uwyn.jhighlight.renderer.Renderer;
import com.uwyn.jhighlight.renderer.XhtmlRendererFactory;

/**
 * 
 * @author ezheleznyakov@exadel.com
 * 
 */
public class RichFacesInsertTemplate extends VpeAbstractTemplate {

	private static String RESOURCE_NOT_FOUND_MESSAGE = "Resource was not found."; //$NON-NLS-1$
	private static String RESOURCE_READING_ERROR_MESSAGE = "Resource reading error."; //$NON-NLS-1$
	private static String HIGHLIGHT_ERROR_MESSAGE = "Error occured during highlight."; //$NON-NLS-1$
	private static String ERROR_MESSAGE_STYLE = "color: red; font-weight: bold;"; //$NON-NLS-1$
	
    private static String SRC_ATTR_NAME = "src"; //$NON-NLS-1$
    private static String HIGHTLIGHT_ATTR_NAME = "highlight"; //$NON-NLS-1$

    private static String CODE_TAG = "code>"; //$NON-NLS-1$

    private static String CLASS = "class="; //$NON-NLS-1$

    private static String STYLE = "style="; //$NON-NLS-1$

    private static String OPEN_BRACKET = "{"; //$NON-NLS-1$
    private static String CLOSE_BRACKET = "}"; //$NON-NLS-1$

    private static String SPACE = "&nbsp;"; //$NON-NLS-1$

    private static String SPAN_TAG = "<span style=\"color: rgb(255,255,255)\">_</span>"; //$NON-NLS-1$

    private static String EMPTY_STRING = ""; //$NON-NLS-1$
    private static String UTF8 = "utf-8"; //$NON-NLS-1$

    private static String HTML = "html"; //$NON-NLS-1$
    private static String XHTML = "xhtml"; //$NON-NLS-1$
    private static String XML = "xml"; //$NON-NLS-1$
    private static String JAVA = "java"; //$NON-NLS-1$
    private static String CPP = "cpp"; //$NON-NLS-1$
    private static String CPLUSPLUS = "c++"; //$NON-NLS-1$
    private static String GROOVY = "groovy"; //$NON-NLS-1$
    private static String LZX = "lzx"; //$NON-NLS-1$

    private nsIDOMDocument visualDocument;

    public VpeCreationData create(VpePageContext pageContext, Node sourceNode,
	    nsIDOMDocument visualDocument) {

	this.visualDocument = visualDocument;

	nsIDOMElement div = visualDocument
		.createElement(HtmlComponentUtil.HTML_TAG_DIV);
	VpeCreationData vpeCreationData = new VpeCreationData(div);

	Element sourceElement = (Element)sourceNode;
	String srcValue = sourceElement.getAttribute(SRC_ATTR_NAME);	
	String finalStr = ""; //$NON-NLS-1$
	String buf = ""; //$NON-NLS-1$

	// if there is no source show error message 
	if (!sourceElement.hasAttribute(SRC_ATTR_NAME) || "".equalsIgnoreCase(srcValue)) { //$NON-NLS-1$
	    div.setAttribute(HtmlComponentUtil.HTML_STYLE_ATTR, ERROR_MESSAGE_STYLE);
	    nsIDOMText text = visualDocument.createTextNode(RESOURCE_NOT_FOUND_MESSAGE);
	    div.appendChild(text);
	    return vpeCreationData;
	}
	BufferedReader br = null;
	try {
	    IFile iFile = VpeCreatorUtil.getFile(srcValue, pageContext);
	    if (iFile==null || !iFile.isAccessible()) {
		div.setAttribute(HtmlComponentUtil.HTML_STYLE_ATTR,
			    ERROR_MESSAGE_STYLE);
		    nsIDOMText text = visualDocument
			    .createTextNode(RESOURCE_READING_ERROR_MESSAGE);
		    div.appendChild(text);
		    return vpeCreationData;
	    }
	    File file = new File(iFile.getLocation().toOSString());
	    br = new BufferedReader(new InputStreamReader(
		    new FileInputStream(file)));
	    while ((buf = br.readLine()) != null)
		finalStr += buf + "\n"; //$NON-NLS-1$

	} catch (IOException e) {
	    div.setAttribute(HtmlComponentUtil.HTML_STYLE_ATTR,
		    ERROR_MESSAGE_STYLE);
	    nsIDOMText text = visualDocument
		    .createTextNode(RESOURCE_READING_ERROR_MESSAGE);
	    div.appendChild(text);
	    return vpeCreationData;
	} finally {
		try {
			if(br!=null) {
				br.close();
			}
		} catch (IOException e) {
			RichFacesTemplatesActivator.getPluginLog().logError(e);
		}
	}

	String highlightValue = sourceElement.getAttribute(HIGHTLIGHT_ATTR_NAME);
	if (!sourceElement.hasAttribute(HIGHTLIGHT_ATTR_NAME) || !searchInSupportedTypes(highlightValue)) {
	   // finalStr = finalStr.replace('\n', ' ');
	    nsIDOMText text = visualDocument.createTextNode(finalStr);
	    div.appendChild(text);
	    return vpeCreationData;
	}

	Renderer renderer = XhtmlRendererFactory.getRenderer(highlightValue);
	String transformStr = null;
	try {
	    transformStr = renderer.highlight("", finalStr, UTF8, false); //$NON-NLS-1$
	    transformStr = convertString(transformStr, highlightValue);
	    Node node = parseTransformString(transformStr);
	    buildVisualNode(node, div);
	} catch (IOException e1) {
		div.setAttribute(HtmlComponentUtil.HTML_STYLE_ATTR, ERROR_MESSAGE_STYLE);
	    nsIDOMText text = visualDocument.createTextNode(HIGHLIGHT_ERROR_MESSAGE);
	    div.appendChild(text);
	    return vpeCreationData;
	}
	return vpeCreationData;
    }

    /**
     * 
     * @param str
     * @param highlightValue
     *                highlight attribute value
     */
    private String convertString(String str, String highlightValue) {

	HashMap<String, String> map = new HashMap<String, String>();

	if (highlightValue.equalsIgnoreCase(HTML)
		|| highlightValue.equalsIgnoreCase(XHTML)
		|| highlightValue.equalsIgnoreCase(LZX))
	    highlightValue = XML;
	if (highlightValue.equalsIgnoreCase(GROOVY))
	    highlightValue = JAVA;
	if (highlightValue.equalsIgnoreCase(CPLUSPLUS))
	    highlightValue = CPP;

	String sym = "." + highlightValue + "_"; //$NON-NLS-1$ //$NON-NLS-2$

	for (int i = 0; i < str.length();) {
	    int start = str.indexOf(sym, i);
	    if (start == -1)
		break;
	    int startBracket = str.indexOf(OPEN_BRACKET, start);
	    String key = str.substring(start + 1, startBracket - 1);
	    int endBracket = str.indexOf(CLOSE_BRACKET, startBracket);
	    String value = str.substring(startBracket + 2, endBracket - 2);
	    i = endBracket;
	    map.put(key, value);
	}

	int start = str.indexOf(CODE_TAG);
	int end = str.indexOf(CODE_TAG, start + 1);
	str = str.substring(start - 1, end + 5);

	str = str.replaceAll(CLASS, STYLE);

	Set<String> set = map.keySet();

	for (String key : set) {
	    String value = map.get(key);
	    str = str.replaceAll(key, value);
	}
	str = str.replace(SPACE, SPAN_TAG);
	return str;
    }

    /**
     * 
     * @param fileTransform
     */
    @SuppressWarnings("deprecation")
    public Node parseTransformString(String transformString) {

	DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();

	DocumentBuilder builder = null;
	Document doc = null;
	Node node = null;
	try {
	    builder = fact.newDocumentBuilder();
	    doc = builder.parse(new StringBufferInputStream(transformString));
	    node = doc.getElementsByTagName("code").item(0); //$NON-NLS-1$
	} catch (IOException e) {
	    return node;
	} catch (SAXException e) {
	    return node;
	} catch (ParserConfigurationException e) {
		return node;
	}
	return node;
    }

    /**
     * 
     * @param highlightValue
     *                value of highlight attribute
     * @return true of highlight value correct
     */

    private boolean searchInSupportedTypes(String highlightValue) {

	if (highlightValue == null)
	    return true;

	if (highlightValue.trim().equals(EMPTY_STRING))
	    return false;

	Set<?> set = XhtmlRendererFactory.getSupportedTypes();

	for (Object object : set)
	    if (highlightValue.equalsIgnoreCase((String) object))
		return true;

	return false;
    }

    /**
     * 
     * @param node
     * @param el
     * @return
     */
    private void buildVisualNode(Node node, nsIDOMElement el) {

	if (node instanceof Text) {
	    nsIDOMText text = visualDocument.createTextNode(node
		    .getTextContent());
	    el.appendChild(text);

	} else {
	    nsIDOMElement elem = visualDocument.createElement(node
		    .getNodeName());
	    el.appendChild(elem);

	    for (int i = 0; i < node.getAttributes().getLength(); i++)
			elem.setAttribute(node.getAttributes().item(i).getNodeName(),
				node.getAttributes().item(i).getNodeValue());

	    for (int i = 0; i < node.getChildNodes().getLength(); i++)
	    	buildVisualNode(node.getChildNodes().item(i), elem);
	}
    }

    /**
     * Checks, whether it is necessary to re-create an element at change of
     * attribute
     * 
     * @param pageContext
     *                Contains the information on edited page.
     * @param sourceElement
     *                The current element of the source tree.
     * @param visualDocument
     *                The document of the visual tree.
     * @param visualNode
     *                The current node of the visual tree.
     * @param data
     *                The arbitrary data, built by a method <code>create</code>
     * @param name
     *                Attribute name
     * @param value
     *                Attribute value
     * @return <code>true</code> if it is required to re-create an element at
     *         a modification of attribute, <code>false</code> otherwise.
     */
    public boolean recreateAtAttrChange(VpePageContext pageContext,
	    Element sourceElement, nsIDOMDocument visualDocument,
	    nsIDOMElement visualNode, Object data, String name, String value) {
	return true;
    }
    
    @Override
    public void setSourceAttributeSelection(VpePageContext pageContext,
	    Element sourceElement, int offset, int length, Object data) {
	VpeSourceDomBuilder sourceBuilder = pageContext.getSourceBuilder();
	sourceBuilder.setSelection(sourceElement, 0, 0);
    }
}