/*******************************************************************************
 * Copyright (c) 2006 Oracle Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Oracle Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.bpel.validator.xpath0;

/**
 * Validator BPEL model dependency
 */

import javax.xml.namespace.QName;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.eclipse.bpel.validator.model.ARule;
import org.eclipse.bpel.validator.model.IModelQuery;
import org.eclipse.bpel.validator.model.IModelQueryLookups;
import org.eclipse.bpel.validator.model.INode;
import org.eclipse.bpel.validator.model.IProblem;
import org.eclipse.bpel.validator.model.Validator;


/**
 * This validator validates the XPath expressions used in the BPEL source.
 * <p>
 * The validation consists of 2 steps:
 * <ul>
 * <li>Compilation and syntax check,and
 * <li>Static analysis on the expression
 * </ul>
 * <p>
 * The static analysis is performed by visiting the parsed expression tree and
 * checking certain things against the actual BPEL model.
 * <p>
 * The nodes which may contain XPath expressions in BPEL 2.0 and their expected
 * type is as follows:
 * 
 * <ul>
 * <li> branches - unsigned integer
 * <li> condition - boolean
 * <li> finalCounterValue - unsigned integer
 * <li> for - duration
 * <li> from - any
 * <li> joinCondition - boolean
 * <li> repeatEvery - duration
 * <li> startCounterValue - unsigned integer
 * <li> to - variable reference expression ($var/foo)
 * <li> transitionCondition - boolean
 * <li> until - deadline
 * 
 * </ul>
 * 
 * 
 * <p>
 * 
 * @author Michal Chmielewski (michal.chmielewski@oracle.com)
 * @date Sep 27, 2006
 * 
 */

@SuppressWarnings( { "nls", "boxing" })
public class XPathValidator extends Validator {

	/** The string format of the XPath expression */
	String exprString;

	String exprStringTrimmed;

	/** the parsed XPath expression */
	XPathExpression xpathExpr;

	/** The node name in which this expression is placed */
	protected String fNodeName;

	/** expression name by node name */
	protected String fExprByNode;

	/** parent node */
	protected INode fParentNode;

	protected XPathExpression xpathExpression;

	/**
	 * Start the validation of this XPathExpression
	 */

	@Override
	public void start() {
		super.start();

		exprString = mModelQuery.lookup(mNode,
				IModelQueryLookups.LOOKUP_TEXT_TEXT, null, null);

		exprStringTrimmed = exprString.trim();

		fNodeName = toString(mNode.nodeName());

		fParentNode = mNode.parentNode();

		/** fExprByNode is a key to a localization map */
		fExprByNode = "text.node." + mNode.nodeName().getLocalPart();
	}

	/**
	 * Do a quick sanity check.
	 */

	public void rule_SanityCheck_1() {
		IProblem problem;

		if (isEmptyOrWhitespace(exprString)) {
			problem = createError();
			problem.fill("XPATH_EMPTY_EXPRESSION", fNodeName, fExprByNode);
			exprString = null;

			disableRules();
		}
	}

	/**
	 * Check syntax of this expression ...
	 * 
	 */

	public void rule_CheckExpressionSyntax_2() {

		if (exprString == null) {
			return;
		}

		IProblem problem;

		try {
			XPathFactory factory = XPathFactory.newInstance();
			xpathExpression = factory.newXPath().compile(exprString);			
		} catch (XPathExpressionException e) {
			String message = getMessageFrom ( e );
						problem = createError();
			problem.fill("XPATH_EXPRESSION_SYNTAX", fNodeName,
					exprStringTrimmed, fExprByNode, message);
			// repointOffsets(problem, e.getPosition(), e.getPosition() + 3);
			// TODO: Position in the expression ... ?
			disableRules();

		} 
	}

	/**
	 * Sets the value of the expression result. This is run as the last rule in
	 * pass1 and it examines the top element on the stack that the
	 * visitor/validator combination has produced.
	 * 
	 */

	public void rule_ExpressionType_999999() {
		setValue("expression.type",null);
	}
	
	
	
	
	String getMessageFrom (Throwable t) {
		int i = 10;
		while (t.getCause() != null && i > 0) {
			t = t.getCause();
			i--;
		}
		return t.getMessage();		
	}
	
	
//	protected void repointOffsets(IProblem problem, Expr expr) {
//		if (expr.getPosition() < 0 || expr.getEndPosition() < 0) {
//			return;
//		}
//		repointOffsets(problem, expr.getPosition(), expr.getEndPosition());
//	}

	/**
	 * @param problem
	 * @param offset
	 * @param offsetEnd
	 */
	protected void repointOffsets(IProblem problem, int offset, int offsetEnd) {

		int charStart = mModelQuery.lookup(mNode,
				IModelQueryLookups.LOOKUP_NUMBER_CHAR_START_2, -1);
		int charEnd = mModelQuery.lookup(mNode,
				IModelQueryLookups.LOOKUP_NUMBER_CHAR_END_2, -1);

		int lineNo = mModelQuery.lookup(mNode,
				IModelQueryLookups.LOOKUP_NUMBER_LINE_NO_2, -1);
		int columnNo = mModelQuery.lookup(mNode,
				IModelQueryLookups.LOOKUP_NUMBER_COLUMN_NO_2, -1);

		if (charStart < 0 || charEnd < 0) {
			return;
		}

		charStart += offset;
		charEnd += offsetEnd;

		// Technically that should be it, but there are 3 issues:
		// - have to re-point column
		// - have to re-point row
		// - have to adjust for \n in charStart and charEnd (JAXEN issue).

		for (int i = 0, j = Math.min(exprString.length(), offset); i < j; i++) {

			if (exprString.charAt(i) == '\n') {
				// This is a JAXEN problem/hack
				charStart += 1;

				if (lineNo > 0) {
					lineNo += 1;
				}
				columnNo = 0;
			}

			if (columnNo >= 0) {
				columnNo += 1;
			}
		}

		// Now do the same thing for charEnd
		for (int i = 0, j = Math.min(exprString.length(), offsetEnd); i < j; i++) {
			// This is a JAXEN problem/hack
			if (exprString.charAt(i) == '\n') {
				charEnd += 1;
			}
		}

		problem.setAttribute(IProblem.CHAR_START, charStart);
		problem.setAttribute(IProblem.CHAR_END, charEnd);

		if (lineNo > 0) {
			problem.setAttribute(IProblem.LINE_NUMBER, lineNo);
		}
		if (columnNo > 0) {
			problem.setAttribute(IProblem.COLUMN_NUMBER, columnNo);
		}

	}

	/**
	 * Perform static analysis of this XPath expression.
	 * 
	 */
	@ARule(sa = 0, desc = "Create the static analysis visitor for expression analysis", author = "michal.chmielewski@oracle.com", date = "01/30/2007")
	public void rule_StaticXPathExpressionAnalysis_10() {

		// mVisitor = new XPathVisitor(this);

		INode typeNode = getValue("expression.type", null);

//		if (typeNode != null) {
//			mVisitor.contextPush(typeNode);
//		}
	}


	/**
	 * Check the deadline expression.
	 */

	public void checkDeadlineExpression() {

	}

	/**
	 * Check the duration expression variant.
	 */

	public void checkDurationExpression() {
	}

	/**
	 * Check boolean expressions
	 */

	public void checkBooleanExpression() {

	}

	/**
	 * Check the integer expression.
	 * 
	 * Not this is not a rule.
	 */

	public void checkIntegerExpression() {
	}

	boolean isNamespaceOK(QName qname) {
		if (qname == null) {
			return false;
		}
		return (isEmpty(qname.getNamespaceURI()) == isEmpty(qname.getPrefix()));
	}

	protected boolean isJoinCondition() {
		/** ND_ are QNames, so make sure you compare apples to apples ... */
		return ND_JOIN_CONDITION.equals(mNode.nodeName());
	}

	protected IModelQuery getModelQuery() {
		return mModelQuery;
	}

	protected INode getNode() {
		return mNode;
	}

	/**
	 * This exists here for visibility to XPathVisitor.
	 * 
	 * @see org.eclipse.bpel.validator.model.Validator#createError()
	 */

	@Override
	protected IProblem createError() {
		return super.createError();
	}

	/**
	 * This exists here for visibility to XPathVisitor.
	 * 
	 * @see org.eclipse.bpel.validator.model.Validator#createInfo()
	 */
	@Override
	protected IProblem createInfo() {
		return super.createInfo();
	}

	/**
	 * This exists here for visibility to XPathVisitor.
	 * 
	 * @see org.eclipse.bpel.validator.model.Validator#createWarning()
	 */
	@Override
	protected IProblem createWarning() {
		return super.createWarning();
	}

	/**
	 * @see org.eclipse.bpel.validator.model.Validator#runRules(java.lang.String,
	 *      java.lang.Object[])
	 */

	@Override
	protected void runRules(String tag, Object... args) {
		// this exists here for visibility to XPathVisitor.
		super.runRules(tag, args);
	}

	protected boolean duplicateThing(String... args) {

		String key = null;
		if (args.length == 1) {
			key = args[0];
		} else {
			StringBuilder sb = new StringBuilder();
			for (String a : args) {
				sb.append(a);
			}
			key = sb.toString();
		}

		if (containsValueKey(key)) {
			return true;
		}

		setValue(key, Boolean.TRUE);
		return false;
	}

	protected boolean checkPrefix(String prefix, String name) {

		if (isEmptyOrWhitespace(prefix)) {
			return true;
		}

		String nsURI = lookupNamespace(prefix);

		if (isEmpty(nsURI)) {
			IProblem problem = createError();
			problem.fill("XPATH_UNRESOLVED_NAMESPACE_PREFIX", //$NON-NLS-1$
					prefix, name);

			return false;
		}

		return true;
	}

	protected String lookupNamespace(String prefix) {
		return mModelQuery.lookup(mNode,
				IModelQueryLookups.LOOKUP_TEXT_PREFIX2NS, prefix, null);
	}


}
