package org.seasar.jsf.template.impl;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;

import org.cyberneko.html.parsers.DOMParser;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.exception.IORuntimeException;
import org.seasar.framework.exception.SAXRuntimeException;
import org.seasar.jsf.template.ComponentFactory;
import org.seasar.jsf.template.NodeHandler;
import org.seasar.jsf.template.ViewTemplate;
import org.seasar.jsf.template.ViewTemplateFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * @author higa
 *  
 */
public class ViewTemplateFactoryImpl implements ViewTemplateFactory {

	private S2Container container_;

	private String viewExtension_ = "html";

	private String encoding_ = "Shift_JIS";

	private List nodeHandlers_ = new ArrayList();
	
	private Map viewTemplates_ = new HashMap();

	public ViewTemplateFactoryImpl() {
	}

	public String getViewExtension() {
		return viewExtension_;
	}

	public void setViewExtension(String viewExtension) {
		viewExtension_ = viewExtension;
	}

	public String getEncoding() {
		return encoding_;
	}

	public void setEncoding(String encoding) {
		encoding_ = encoding;
	}

	/**
	 * @see org.seasar.jsf.template.ViewTemplateFactory#createViewTemplate(java.lang.String)
	 */
	public ViewTemplate getViewTemplate(String viewId) {
		ViewTemplate template = (ViewTemplate) viewTemplates_.get(viewId);
		if (template != null && !template.isModified()) {
			return template;
		}
		ServletContext ctx = container_.getRoot().getServletContext();
		DOMParser parser = createParser();
		String path = viewId + "." + viewExtension_;
		String realPath = ctx.getRealPath(path);
		try {
			parser.parse(new InputSource(ctx.getResourceAsStream(path)));
		} catch (SAXException ex) {
			throw new SAXRuntimeException(ex);
		} catch (IOException ex) {
			throw new IORuntimeException(ex);
		}
		Element rootElement = parser.getDocument().getDocumentElement();
		ComponentFactory root = buildComponentFactoryTree(rootElement);
		template = new ViewTemplateImpl(viewId, root, new File(realPath));
		viewTemplates_.put(viewId, template);
		return template;
	}

	protected ComponentFactory buildComponentFactoryTree(Node node) {
		ComponentFactory root = createComponentFactory(node);
		appendChildrenComponentFactory(root, node.getChildNodes());
		return root;
	}

	protected ComponentFactory createComponentFactory(Node node) {
		NodeHandler handler = getNodeHandler(node);
		if (handler != null) {
			return handler.createComponentFactory(node);
		}
		return null;
	}

	protected NodeHandler getNodeHandler(Node node) {
		for (int i = 0; i < nodeHandlers_.size(); ++i) {
			NodeHandler handler = (NodeHandler) nodeHandlers_.get(i);
			if (handler.isHandleable(node)) {
				return handler;
			}
		}
		return null;
	}

	protected void appendChildrenComponentFactory(ComponentFactory parent,
			NodeList childrenNode) {
		for (int i = 0; i < childrenNode.getLength(); ++i) {
			Node n = childrenNode.item(i);
			appendChildComponentFactory(parent, n);
		}
	}

	protected void appendChildComponentFactory(ComponentFactory parent,
			Node childNode) {
		ComponentFactory child = createComponentFactory(childNode);
		if (child != null) {
			parent.addChild(child);
			appendChildrenComponentFactory(child, childNode.getChildNodes());
		}
	}

	/**
	 * @see org.seasar.jsf.template.ViewTemplateFactory#setContainer(org.seasar.framework.container.S2Container)
	 */
	public void setContainer(S2Container container) {
		container_ = container;
	}

	/**
	 * @see org.seasar.jsf.template.ViewTemplateFactory#addNodeHandler(org.seasar.jsf.template.NodeHandler)
	 */
	public void addNodeHandler(NodeHandler nodeHandler) {
		nodeHandlers_.add(nodeHandler);
	}

	protected DOMParser createParser() {
		DOMParser parser = new DOMParser();
		try {
			parser.setProperty(
					"http://cyberneko.org/html/properties/default-encoding",
					encoding_);
			parser
					.setProperty(
							"http://cyberneko.org/html/properties/names/attrs",
							"default");
			parser
					.setProperty(
							"http://cyberneko.org/html/properties/names/elems",
							"match");
		} catch (SAXException ex) {
			throw new SAXRuntimeException(ex);
		}
		return parser;
	}
}