package eworks.RQL.model;

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

/**
 * <p>Represents a RQL range that retrieves &quot;RDF statements&quot;.</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 StatementRange extends Range {

	private Variable var1;
	private Variable var2;
	private Variable var3;
	private ArrayList statements;	
	private int currentStatement;
	private int nextStatement;

	/**
	 * Creates a new statement range using two certain data variables and a
	 * certain property variable.
	 * 
	 * @param var1 The data variable to be used fetching a statement's subject.
	 * @param var2 The property variable to be used fetching a statement's property.
	 * @param var3 The data variable to be used fetching a statement's object.
	 */	
	public StatementRange( DataVariable var1, PropertyVariable var2, DataVariable var3 ) {
		super( "{" + var1.getName() + "}" + var2.getName() + "{" + 
			var3.getName() + "}" );
		this.var1=var1;		
		this.var2=var2;		
		this.var3=var3;		
	}
	
	/**
	 * Resets this range so that a succeeding call of <code>nextHit()</code>
	 * will return the first hit (if any).
	 *
	 * @throws NoModelBoundException if no model has been bound using
	 *         <code>bindModel()</code>.
	 * @see #nextHit()
	 * @see #bindModel(Model)
	 */	
	public void reset() throws NoModelBoundException {
		if (this.model == null)
			throw new NoModelBoundException();

		this.statements = this.model.getStatements();
		
		this.currentStatement = -1;
		this.nextStatement = 0;
		this.nextHit();
		this.currentStatement = -1;
	}
	
	/**
	 * 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 {
		if (this.model == null)
			throw new NoModelBoundException();
		
		return this.nextStatement >= 0;
	}
	
	/**
	 * 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 {
		if (this.model == null)
			throw new NoModelBoundException();

		if (! this.hasMoreHits())
			throw new NoSuchElementException();

		this.currentStatement = this.nextStatement;
		this.nextStatement = -1;
		
		for (int i=this.currentStatement+1; i<this.statements.size(); i++) {
			Triple t = (Triple) this.statements.get(i);
			if (! t.getPredicate().startsWith("http://www.w3.org/2000/01/rdf-schema#"))
				if (! t.getPredicate().startsWith("http://www.w3.org/1999/02/22-rdf-syntax-ns#")) {
					this.nextStatement = i;
					return;
				}
		}
	}

	/**
	 * 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 {
		
		if (this.currentStatement < 0)
			throw new NoSuchElementException();
		
		if (this.model == null)
			throw new NoModelBoundException();
		
		Triple t = (Triple) statements.get(this.currentStatement);
		Object o = t.getObject();
		
		symbols.bind(var1,new UriLiteralValue(t.getSubject()));
		symbols.bind(var2,new UriLiteralValue(t.getPredicate()));
		
		LiteralValue val3;
		if (o instanceof Literal)
			val3 = new StringLiteralValue(((Literal) o).getvalue());
		else
			val3 = new UriLiteralValue((String) o);
		symbols.bind(var3, val3);
	}
}