package org.seasar.jsf.processor;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.faces.application.Application;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.servlet.ServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.Tag;

import org.seasar.jsf.JsfConfig;
import org.seasar.jsf.JsfConstants;
import org.seasar.jsf.JsfContext;
import org.seasar.jsf.TagProcessor;
import org.seasar.jsf.ViewTemplate;
import org.seasar.jsf.ViewTemplateFactory;
import org.seasar.jsf.exception.InsertProcessorNotFoundRuntimeException;
import org.seasar.jsf.util.BindingUtil;

/**
 * @author higa
 *  
 */
public class ViewProcessor extends TagProcessorImpl {

	private static final String TAG = "view";

	private String contentType;

	private String encoding;

	private String extendsPath;

	private String initAction;

	private List insertProcessors = new ArrayList();

	private ViewTemplateFactory viewTemplateFactory;

	public ViewProcessor() {
	}

	public ViewProcessor(JsfConfig jsfConfig,
			ViewTemplateFactory viewTemplateFactory) {
		setProperty(JsfConstants.INJECT_ATTR, jsfConfig
				.getTaglibPrefix(JsfConstants.JSF_CORE_URI)
				+ ":" + TAG);
		processInject(jsfConfig);
		this.viewTemplateFactory = viewTemplateFactory;
	}

	public String getContentType() {
		return contentType;
	}

	public void setContentType(String contentType) {
		this.contentType = contentType;
		int index = contentType.indexOf(JsfConstants.CHARSET);
		if (index < 0) {
			throw new IllegalArgumentException(JsfConstants.CHARSET
					+ " not found in " + contentType);
		}
		int index2 = contentType.indexOf(JsfConstants.EQUAL, index + 1);
		encoding = contentType.substring(index2 + 1);
		encoding = encoding.trim();
	}

	public String getEncoding() {
		return encoding;
	}

	public InsertProcessor getInsertProcessor(String name) {
		for (int i = 0; i < insertProcessors.size(); ++i) {
			InsertProcessor ip = (InsertProcessor) insertProcessors.get(i);
			if (name == null && ip.getName() == null || name != null && name.equalsIgnoreCase(ip.getName())) {
				return ip;
			}
		}
		throw new InsertProcessorNotFoundRuntimeException(name);
	}

	public void addInsertProcessor(InsertProcessor insertProcessor) {
		insertProcessors.add(insertProcessor);
	}

	public String[] getIncludes() {
		Set includes = new HashSet();
		addIncludes(includes);
		return (String[]) includes.toArray(new String[includes.size()]);
	}

	protected void addIncludes(Set includes) {
		for (int i = 0; i < insertProcessors.size(); ++i) {
			InsertProcessor ip = (InsertProcessor) insertProcessors.get(i);
			String src = ip.getSrc();
			if (src != null) {
				includes.add(src);
			}
		}
		ViewProcessor extendsViewProcessor = getExtendsViewProcessor();
		if (extendsViewProcessor != null) {
			extendsViewProcessor.addIncludes(includes);
		}
	}

	public String getExtendsPath() {
		return extendsPath;
	}

	public void setExtendsPath(String extendsPath) {
		this.extendsPath = extendsPath;
	}

	protected ViewProcessor getExtendsViewProcessor() {
		if (extendsPath == null) {
			return null;
		}
		ViewTemplate vt = viewTemplateFactory.getViewTemplate(extendsPath);
		return (ViewProcessor) vt.getRootTagProcessor();
	}

	public String getInitAction() {
		return initAction;
	}

	public void setInitAction(String initAction) {
		this.initAction = initAction;
	}

	public void process(JsfContext jsfContext, Tag parentTag)
			throws JspException {

		setupParams(jsfContext.getJsfConfig(), jsfContext.getPageContext().getRequest());
		ViewProcessor extendsViewProcessor = getExtendsViewProcessor();
		if (extendsViewProcessor != null) {
			setupInsertProcessorMap(jsfContext.getPageContext());
			extendsViewProcessor.process(jsfContext, parentTag);
		} else {
			super.process(jsfContext, parentTag);
		}
	}
	
	protected void setupInsertProcessorMap(PageContext pageContext) {
		Map insertProcessorMap = getInsertProcessorMap(pageContext);
		for (int i = 0; i < insertProcessors.size(); ++i) {
			InsertProcessor ip = (InsertProcessor) insertProcessors.get(i);
			String name = ip.getName();
			if (name == null || insertProcessorMap.containsKey(name)) {
				continue;
			}
			insertProcessorMap.put(name, ip);
		}
	}
	
	protected void setupParams(JsfConfig jsfConfig, ServletRequest request) {
		FacesContext context = FacesContext.getCurrentInstance();
		Application app = context.getApplication();
		String inject = jsfConfig.getTaglibPrefix(JsfConstants.JSF_CORE_URI) + ":param";
		TagProcessor bodyProcessor = findBodyProcessor(this);
		if (bodyProcessor == null) {
			return;
		}
		for (int i = 0; i < bodyProcessor.getChildCount(); ++i) {
			TagProcessor tp = bodyProcessor.getChild(i);
			if (inject.equalsIgnoreCase(tp.getProperty(JsfConstants.INJECT_ATTR))) {
				String name = tp.getProperty(JsfConstants.NAME_ATTR);
				if (request.getAttribute(name) != null) {
					continue;
				}
				String valstr = tp.getProperty(JsfConstants.VALUE_ATTR);
				Object value = valstr;
				if (BindingUtil.isValueReference(valstr)) {
					ValueBinding vb = app.createValueBinding(valstr);
					value = vb.getValue(context);
				}
				request.setAttribute(name, value);
			}
		}
	}
	
	protected TagProcessor findBodyProcessor(TagProcessor tp) {
		for (int i = 0; i < tp.getChildCount(); ++i) {
			TagProcessor child = tp.getChild(i);
			if (child instanceof ElementProcessor) {
				ElementProcessor ep = (ElementProcessor) child;
				if (JsfConstants.BODY_ELEM.equalsIgnoreCase(ep.getTagName())) {
					return child;
				}
			}
			TagProcessor bp = findBodyProcessor(child);
			if (bp != null) {
				return bp;
			}
		}
		return null;
	}
}