package org.seamframework.sql;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

public class Query extends Expressions implements Statement  {
	
	private final List<From<?>> froms = new ArrayList<From<?>>();
	private final List<Expression<?>> selections = new ArrayList<Expression<?>>();
	private final List<Selection<?>> orderBys = new ArrayList<Selection<?>>();
	private Expression<Boolean> where;
	
	private final class Alias<Y> implements Expression<Y> {
		private final String alias;
		private final Expression<Y> expression;

		private Alias(Expression<Y> expression, String alias) {
			this.alias = alias;
			this.expression = expression;
		}
		
		@Override
		public Type<Y> getType() {
			return expression.getType();
		}
		
		@Override
		public String toSql() {
			return new StringBuffer() 
				.append( expression.toSql() )
				.append(" as ")
				.append(alias)
				.toString();
		}
		
		public String getAlias() {
			return alias;
		}
		
		@Override
		public List<Parameter<?>> getParameters() {
			return expression.getParameters();
		}
	}

	protected List<Expression<?>> getSelections() {
		return Collections.unmodifiableList(selections);
	}
	
	public <Y> From<Y> from(Table<Y> table) {
		From<Y> from = new From<Y>(table);
		froms.add(from);
		return from;
	}
	
	public <Y> From<Y> from(Table<Y> table, String alias) {
		From<Y> from = new From<Y>(table, alias);
		froms.add(from);
		return from;
	}
	
	public void select(Expression<?>... expressions) {
		for (Expression<?> e: expressions) {
			selections.add(e);
		}
	}
	
	public <Y> Selection<Y> select(Expression<Y> expression, String alias) {
		Alias<Y> aliased = new Alias<Y>(expression, alias);
		selections.add(aliased);
		return aliased; 
	}
	
	public void where(Expression<Boolean> where) {
		this.where = where;
	}
	
	public void orderBy(Selection<?>... selections) {
		for (Selection<?> s: selections) {
			orderBys.add(s);
		}
	}
	
	public String toSql() {
		StringBuilder sql = new StringBuilder();
		sql.append("select ");
		for ( Iterator<Expression<?>> i = selections.iterator(); i.hasNext(); ) {
			sql.append( i.next().toSql() );
			if ( i.hasNext() ) sql.append(", ");
		}
		sql.append(" from ");
		for ( From<?> from: froms) {
			sql.append( from.toSql() );
		}
		if (where!=null) {
			sql.append(" where ")
				.append( where.toSql() );
		}
		if ( !orderBys.isEmpty() ) {
			sql.append(" order by ");
			for ( Iterator<Selection<?>> i = orderBys.iterator(); i.hasNext(); ) {
				Selection<?> s = i.next();
				if ( s instanceof Alias<?> ) {
					sql.append( ( (Alias<?>) s).getAlias() );
				}
				else {
					int ind = selections.indexOf(s);
					if (ind<0) throw new NoSuchElementException();
					sql.append( ind+1 );
				}
				if ( i.hasNext() ) sql.append(", ");
			}
		}
		return sql.toString();
	}
	
	@Override
	public List<Parameter<?>> getParameters() {
		return Collections.unmodifiableList(where.getParameters());
	}
	
}
