/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.sql;

import java.util.HashSet;
import java.util.Iterator;

import org.hibernate.dialect.Dialect;

/**
 * A translated HQL query
 *
 * @author Gavin King
 */
public class QuerySelect {
	private Dialect dialect;
	private JoinFragment joins;
	private StringBuilder select = new StringBuilder();
	private StringBuilder where = new StringBuilder();
	private StringBuilder groupBy = new StringBuilder();
	private StringBuilder orderBy = new StringBuilder();
	private StringBuilder having = new StringBuilder();
	private String comment;
	private boolean distinct;

	private static final HashSet<String> DONT_SPACE_TOKENS = new HashSet<String>();

	static {
		//dontSpace.add("'");
		DONT_SPACE_TOKENS.add( "." );
		DONT_SPACE_TOKENS.add( "+" );
		DONT_SPACE_TOKENS.add( "-" );
		DONT_SPACE_TOKENS.add( "/" );
		DONT_SPACE_TOKENS.add( "*" );
		DONT_SPACE_TOKENS.add( "<" );
		DONT_SPACE_TOKENS.add( ">" );
		DONT_SPACE_TOKENS.add( "=" );
		DONT_SPACE_TOKENS.add( "#" );
		DONT_SPACE_TOKENS.add( "~" );
		DONT_SPACE_TOKENS.add( "|" );
		DONT_SPACE_TOKENS.add( "&" );
		DONT_SPACE_TOKENS.add( "<=" );
		DONT_SPACE_TOKENS.add( ">=" );
		DONT_SPACE_TOKENS.add( "=>" );
		DONT_SPACE_TOKENS.add( "=<" );
		DONT_SPACE_TOKENS.add( "!=" );
		DONT_SPACE_TOKENS.add( "<>" );
		DONT_SPACE_TOKENS.add( "!#" );
		DONT_SPACE_TOKENS.add( "!~" );
		DONT_SPACE_TOKENS.add( "!<" );
		DONT_SPACE_TOKENS.add( "!>" );
		DONT_SPACE_TOKENS.add( "(" ); //for MySQL
		DONT_SPACE_TOKENS.add( ")" );
	}

	public QuerySelect(Dialect dialect) {
		this.dialect = dialect;
		joins = new QueryJoinFragment( dialect, false );
	}

	public JoinFragment getJoinFragment() {
		return joins;
	}

	public void addSelectFragmentString(String fragment) {
		if ( fragment.length() > 0 && fragment.charAt( 0 ) == ',' ) {
			fragment = fragment.substring( 1 );
		}
		fragment = fragment.trim();
		if ( fragment.length() > 0 ) {
			if ( select.length() > 0 ) {
				select.append( ", " );
			}
			select.append( fragment );
		}
	}

	public void addSelectColumn(String columnName, String alias) {
		addSelectFragmentString( columnName + ' ' + alias );
	}

	public void setDistinct(boolean distinct) {
		this.distinct = distinct;
	}

	public void setWhereTokens(Iterator tokens) {
		//if ( conjunctiveWhere.length()>0 ) conjunctiveWhere.append(" and ");
		appendTokens( where, tokens );
	}

	public void prependWhereConditions(String conditions) {
		if ( where.length() > 0 ) {
			where.insert( 0, conditions + " and " );
		}
		else {
			where.append( conditions );
		}
	}

	public void setGroupByTokens(Iterator tokens) {
		//if ( groupBy.length()>0 ) groupBy.append(" and ");
		appendTokens( groupBy, tokens );
	}

	public void setOrderByTokens(Iterator tokens) {
		//if ( orderBy.length()>0 ) orderBy.append(" and ");
		appendTokens( orderBy, tokens );
	}

	public void setHavingTokens(Iterator tokens) {
		//if ( having.length()>0 ) having.append(" and ");
		appendTokens( having, tokens );
	}

	public void addOrderBy(String orderByString) {
		if ( orderBy.length() > 0 ) {
			orderBy.append( ", " );
		}
		orderBy.append( orderByString );
	}

	public String toQueryString() {
		StringBuilder buf = new StringBuilder( 50 );
		if ( comment != null ) {
			buf.append( "/* " ).append( comment ).append( " */ " );
		}
		buf.append( "select " );
		if ( distinct ) {
			buf.append( "distinct " );
		}
		String from = joins.toFromFragmentString();
		if ( from.startsWith( "," ) ) {
			from = from.substring( 1 );
		}
		else if ( from.startsWith( " inner join" ) ) {
			from = from.substring( 11 );
		}

		buf.append( select.toString() )
				.append( " from" )
				.append( from );

		String outerJoinsAfterWhere = joins.toWhereFragmentString().trim();
		String whereConditions = where.toString().trim();
		boolean hasOuterJoinsAfterWhere = outerJoinsAfterWhere.length() > 0;
		boolean hasWhereConditions = whereConditions.length() > 0;
		if ( hasOuterJoinsAfterWhere || hasWhereConditions ) {
			buf.append( " where " );
			if ( hasOuterJoinsAfterWhere ) {
				buf.append( outerJoinsAfterWhere.substring( 4 ) );
			}
			if ( hasWhereConditions ) {
				if ( hasOuterJoinsAfterWhere ) {
					buf.append( " and (" );
				}
				buf.append( whereConditions );
				if ( hasOuterJoinsAfterWhere ) {
					buf.append( ")" );
				}
			}
		}

		if ( groupBy.length() > 0 ) {
			buf.append( " group by " ).append( groupBy.toString() );
		}
		if ( having.length() > 0 ) {
			buf.append( " having " ).append( having.toString() );
		}
		if ( orderBy.length() > 0 ) {
			buf.append( " order by " ).append( orderBy.toString() );
		}

		return dialect.transformSelectString( buf.toString() );
	}

	private static void appendTokens(StringBuilder buf, Iterator iter) {
		boolean lastSpaceable = true;
		boolean lastQuoted = false;
		while ( iter.hasNext() ) {
			String token = (String) iter.next();
			boolean spaceable = !DONT_SPACE_TOKENS.contains( token );
			boolean quoted = token.startsWith( "'" );
			if ( spaceable && lastSpaceable ) {
				if ( !quoted || !lastQuoted ) {
					buf.append( ' ' );
				}
			}
			lastSpaceable = spaceable;
			buf.append( token );
			lastQuoted = token.endsWith( "'" );
		}
	}

	public void setComment(String comment) {
		this.comment = comment;
	}

	public QuerySelect copy() {
		QuerySelect copy = new QuerySelect( dialect );
		copy.joins = this.joins.copy();
		copy.select.append( this.select.toString() );
		copy.where.append( this.where.toString() );
		copy.groupBy.append( this.groupBy.toString() );
		copy.orderBy.append( this.orderBy.toString() );
		copy.having.append( this.having.toString() );
		copy.comment = this.comment;
		copy.distinct = this.distinct;
		return copy;
	}

}
