/* SimpleEvaluatorX.java

	Purpose:
		
	Description:
		
	History:
		Jul 29, 2011 9:30:30 AM, Created by henrichen

Copyright (C) 2011 Potix Corporation. All Rights Reserved.
*/

package org.zkoss.bind.impl;

import org.zkoss.bind.BindContext;
import org.zkoss.bind.sys.BindEvaluatorX;
import org.zkoss.bind.sys.Binding;
import org.zkoss.xel.ExpressionFactory;
import org.zkoss.xel.ExpressionX;
import org.zkoss.xel.FunctionMapper;
import org.zkoss.xel.ValueReference;
import org.zkoss.xel.VariableResolver;
import org.zkoss.xel.XelContext;
import org.zkoss.xel.XelException;
import org.zkoss.zel.ImportHandler;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Components;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.xel.impl.SimpleEvaluator;

/**
 * A simple implementation of {@link BindEvaluatorX}.
 * 
 * @author henrichen
 * @since 6.0.0
 */
public class BindEvaluatorXImpl extends SimpleEvaluator implements BindEvaluatorX {
	private static final long serialVersionUID = 1L;

	public BindEvaluatorXImpl(FunctionMapper mapper, Class<? extends ExpressionFactory> expfcls) {
		super(mapper, expfcls);
	}

	public Object getValue(BindContext ctx, Component comp, ExpressionX expression) throws XelException {
		try {
			if (expression instanceof PropertyExpression) {
				return expression.evaluate(null);
			}
			return expression.evaluate(newXelContext(ctx, comp));
		} catch (Exception x) {
			throw MiscUtil.mergeExceptionInfo(x, comp);
		}
	}

	public void setValue(BindContext ctx, Component comp, ExpressionX expression, Object value) throws XelException {
		//ZK-1063 No exception if binding to a non-existed property
		//Dennis, Removed the try-catch PropertyNotFoundException, we don't have history to check why we did try-catch before
		//However, it should throw the property-not-found to let user be aware it. 
		try {

			if (expression instanceof PropertyExpression) {
				expression.setValue(null, value);
			} else {
				expression.setValue(newXelContext(ctx, comp), value);
			}

		} catch (Exception x) {
			throw MiscUtil.mergeExceptionInfo(x, comp);
		}
	}

	public ExpressionX parseExpressionX(BindContext ctx, String expression, Class<?> expectedType) throws XelException {
		Component comp = null;
		if (ctx != null) {
			comp = ctx.getComponent();
			if (comp == null) {
				final Binding binding = ctx.getBinding();
				if (binding != null) {
					comp = binding.getComponent();
				}
			}
		}
		try {
			return (ExpressionX) getExpressionFactory().parseExpression(newXelContext(ctx, comp),
					"${" + expression + "}", expectedType);
		} catch (Exception x) {
			throw MiscUtil.mergeExceptionInfo(x, comp);
		}
	}

	public Class<?> getType(BindContext ctx, Component comp, ExpressionX expression) throws XelException {
		return expression.getType(newXelContext(ctx, comp));
	}

	public ValueReference getValueReference(BindContext ctx, Component comp, ExpressionX expression)
			throws XelException {
		try {
			ValueReference ref = expression.getValueReference(newXelContext(ctx, comp));
			//bug 1129-ref NPE, no value reference if it is a SimpleNode
			if (ref == null) {
				XelContext xctx = newXelContext(ctx, comp);
				//Dennis, a special control flag to ignore ref-binding getValue in BindELResolver
				if (ctx.getAttribute(BinderImpl.IGNORE_REF_VALUE) == null) //ZK-3185
					xctx.setAttribute(BinderImpl.IGNORE_REF_VALUE, Boolean.TRUE);
				Object val = expression.evaluate(xctx);
				if (val instanceof ReferenceBindingImpl) { //get value-reference from ref-binding
					ref = ((ReferenceBindingImpl) val).getValueReference();
				}
			}
			return ref;
		} catch (Exception x) {
			throw MiscUtil.mergeExceptionInfo(x, comp);
		}
	}

	//utility to create an XelContext associated to the reference
	protected XelContext newXelContext(BindContext ctx, final Component comp) {
		final FunctionMapper mapper = getFunctionMapper(comp);
		//ZK-1795MVVM nested template may cause exception
		//Dennis: Shouldn't get the real variable-resolver and keep it, it will set ref as evaluator's self, 
		//When nested MVVM templates, if there are a ref-binding, it will newXelContext multiple time to do new evaluation in this evaluation,
		//this cause the real variable-resolver's self be changed when newXelContext called.
		final VariableResolver resolver = new VariableResolver() {
			public Object resolveVariable(String name) throws XelException {
				VariableResolver vr = getVariableResolver(comp);
				return vr == null ? null : vr.resolveVariable(name);
			}
		};

		final XelContext xelc = new SimpleBindXelContext(comp, ctx != null ? ctx.getBinder() : null, resolver, mapper); //super.newXelContext(comp);

		xelc.setAttribute(BinderImpl.BINDCTX, ctx);
		if (ctx != null) {
			xelc.setAttribute(BinderImpl.BINDING, ctx.getBinding());
			//Dennis, a special control flag to ignore adding tracker when doing el,
			//see BindELContext#ignoreTracker
			xelc.setAttribute(BinderImpl.IGNORE_TRACKER, ctx.getAttribute(BinderImpl.IGNORE_TRACKER));
		}
		if (comp != null) {
			final Page page = Components.getCurrentPage(comp);
			if (page != null) {
				xelc.setAttribute(ImportHandler.PageClassResolver.class.getName(),
						(ImportHandler.PageClassResolver) page::resolveClass);
			}
		}
		return xelc;
	}

	public boolean isReadOnly(BindContext ctx, Component comp, ExpressionX expression) throws XelException {
		return expression.isReadOnly(newXelContext(ctx, comp));
	}
}
