/**
 *
 */
package org.openforis.idm.model.expression.internal;

import java.util.HashSet;
import java.util.Set;
import java.util.Stack;

import org.apache.commons.jxpath.ri.JXPathCompiledExpression;
import org.apache.commons.jxpath.ri.compiler.CoreFunction;
import org.apache.commons.jxpath.ri.compiler.Expression;
import org.apache.commons.jxpath.ri.compiler.Operation;
import org.openforis.idm.metamodel.NodeDefinition;
import org.openforis.idm.metamodel.expression.ExpressionValidator.ExpressionValidationResult;
import org.openforis.idm.metamodel.expression.ExpressionValidator.ExpressionValidationResultFlag;
import org.openforis.idm.model.expression.ExpressionFactory;

/**
 * @author M. Togna
 * @author D. Wiell
 */
public class ModelJXPathCompiledExpression extends JXPathCompiledExpression {
	
	private ExpressionFactory expressionFactory;

	public ModelJXPathCompiledExpression(ExpressionFactory expressionFactory, String xpath, Expression expression) {
		super(xpath, expression);
		this.expressionFactory = expressionFactory;
	}

	public Set<String> getReferencedPaths() {
		return expressionFactory.getReferencedPathEvaluator().determineReferencedPaths(getExpression());
	}

	public ExpressionValidationResult validate(final NodeDefinition contextNodeDef) {
		return validateOperations(new OperationVaildator() {
			public ExpressionValidationResult validate(Operation operation) {
				if (operation instanceof ModelExtensionFunction) {
					CustomFunction customFunction = expressionFactory.lookupFunction((ModelExtensionFunction) operation);
					if (null == customFunction) {
						String fullName = ((ModelExtensionFunction) operation).getFullName();
						String message = String.format("function '%s' does not exist", fullName);
						String functionNames = expressionFactory.getFullFunctionNames().toString();
						String detailedMessage = String.format("function '%s' does not exist\n Possible function names:\n%s", fullName, functionNames);
						return new ExpressionValidationResult(ExpressionValidationResultFlag.ERROR, message, detailedMessage);
					} else {
						return customFunction.validateArguments(contextNodeDef, nullToEmpty(operation));
					}
				}
				return new ExpressionValidationResult();
			}

			private Expression[] nullToEmpty(Operation operation) {
				Expression[] arguments = operation.getArguments();
				if (arguments == null) {
					arguments = new Expression[0];
				}
				return arguments;
			}
		});
	}
	
	private ExpressionValidationResult validateOperations(OperationVaildator operationValidator) {
		Stack<Expression> stack = new Stack<Expression>();
		stack.push(getExpression());
		while (!stack.isEmpty()) {
			Expression expression = stack.pop();
			if (expression instanceof Operation) {
				ExpressionValidationResult result = operationValidator.validate((Operation) expression);
				if (result.isError()) {
					return result;
				}
			}
		}
		return new ExpressionValidationResult();
	}
	
	public Set<String> getFunctionNames() {
		Set<String> names = new HashSet<String>();
		Stack<Expression> stack = new Stack<Expression>();
		stack.push(getExpression());
		while (!stack.isEmpty()) {
			Expression expression = stack.pop();
			if (expression instanceof Operation) {
				if (expression instanceof CoreFunction || expression instanceof ModelExtensionFunction) {
					String name = expression.toString().replaceAll("\\(.*\\)", "");
					names.add(name);
				}
				Expression[] arguments = ((Operation) expression).getArguments();
				if (arguments != null && arguments.length > 0) {
					for (Expression arg : arguments) {
						stack.push(arg);
					}
				}
			}
		}
		return names;
	}
	
	private interface OperationVaildator {
		
		ExpressionValidationResult validate(Operation operation);
		
	}
}

@@ -59,24 +59,50 @@
 nternal;%0D%0A%0D%0A
+import java.util.Arrays;%0D%0A
 import java.
@@ -1519,37 +1519,106 @@
 %09%09%09%09
-CustomFunction customFunction
+ModelExtensionFunction modelExtensionFun = (ModelExtensionFunction) operation;%0D%0A%09%09%09%09%09boolean valid
  = e
@@ -1638,14 +1638,15 @@
 ory.
-lookup
+isValid
 Func
@@ -1650,18 +1650,17 @@
 unction(
-(M
+m
 odelExte
@@ -1671,218 +1671,408 @@
 nFun
-ction) operation);%0D%0A%09%09%09%09%09if (null == customFunction) %7B%0D%0A%09%09%09%09%09%09String fullName = ((ModelExtensionFunction) operation).getFullName();%0D%0A%09%09%09%09%09%09String message = String.format(%22function '%25s' does not exist%22, f
+.getPrefix(), modelExtensionFun.getName());%0D%0A%09%09%09%09%09if (valid) %7B%0D%0A%09%09%09%09%09%09CustomFunction customFunction = expressionFactory.lookupFunction((ModelExtensionFunction) operation);%0D%0A%09%09%09%09%09%09return customFunction.validateArguments(contextNodeDef, nullToEmpty(operation));%0D%0A%09%09%09%09%09%7D else %7B%0D%0A%09%09%09%09%09%09String fullName = modelExtensionFun.getPrefix() == null ? modelExtensionFun.getName() : modelExtensionFun.getF
 ullName
+(
 );%0D%0A
@@ -2374,17 +2374,8 @@
 ROR,
- message,
  det
@@ -2394,111 +2394,8 @@
 );%0D%0A
-%09%09%09%09%09%7D else %7B%0D%0A%09%09%09%09%09%09return customFunction.validateArguments(contextNodeDef, nullToEmpty(operation));%0D%0A
 %09%09%09%09
@@ -2981,32 +2981,76 @@
 f Operation) %7B%0D%0A
+%09%09%09%09Operation op = (Operation) expression;%0D%0A
 %09%09%09%09ExpressionVa
@@ -3101,38 +3101,18 @@
 alidate(
-(Operation) expression
+op
 );%0D%0A%09%09%09%09
@@ -3157,16 +3157,116 @@
 esult;%0D%0A
+%09%09%09%09%7D else %7B%0D%0A%09%09%09%09%09Expression%5B%5D args = op.getArguments();%0D%0A%09%09%09%09%09stack.addAll(Arrays.asList(args));%0D%0A
 %09%09%09%09%7D%0D%0A%09

Improve expression validations: validate function names in default namespace