package eworks.RQL.model;

import java.net.URISyntaxException;
import java.util.*;
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 instances of &quot;RDFS classes&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 InstancesOfClassRange extends Range {

	private Variable var;
	private String className;
	private Collection classes;
	private Iterator dataResources;
	private RDF_Resource current;
	private RDF_Resource next;

	/**
	 * Creates a new range using the name of a certain &quot;RDFS class&quot; and a
	 * certain variable to bound. All instances of the given &quot;RDFS class&quot;
	 * will be bound to the given variable.
	 * 
	 * @param className The name of the &quot;RDFS class&quot;.
	 * @param var The variable to bound.
	 */	
	public InstancesOfClassRange( String className, Variable var ) {
		super( className );
		this.className=className.toLowerCase();
		this.var=var;
	}
	
	/**
	 * Creates a new range using the name of a certain &quot;RDFS class&quot;.
	 * 
	 * @param className The name of the &quot;RDFS class&quot;.
	 */	
	public InstancesOfClassRange( String className ) {
		this( className, null );
	}

	/**
	 * 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.classes = new ArrayList();
		
		Iterator i = this.model.getClasses().iterator();
		while (i.hasNext()) {
			RDF_Class c = (RDF_Class) i.next();
			if (c.getID().toLowerCase().endsWith(this.className))
				this.classes.add(c);
		}
		
		Collection potentiellSubClasses = this.model.getClasses();

		boolean done;
		do {
			potentiellSubClasses.removeAll(this.classes);
			done = true;
			
			i = potentiellSubClasses.iterator();
			while (i.hasNext()) {
				RDF_Class c = (RDF_Class) i.next();
				Iterator j = c.getsubClassOf().iterator();
				while (j.hasNext()) {
					RDF_Class d = (RDF_Class) j.next();
					if (this.classes.contains(d)) {
						this.classes.add(c);
						done = false;
					}
				}
			}
		} while (! done); 

		this.dataResources = this.model.getDataResources().iterator();
		this.current = null;
		this.next = null;
		
		this.nextHit(true);
	}
	
	/**
	 * 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.next != null);
	}
	
	/**
	 * 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 {
		this.nextHit(false);
	}
	
	private void nextHit( boolean ignoreMissing ) throws NoModelBoundException,
	NoSuchElementException {
		if (this.model == null)
			throw new NoModelBoundException();

		if (! this.hasMoreHits() && ! ignoreMissing)
			throw new NoSuchElementException();
		
		this.current = this.next;
		this.next = null;

		while (this.dataResources.hasNext()) {
			RDF_Resource res = (RDF_Resource) this.dataResources.next();
			Iterator types = res.gettype().iterator();
			while (types.hasNext()) {
				RDF_Class type = (RDF_Class) types.next();
				if (this.classes.contains(type)) {
					this.next = res;
					return;
				}
			}
		}
		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.model == null)
			throw new NoModelBoundException();

		if (this.current == null)
			throw new NoSuchElementException();
		
		symbols.bind(this.var,new UriLiteralValue(this.current.getID()));
	}
}