/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jena.reasoner.rulesys.impl;

import java.util.*;

import org.apache.jena.graph.* ;

/**
 * Represents one input left of a join node. The queue points to a sibling queue
 * representing the other leg which should be joined against.
 */
public class RETEQueue implements RETESinkNode, RETESourceNode {

	/**
	 * A multi-set of partially bound envionments indices for matching are
	 * specified by matchIndices
	 */
	protected BindingVectorMultiSet queue;

	/** A set of variable indices which should match between the two inputs */
	protected byte[] matchIndices;

	/** The sibling queue which forms the other half of the join node */
	protected RETEQueue sibling;

	/** The node that results should be passed on to */
	protected RETESinkNode continuation;

	/**
	 * Constructor. The queue is not usable until it has been bound to a sibling
	 * and a continuation node.
	 * 
	 * @param matchIndices
	 *            set of variable indices which should match between the two
	 *            inputs
	 */
	public RETEQueue(byte[] matchIndices) {
		this.matchIndices = matchIndices;
		this.queue = new BindingVectorMultiSet(matchIndices);
	}

	/**
	 * Constructor. The queue is not usable until it has been bound to a sibling
	 * and a continuation node.
	 * 
	 * @param matchIndexList
	 *            List of variable indices which should match between the two
	 *            inputs
	 */
	public RETEQueue(List<? extends Byte> matchIndexList) {
		int len = matchIndexList.size();
		matchIndices = new byte[len];
		for (int i = 0; i < len; i++) {
			matchIndices[i] = matchIndexList.get( i );
		}
		this.queue = new BindingVectorMultiSet(matchIndices);
	}

	/**
	 * Set the sibling for this node.
	 */
	public void setSibling(RETEQueue sibling) {
		this.sibling = sibling;
	}

	/**
	 * Set the continuation node for this node (and any sibling)
	 */
	@Override
	public void setContinuation(RETESinkNode continuation) {
		this.continuation = continuation;
		if (sibling != null)
			sibling.continuation = continuation;
	}

	/**
	 * Propagate a token to this node.
	 * 
	 * @param env
	 *            a set of variable bindings for the rule being processed.
	 * @param isAdd
	 *            distinguishes between add and remove operations.
	 */
	@Override
	public void fire(BindingVector env, boolean isAdd) {
		// Store the new token in this store
		if (isAdd) {
			queue.add(env);
		} else {
			queue.remove(env);
		}

		// Cross match new token against the entries in the sibling queue

		Node[] envNodes = env.getEnvironment();

		for (Iterator<BindingVector> i = sibling.queue.getSubSet(env); i
				.hasNext();) {
			Node[] candidate = i.next().getEnvironment();
			// matching is no longer required since queue.getSubSet(env) returns
			// a HashMap with matching BindingVector's

			// Instantiate a new extended environment
			Node[] newNodes = new Node[candidate.length];
			for (int j = 0; j < candidate.length; j++) {
				Node n = candidate[j];
				newNodes[j] = (n == null) ? envNodes[j] : n;
			}
			BindingVector newEnv = new BindingVector(newNodes);
			// Fire the successor processing
			continuation.fire(newEnv, isAdd);
		}
	}

	/**
	 * Clone this node in the network.
	 * 
	 * @param context
	 *            the new context to which the network is being ported
	 */
	@Override
	public RETENode clone(Map<RETENode, RETENode> netCopy,
			RETERuleContext context) {
		RETEQueue clone = (RETEQueue) netCopy.get(this);
		if (clone == null) {
			clone = new RETEQueue(matchIndices);
			netCopy.put(this, clone);
			clone.setSibling((RETEQueue) sibling.clone(netCopy, context));
			clone.setContinuation((RETESinkNode) continuation.clone(netCopy,
					context));
			clone.queue.putAll(queue);
		}
		return clone;
	}
}
