/*******************************************************************************
 * 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.rules;

import java.util.Map;
import java.util.Set;

import org.eclipse.bpel.validator.model.ARule;
import org.eclipse.bpel.validator.model.Filters;
import org.eclipse.bpel.validator.model.IFilter;
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.NodeNameFilter;



/**
 * Validates variable related rules.
 * <p>
 * When thinking 
 * @author Michal Chmielewski (michal.chmielewski@oracle.com)
 * @date Sep 14, 2006
 *
 */

@SuppressWarnings("nls")

public class SourceValidator extends CValidator {
	
	/** Parent nodes */
	static public IFilter<INode> PARENTS = new NodeNameFilter( ND_SOURCES );
	
	
	String ncName ;	

	INode fFlowNode;


	protected Set<String> fSet;

	protected Map<String,INode> fSourceMap;


	protected INode fRepeatableConstructNode;


	protected INode fFaultHandlerNode;


	protected INode fActivityNode;


	protected Map<String,INode> fTargetMap;


	protected INode fTargetActivity;
	
	
	/**
	 * Start performing checks.
	 */	
	
	@Override
	protected void start () {
		super.start();		
		ncName = mNode.getAttribute( AT_LINK_NAME );
		
		// activity/sources/source
		fActivityNode = fParentNode.parentNode();
	}
	
	
	/**
	 * Rule to check the name of the variable. 
	 */
	
	public void rule_CheckName_1 () {					
		// Must be a valid NCName ...
		checkNCName(mNode, ncName, AT_LINK_NAME );			
	}
	
	
	/**
	 * 
	 */
	@ARule(
		sa = 1200,
		desc = "Check if we are within a flow, if not disable rest of rules",
		author = "michal.chmielewski@oracle.com",
		date = "02/17/2007"
	)
	public void rule_CheckIfInFlow_2 () {
		
		fFlowNode = mSelector.selectParent(fActivityNode, Filters.FLOW );
		if (isUndefined(fFlowNode) ) {
			
			disableRules(10,1000);
			return ;
		} 	
		fSet = getValue(fFlowNode, FlowValidator.LINKS_SET,null);
	}
	
	
	/**
	 * 
	 */
	
	@ARule(
		sa = 65,
		desc = "The value of the linkName attribute of <source> MUST be the name of a <link> declared in an enclosing <flow> activity.",
		author = "michal.chmielewski@oracle.com",
		date = "02/16/2007"
	)		
	
	public void rule_CheckAgainstDeclaredLink_11 () {
		if (fSet == null) {
			return ;
		}
		
		IProblem problem;
		
		if (fSet.contains(ncName) == false) {
			problem = createError();
			problem.fill("BPELC_LINK__UNDEFINED", 
					toString(mNode.nodeName()),
					ncName
			);
		}				
	}
	
	
	/**
	 * Check if source already used.
	 */
	
	@ARule(
		sa = 66,
		desc = "Every link must have exactly one activity as source and one as target",
		author = "michal.chmielewski@oracle.com",
		date = "02/17/2007"
	)
	public void rule_CheckIfSourceAlreadyUsed_12 () {
		fSourceMap = getValue(fFlowNode, FlowValidator.SOURCE_MAP,null);
		
		IProblem problem;
		
		if (fSourceMap.containsKey(ncName)) {
			problem = createError();
			problem.fill("BPELC_LINK__NAME_USED",
				toString(mNode.nodeName()),
				ncName
			);
			
			return ;
		}
		
		// activity/sources/source
		fSourceMap.put( ncName, fActivityNode );
	}
	
	

	/**
	 * Cross of repeatable constructs.
	 */
	
	@ARule(
		sa = 70,
		desc = "Link must not cross repeatable constructs",
		author = "michal.chmielewski@oracle.com",
		date = "02/17/2007"
	)
	
	public void rule_CheckRepeatableConstruct_15 () {

		// activity / sources / source
		
		fRepeatableConstructNode = mSelector.selectParent(
				fActivityNode.parentNode(), 
				Filters.REPEATABLE_CONSTRUCT);
	
		// if not there, then we are done
		if (isUndefined(fRepeatableConstructNode)) {
			return ;
		}
		
		// otherwise check if 
		// fFlowNode is a ancestor of fRepeatableNode
		INode aFlowNode = mSelector.selectParent(fRepeatableConstructNode, 
			new IFilter<INode>() {
				public boolean select(INode node) {
					return node.equals(fFlowNode);
				}			
			}
		);
		
		IProblem problem;
		
		if (mModelQuery.check(IModelQueryLookups.TEST_EQUAL, aFlowNode, fFlowNode)) {
			
			problem = createError();
			problem.fill("BPELC_LINK__CROSS_REPEATABLE",
					toString(mNode.nodeName()),
					ncName,
					toString(fRepeatableConstructNode.nodeName()),
					fRepeatableConstructNode.getAttribute(AT_NAME)
			);			
		}		
		
	}
	
	

	/**
	 * Cross of event handler boundary. We run this rule at the end, to make sure 
	 * that all the links had been resolved during pass 1.
	 */
	
	
	@ARule(
		sa = 71,
		desc = "Check for links crossing event handlers boundaries",
		author = "michal.chmielewski@oracle.com",
		date = "02/17/2007",
		tag = "pass2"
	)
	
	public void rule_CheckEventHandlersBoundary_20 () {

		// activity / sources / source
		
		// see if we are crossing an fault handler boundary
		fFaultHandlerNode = mSelector.selectParent(
				fActivityNode.parentNode(), 
				Filters.FAULT_HANDLER_BOUNDARY );
	
		
		// if not there, then we are done
		if (isUndefined(fFaultHandlerNode)) {
			return ;
		}
		
		// Lookup the target map, so that we can find out what the target 
		// of this link is.
		fTargetMap = getValue(fFlowNode, FlowValidator.TARGET_MAP, null);
		
		// Get the target activity ...
		fTargetActivity = fTargetMap.get(ncName);
		
		// if not there, we are out
		if (isUndefined(fTargetActivity)) {
			return ;
		}
		
		// make sure that the target activity is outside the scope 
		// containing the event handler.
		INode scope = mSelector.selectParent(fFaultHandlerNode, Filters.SCOPE);
		
		if (isUndefined(scope)) {
			// problem !
			return ;
		}
		
		// walk up from the target activity, if we hit the scope then bad.
		// we must be outside the scope.
		
		INode target = fTargetActivity;
		while (target != null) {
			
			if (mModelQuery.check(IModelQueryLookups.TEST_EQUAL, target, scope)) {
				
				// problem
				
				IProblem problem = createError();
				problem.fill("BPELC_LINK__OUTBOUND_ONLY",
						toString(mNode.nodeName()),
						ncName,
						toString(fFaultHandlerNode.nodeName()) 
					);

				break;
			}
			
			target = target.parentNode();
		}
		
		
		
	}
	
	/** 
	 * End of public rule methods.
	 * 
	 * Other methods are support methods for this class to perform its
	 * validation function.
	 * 
	 */

}
