package eworks.RQL.model;

import eworks.RQL.engine.*;
import eworks.RQL.engine.SymbolTable;
import gr.forth.ics.vrp.corevrp.model.*;
import java.net.URISyntaxException;
import java.util.*;

/**
 * <p>A list of single &quot;RQL ranges&quot; represented by
 * <code>Range</code>-instances.</p>
 * <p>A RQL range is used to define a query's &quot;data source&quot;, that's the
 * expression after &quot;FROM&quot; according to the RQL syntax. A range is compareable
 * to the list of table names (after &quot;FROM&quot; as well) according to the SQL
 * syntax.</p>
 * 
 * @author Fabian Wleklinski (<a href="mailto:fabian@wleklinski.de">fabian@wleklinski.de</a>)
 * @version 1.00 (2003-11-03)
 * @see <a href="http://139.91.183.30:9090/RDF/RQL/" target="_blank">
 *      The RDF Query Language (RQL)</a>
 */
public class RangeList {

	private Range[] ranges;
	
	private boolean hasMoreHits;

	/**
	 * Creates a new and empty range list without any encapsulated range.
	 */
	public RangeList() {
		super();
		this.ranges = new Range[0];
	}

	/**
	 * Creates a new range list encapsulating a single range.
	 * 
	 * @param range The range to encapsulate.
	 */
	public RangeList( Range range ) {
		super();
		this.ranges = new Range[] { range };
	}

	/**
	 * Returns the number of ranges encapsulated by this range list.
	 * 
	 * @return The number of ranges.
	 */
	public int getLength() {
		return this.ranges.length;
	}
	
	/**
	 * Returns the i-th range that is encapsulated by this range list.
	 * 
	 * @param i The number of the range to return.
	 * @return The i-th range (0 <= i <= <code>getLength()</code>).
	 * @throws ArrayIndexOutOfBoundsException if <code>i</code> is negative or not
	 *         lower than <code>getLength()</code>.
	 * @see #getLength()
	 */
	public Range get(int i) {
		return this.ranges[i];
	}
	
	/**
	 * Binds this range list to a certain model.
	 * 
	 * @param model The model this range list is bound to.
	 */
	public void bindModel( Model model ) {
		// assume that there are some hits
		this.hasMoreHits = true;
		
		// iterate trough all ranges, e.g. {o}@p{s} or anything else
		for (int i=this.ranges.length-1; i>=0; i--) {
			// bind this range with the given model
			ranges[i].bindModel(model);
			
			try {
				if (! ranges[i].hasMoreHits())
					// current range has no hits => the whole rangelist has no hits
					this.hasMoreHits = false;
				else
					// get the first hit for all ranges - except the last one
					if (i < this.ranges.length-1)
						ranges[i].nextHit();
			} catch(NoModelBoundException e) {
				// cannot happen
				System.out.println(e.toString());
			}
		}
	}
	
	/**
	 * Checks if there are more hits that can be iteratively retrieved using
	 * <code>nextHit()</code>.
	 *
	 * @return <code>true</code> if and only if there are more hits.
	 * @throws NoModelBoundException if no model has been bound using
	 *         <code>bindModel()</code>.
	 * @see #nextHit()
	 * @see #bindModel(Model)
	 */	
	public boolean hasMoreHits() throws NoModelBoundException {
		return this.hasMoreHits;
	}
		
	/**
	 * Binds the current hit's values to some symbols. A caller should use
	 * <code>hasMoreHits()</code> and <code>nextHit()</code> to ensure that
	 * there is are some hits and to navigate iteratively forward trough them.
	 *
	 * @throws NoModelBoundException if no model has been bound using
	 *         <code>bindModel()</code>.
	 * @throws NoSuchElementException if there is no current hit.
	 * @throws VariableAlreadyBoundException if the used variable has already
	 *         been bound to another value.
	 * @throws URISyntaxException if a ressource is no valid URI, see
	 *         <a href="http://www.w3.org/Addressing/URL/uri-spec.html" target="_blank">
	 *         Universal Resource identifiers in WWW</a>.
	 * @see #hasMoreHits()
	 * @see #nextHit()
	 * @see #bindModel(Model)
	 */	
	public void bindSymbols(SymbolTable symbols) throws NoModelBoundException,
	NoSuchElementException, VariableAlreadyBoundException, URISyntaxException {

		for (int i=this.ranges.length-1; i>=0; i--) {
			this.ranges[i].bindSymbols(symbols);
		}
	}

	/**
	 * Gets the next hit, if any. Callers should use <code>hasMoreHits()</code>
	 * before to ensure that there is a further hit. After calling this method
	 * <code>bindSymbols()</code> can be used to bind the next hit's values to
	 * some symbols.
	 *
	 * @throws NoModelBoundException if no model has been bound using
	 *         <code>bindModel()</code>.
	 * @throws NoSuchElementException if there is no further hit.
	 * @see #hasMoreHits()
	 * @see #bindSymbols(SymbolTable)
	 * @see #bindModel(Model)
	 */	
	public void nextHit() throws NoModelBoundException,
	NoSuchElementException {
		
		boolean found = false;
		this.hasMoreHits = false;
		
		for (int i=this.ranges.length-1; i>=0; i--) {
			Range r = this.ranges[i];
			
			if (! found) {
				if (r.hasMoreHits())
					found = true;
				else
					r.reset();
				r.nextHit();
			}
			
			if (! this.hasMoreHits)
				this.hasMoreHits = r.hasMoreHits();
		}
		
		if (! found)
			throw new NoSuchElementException();
	}

	/**
	 * Adds a new range to this range list.
	 * 
	 * @param range The range to be added.
	 */	
	public void add(Range range) {
		Range[] ranges = new Range[ this.ranges.length + 1 ];
				
		System.arraycopy(this.ranges,0,ranges,0,this.ranges.length);
		ranges[this.ranges.length] = range;
		this.ranges = ranges;
	}

	/**
	 * Returns this range list's string representation.
	 * 
	 * @return This range list's string representation.
	 */
	public String toString() {
		String result = " RangeList[";
		for (int i=this.ranges.length-1; i>=0; i--) {
			result += this.ranges[i].toString();
		}
		result += "]";
			 
		return result;
	}
}
