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

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
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.webapp.tidy.TidyParser;
import org.ajax4jsf.webapp.tidy.TidyXMLFilter;
import org.ajax4jsf.renderkit.HeaderResourcesRendererBase;
import org.richfaces.component.TemplateComponent;
import org.richfaces.json.JSContentHandler;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * @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 = TidyXMLFilter.class.getResourceAsStream("tidy.properties");
			tidyProperties.load(propertiesStream);
		} finally {
			if (propertiesStream != null) {
				propertiesStream.close();
			}
		}

		TidyParser tidyParser = new TidyParser(tidyProperties);
		Document parsedHtml = tidyParser.parseHtmlByTidy(new StringReader(string), null);
		NodeList nodeList = parsedHtml.getDocumentElement().getChildNodes();
		Node bodyNode = nodeList.item(nodeList.getLength() - 1);
		NodeList bodyChildren = bodyNode.getChildNodes();
		int bodyChildrenLength = bodyChildren.getLength();

		writer.write("[");

		try {
			Transformer transformer;

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

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

			JSContentHandler contentHandler = new MacroDefinitionJSContentHandler(writer, "Richfaces.evalMacro(\"", "\", context)");
			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("]");
	}
	
	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");
	}
}
