/* TemplateResolver.java

	Purpose:
		
	Description:
		
	History:
		2012/1/4 Created by Dennis Chen

Copyright (C) 2011 Potix Corporation. All Rights Reserved.
*/
package org.zkoss.bind.impl;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

import org.zkoss.bind.BindContext;
import org.zkoss.bind.Binder;
import org.zkoss.bind.sys.BindEvaluatorX;
import org.zkoss.bind.sys.BinderCtrl;
import org.zkoss.bind.sys.Binding;
import org.zkoss.bind.sys.TemplateResolver;
import org.zkoss.xel.ExpressionX;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.util.Template;

/**
 * The resolver resolves template and handles template changes
 * @author dennis
 * @since 6.0.0
 */
@SuppressWarnings("deprecation")
public class TemplateResolverImpl implements TemplateResolver, /*Binding,*/ Serializable { //implement binding for tieValue
	private static final long serialVersionUID = 1L;
	private final String _templateExpr;
	private final Map<String, Object> _templateArgs;
	private final Binder _binder;
	private final String _attr;
	private final Component _comp;
	private final ExpressionX _expression;

	public TemplateResolverImpl(Binder binder, Component comp, String attr, String templateExpr,
			Map<String, Object> templateArgs) {
		_binder = binder;
		_comp = comp;
		_templateExpr = templateExpr;
		_templateArgs = templateArgs;
		_attr = attr;
		//don't need to pass this, since add tracker was do by loadbinding
		final BindContext ctx = BindContextUtil.newBindContext(binder, null, false, null, _comp, null);
		_expression = binder.getEvaluatorX().parseExpressionX(ctx, templateExpr, Object.class);
	}

	public Binder getBinder() {
		return _binder;
	}

	public Component getComponent() {
		return _comp;
	}

	public String getExpression() {
		return _templateExpr;
	}

	public Map<String, Object> getTemplateArgs() {
		return _templateArgs;
	}

	public String getAttr() {
		return _attr;
	}

	private Template lookupTemplate(Component comp, String name) {
		if (comp == null)
			return null;
		Template template = comp.getTemplate(name);
		return template == null ? lookupTemplate(comp.getParent(), name) : template;
	}

	protected Object evaluateTemplate(Component eachComp, final Object eachData, final int index, final int size) {
		return evaluateTemplate(eachComp, eachData, index, size, null);
	}

	protected Object evaluateTemplate(Component eachComp, final Object eachData, final int index, final int size,
			final String subType) {
		Object oldEach = null;
		Object oldStatus = null;
		try {
			//TODO set subtype to evaluation context, so can use it as a condition.

			//prepare each and eachStatus
			oldEach = eachComp.setAttribute(EACH_VAR, eachData);
			oldStatus = eachComp.setAttribute(EACH_STATUS_VAR, new AbstractForEachStatus() {
				private static final long serialVersionUID = 1L;

				public int getIndex() {
					return index;
				}

				public Object getCurrent() {
					return eachData;
				}

				public Integer getEnd() {
					if (size < 0) {
						throw new UiException("end attribute is not supported"); // the tree case
					}
					return size;
				}
			});

			final BindEvaluatorX eval = _binder.getEvaluatorX();
			final BindContext ctx = BindContextUtil.newBindContext(_binder, null, false, null, eachComp, null);
			final Object value = eval.getValue(ctx, eachComp, _expression);
			return value;
		} finally {
			//Bug ZK-2789: do not use setAttribute when actually trying to removeAttribute
			if (oldEach != null) {
				eachComp.setAttribute(EACH_VAR, oldEach);
			} else {
				eachComp.removeAttribute(EACH_VAR);
			}
			if (oldStatus != null) {
				eachComp.setAttribute(EACH_STATUS_VAR, oldStatus);
			} else {
				eachComp.removeAttribute(EACH_STATUS_VAR);
			}
		}
	}

	public Template resolveTemplate(Component eachComp, final Object eachData, final int index, final int size) {
		return resolveTemplate(eachComp, eachData, index, size, null);
	}

	public Template resolveTemplate(Component eachComp, final Object eachData, final int index, final int size,
			final String subType) {
		final Object value = evaluateTemplate(eachComp, eachData, index, size, subType);
		if (value instanceof Template) {
			return (Template) value;
		} else if (value instanceof String) {
			//lookup from each, to allow put template in rows
			Template template;
			//B70-ZK-2555: for backward compatibility				
			if (subType == null) {
				template = lookupTemplate(eachComp, (String) value);
			} else {
				template = lookupTemplate(eachComp, (String) value + ":" + subType);
				if (template == null) { //if not found, try lookup for value only
					template = lookupTemplate(eachComp, (String) value);
				}
			}
			if (template == null && ((String) value).indexOf('.') > 0) { //might be a class path
				try {
					template = (Template) _comp.getPage().resolveClass(((String) value)).newInstance();
				} catch (Exception e) {
					//ignore;
				}
			}
			return template;
		} else {
			throw new UiException("unknow template type " + value);
		}
	}

	//ZK-739: Allow dynamic template for collection binding.	
	//Tracking template expression to trigger load binding of the template component
	public void addTemplateTracking(final Component eachComp, final Object eachData, final int index, final int size) {
		Binding binding = getTemplateBinding(eachComp, eachData, index, size);
		if (binding != null) {
			//following is for making tracker traces _teamplateExpr with binding
			final BindContext ctx = BindContextUtil.newBindContext(_binder, binding, false, null, eachComp, null);
			final BindEvaluatorX eval = _binder.getEvaluatorX();
			final ExpressionX exprX = eval.parseExpressionX(ctx, _templateExpr, Object.class);
			eval.getValue(ctx, eachComp, exprX);
		}
	}

	//Gets the binding that will be tacker when template change.
	protected Binding getTemplateBinding(Component eachComp, Object eachData, int index, int size) {
		List<Binding> bindings = ((BinderCtrl) _binder).getLoadPromptBindings(_comp, _attr);
		final Binding loadbinding = bindings.size() > 0 ? bindings.get(bindings.size() - 1) : null;
		return loadbinding;
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("[").append(_templateExpr).append("]").append(super.toString());
		return sb.toString();
	}
}
