/**
 * 
 */
package org.richfaces.renderkit;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Properties;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;

import org.ajax4jsf.javascript.JSReference;
import org.ajax4jsf.javascript.ScriptUtils;
import org.ajax4jsf.renderkit.HeaderResourcesRendererBase;
import org.ajax4jsf.resource.util.URLToStreamHelper;
import org.ajax4jsf.webapp.tidy.TidyParser;
import org.ajax4jsf.webapp.tidy.TidyXMLFilter;
import org.richfaces.component.TemplateComponent;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ContentHandler;

/**
 * @author Nick Belaevski - mailto:nbelaevski@exadel.com
 * created 22.06.2007
 *
 */
public abstract class TemplateEncoderRendererBase extends HeaderResourcesRendererBase {
	private static TransformerFactory transformerFactory;

	private static TransformerFactory getTransformerFactory() {
		synchronized (TemplateEncoderRendererBase.class) {
			if (transformerFactory == null) {
				transformerFactory = TransformerFactory.newInstance();
			}
		}

		return transformerFactory;
	}
	
	public final boolean getRendersChildren() {
		return true;
	}

	private void writeScriptBody(ResponseWriter writer, String string) throws IOException {
		Properties tidyProperties = new Properties();
		InputStream propertiesStream = null;
		try {
			propertiesStream = URLToStreamHelper.urlToStreamSafe(
					TidyXMLFilter.class.getResource("tidy.properties"));
			tidyProperties.load(propertiesStream);
		} finally {
			if (propertiesStream != null) {
				propertiesStream.close();
			}
		}

		TidyParser tidyParser = new TidyParser(tidyProperties);
		Document parsedHtml = tidyParser.parseHtmlByTidy(new StringReader(string), null);

		Element documentElement = null;
		
		if (parsedHtml != null) {
			documentElement = parsedHtml.getDocumentElement();
		}
		
		if (documentElement != null) {
			writer.write("[");
			
			NodeList nodeList = documentElement.getChildNodes();
			Node bodyNode = nodeList.item(nodeList.getLength() - 1);
			NodeList bodyChildren = bodyNode.getChildNodes();
			int bodyChildrenLength = bodyChildren.getLength();

			try {
				Transformer transformer;

				TransformerFactory factory = getTransformerFactory();
				
				synchronized (factory) {
					transformer = factory.newTransformer();
				}

				transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
				transformer.setOutputProperty(OutputKeys.METHOD, "xml");

				ContentHandler contentHandler = createContentHandler(writer);
				Result result = new SAXResult(contentHandler);

				for (int i = 0; i < bodyChildrenLength; i++) {
					if (i != 0) {
						writer.write(", ");
					}
					transformer.transform(new DOMSource(bodyChildren.item(i)), result);
				}
			} catch (TransformerException e) {
				throw new IOException(e.getMessage());
			}
			
			writer.write("]");
		
		} else {
			writer.write(ScriptUtils.toScript(JSReference.NULL));
		}
	}
	
	protected void writeScriptBody(FacesContext context, UIComponent component, boolean children)
	throws IOException {
		ResponseWriter writer = context.getResponseWriter();
		StringWriter dumpingWriter = new StringWriter();
		ResponseWriter clonedWriter = writer.cloneWithWriter(dumpingWriter);
		context.setResponseWriter(clonedWriter);
		
		TemplateComponent templateComponent = null;
		if (component instanceof TemplateComponent) {
			templateComponent = (TemplateComponent) component;
		}
		
		try {
			if (templateComponent != null) {
				templateComponent.startTemplateEncode();
			}
			
			if (children) {
				this.renderChildren(context, component);
			} else {
				this.renderChild(context, component);
			}
		} finally {
			if (templateComponent != null) {
				templateComponent.endTemplateEncode();
			}

			clonedWriter.flush();
			context.setResponseWriter(writer);
		}

		writeScriptBody(writer, dumpingWriter.toString());
	}
	
	public void encodeChildren(FacesContext context, UIComponent component)
	throws IOException {
		ResponseWriter writer = context.getResponseWriter();
		writer.startElement("script", component);
		writer.write("var evaluator = ");
		writeScriptBody(context, component, true);
		writer.write(";\n new Insertion.Top($('" + component.getClientId(context) + "'), evaluator.invoke('getContent', window).join(''));");
		writer.endElement("script");
	}
	
	
	protected ContentHandler createContentHandler(Writer writer) {
		return new MacroDefinitionJSContentHandler(writer, "Richfaces.evalMacro(\"", "\", context)");
	}
}
