package eworks.RQL.model;

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

/**
 * <p>Represents a RQL query.</p>
 * <p>Remark: Currently RqlEngine does not support nested queries (subqueries),
 * although RQL itself does support them. In future this class could be used for
 * representing a query's subqueries as well.</p>
 * 
 * @author Fabian Wleklinski (<a href="mailto:fabian@wleklinski.de">fabian@wleklinski.de</a>)
 * @see <a href="http://139.91.183.30:9090/RDF/RQL/" target="_blank">
 *      The RDF Query Language (RQL)</a>
 * @version 1.00 (2003-11-03)
 */
public class Query {
	
	private ProjectionList projectionList;
	private RangeList rangeList;
	private Condition c;
	private boolean distinct;
	
	/**
	 * Creates a new and empty RQL query.
	 */
	public Query() {
	}
	
	/**
	 * Creates a new query with a certain projection list, range list
	 * and disjunction condition.
	 * 
	 * @param pl The projection list to use.
	 * @param rangeList The range list to use.
	 * @param c The disjunction condition to use.
	 * @param distinct If set to <code>true</code>, there will be no duplicates
	 *        in this query's result. (See SQL.)
	 */
	public Query( ProjectionList pl, RangeList rangeList,
		DisjunctionCondition c, boolean distinct) {
			
		this();
		this.setProjection(pl);
		this.setRange(rangeList);
		this.c=c;
		this.rangeList = rangeList;
		this.distinct = distinct;
	}
	
	/**
	 * Returns <code>true</code> if there are no duplicates allowed in this
	 * query's result.
	 * 
	 * @return <code>true</code> if and only if there are no duplicates allowed.
	 */
	public boolean isDistinct() {
		return this.distinct;
	}

	/**
	 * Returns this query's condition used to select, that's the <code>WHERE</code>
	 * clause according to the RQL syntax.
	 * 
	 * @return This query's condition used to select.
	 */	
	public Condition getSelection() {
		return this.c;
	}

	/**
	 * Returns this query's projection list.
	 * 
	 * @return This query's projection list.
	 */	
	public ProjectionList getProjection() {
		return this.projectionList;
	}
	
	/**
	 * Sets this query's projection list.
	 * 
	 * @param pl This query's new projection list.
	 */
	public void setProjection( ProjectionList pl ) {
		this.projectionList=pl;
	}
	
	/**
	 * Returns this query's range.
	 * 
	 * @return This query's range.
	 */
	public RangeList getRange() {
		return this.rangeList;
	}
	
	/**
	 * Sets this query's range.
	 * 
	 * @param rangeList This query's new range.
	 */
	public void setRange( RangeList rangeList ) {
		this.rangeList=rangeList;
	}
	
	/**
	 * Returns this query's string representation.
	 * 
	 * @return This query's string representation.
	 */
	public String toString() {
		String result = "Query[";

		if (this.projectionList != null)
			result += this.projectionList.toString();
			 
		result += this.rangeList.toString();
		
		if (this.c != null)
			result += " Condition[" + this.c.toString() + "]";
			 
		result += " ]";
		
		return result;
	}

	/**
	 * Compacts this query. This is done by calling the disjunction's
	 * <code>compact()</code>-method and storeing the result.
	 */
	public void compact() {
		if (this.c != null)
			this.c = this.c.compact();
	}
	
	/**
	 * Executes this query with an empty symbol table.
	 * 
	 * @param model The model to be used.
	 * @return The query's result.
	 * @throws VariableAlreadyBoundException if one and the same variable is tried
	 *         to be bound twice while evaluating this query.
	 * @throws VariableNotBoundException if a variable's value is being used
	 *         although the variable itself has not been bound.
	 */
	public Tuples execute(Model model)
	throws VariableAlreadyBoundException, VariableNotBoundException {
		
		return this.execute(new eworks.RQL.engine.SymbolTable(), model);
	}
	
	/**
	 * Executes this query with a given symbol table.
	 * 
	 * @param symbols The initial symbol table to be used.
	 * @param model The model to be used.
	 * @return The query's result.
	 * @throws VariableAlreadyBoundException if one and the same variable is tried
	 *         to be bound twice while evaluating this query.
	 * @throws VariableNotBoundException if a variable's value is being used
	 *         although the variable itself has not been bound.
	 */
	public Tuples execute(eworks.RQL.engine.SymbolTable symbols, Model model)
	throws VariableAlreadyBoundException, VariableNotBoundException {
		
		Tuples resultingTupels = new UngroupedTuples();
		this.rangeList.bindModel(model);
		
		try {
			while (this.rangeList.hasMoreHits()) {

				// bind variables
				symbols.clear();

				this.rangeList.nextHit();
				try {
					this.rangeList.bindSymbols(symbols);
				} catch( URISyntaxException e ) {
					System.out.println(e);
				}
				
				// check if this statements satisfies the query's selection:
				if (this.c == null || this.c.matches(symbols)) {

					Tuple resultingTupel;
					if (this.projectionList != null) {
						int length = this.projectionList.getLength();
						if (length==3)
							resultingTupel = new Statement();
						else
							resultingTupel = new TupleImpl(length);
						
						for (int j=this.projectionList.getLength()-1;j>=0;j--) {
							Projection projection = this.projectionList.get(j);
							
							resultingTupel.set(j, projection.getVariable().getValue(symbols));
						}
					} else {
						resultingTupel = symbols.getTuple();
					}
						
					if (this.distinct) {
						if (resultingTupels.contains(resultingTupel))
							continue;
					}
						
					resultingTupels.add(resultingTupel);
				}
			}
		} catch (NoModelBoundException e) {
			System.out.println(e);
		}
		
		return resultingTupels;
	}
}
