/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat Middleware LLC.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 *
 */
package org.hibernate.hql.ast.util;

import java.util.Map;

import org.hibernate.hql.antlr.HqlSqlTokenTypes;
import org.hibernate.hql.ast.tree.FromElement;
import org.hibernate.hql.ast.tree.QueryNode;
import org.hibernate.hql.ast.tree.RestrictableStatement;
import org.hibernate.hql.ast.tree.SqlFragment;
import org.hibernate.hql.ast.tree.Node;
import org.hibernate.hql.ast.HqlSqlWalker;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.sql.JoinFragment;
import org.hibernate.util.StringHelper;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.type.Type;
import org.hibernate.param.CollectionFilterKeyParameterSpecification;

import antlr.ASTFactory;
import antlr.collections.AST;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Creates synthetic and nodes based on the where fragment part of a JoinSequence.
 *
 * @author josh
 */
public class SyntheticAndFactory implements HqlSqlTokenTypes {
	private static final Logger log = LoggerFactory.getLogger( SyntheticAndFactory.class );

	private HqlSqlWalker hqlSqlWalker;
	private AST thetaJoins;
	private AST filters;

	public SyntheticAndFactory(HqlSqlWalker hqlSqlWalker) {
		this.hqlSqlWalker = hqlSqlWalker;
	}

	private Node create(int tokenType, String text) {
		return ( Node ) ASTUtil.create( hqlSqlWalker.getASTFactory(), tokenType, text );
	}

	public void addWhereFragment(
			JoinFragment joinFragment,
			String whereFragment,
			QueryNode query,
			FromElement fromElement,
			HqlSqlWalker hqlSqlWalker) {
		if ( whereFragment == null ) {
			return;
		}

		if ( !fromElement.useWhereFragment() && !joinFragment.hasThetaJoins() ) {
			return;
		}

		whereFragment = whereFragment.trim();
		if ( StringHelper.isEmpty( whereFragment ) ) {
			return;
		}

		// Forcefully remove leading ands from where fragments; the grammar will
		// handle adding them
		if ( whereFragment.startsWith( "and" ) ) {
			whereFragment = whereFragment.substring( 4 );
		}

		log.debug( "Using unprocessed WHERE-fragment [{}]", whereFragment );

		SqlFragment fragment = ( SqlFragment ) create( SQL_TOKEN, whereFragment );
		fragment.setJoinFragment( joinFragment );
		fragment.setFromElement( fromElement );

		if ( fromElement.getIndexCollectionSelectorParamSpec() != null ) {
			fragment.addEmbeddedParameter( fromElement.getIndexCollectionSelectorParamSpec() );
			fromElement.setIndexCollectionSelectorParamSpec( null );
		}

		if ( hqlSqlWalker.isFilter() ) {
			if ( whereFragment.indexOf( '?' ) >= 0 ) {
				Type collectionFilterKeyType = hqlSqlWalker.getSessionFactoryHelper()
						.requireQueryableCollection( hqlSqlWalker.getCollectionFilterRole() )
						.getKeyType();
				CollectionFilterKeyParameterSpecification paramSpec = new CollectionFilterKeyParameterSpecification(
						hqlSqlWalker.getCollectionFilterRole(),
						collectionFilterKeyType,
						0
				);
				fragment.addEmbeddedParameter( paramSpec );
			}
		}

		JoinProcessor.processDynamicFilterParameters(
				whereFragment,
				fragment,
				hqlSqlWalker
		);

		log.debug( "Using processed WHERE-fragment [{}]", fragment.getText() );

		// Filter conditions need to be inserted before the HQL where condition and the
		// theta join node.  This is because org.hibernate.loader.Loader binds the filter parameters first,
		// then it binds all the HQL query parameters, see org.hibernate.loader.Loader.processFilterParameters().
		if ( fragment.getFromElement().isFilter() || fragment.hasFilterCondition() ) {
			if ( filters == null ) {
				// Find or create the WHERE clause
				AST where = query.getWhereClause();
				// Create a new FILTERS node as a parent of all filters
				filters = create( FILTERS, "{filter conditions}" );
				// Put the FILTERS node before the HQL condition and theta joins
				ASTUtil.insertChild( where, filters );
			}
			
			// add the current fragment to the FILTERS node
			filters.addChild( fragment );
		}
		else {
			if ( thetaJoins == null ) {
				// Find or create the WHERE clause
				AST where = query.getWhereClause();
				// Create a new THETA_JOINS node as a parent of all filters
				thetaJoins = create( THETA_JOINS, "{theta joins}" );
				// Put the THETA_JOINS node before the HQL condition, after the filters.
				if (filters==null) {
					ASTUtil.insertChild( where, thetaJoins );
				}
				else {
					ASTUtil.insertSibling( thetaJoins, filters );
				}
			}
			
			// add the current fragment to the THETA_JOINS node
			thetaJoins.addChild(fragment);
		}

	}

	public void addDiscriminatorWhereFragment(
			RestrictableStatement statement,
			Queryable persister,
			Map enabledFilters,
			String alias) {
		String whereFragment = persister.filterFragment( alias, enabledFilters ).trim();
		if ( "".equals( whereFragment ) ) {
			return;
		}
		if ( whereFragment.startsWith( "and" ) ) {
			whereFragment = whereFragment.substring( 4 );
		}

		// Need to parse off the column qualifiers; this is assuming (which is true as of now)
		// that this is only used from update and delete HQL statement parsing
		whereFragment = StringHelper.replace( whereFragment, persister.generateFilterConditionAlias( alias ) + ".", "" );

		// Note: this simply constructs a "raw" SQL_TOKEN representing the
		// where fragment and injects this into the tree.  This "works";
		// however it is probably not the best long-term solution.
		//
		// At some point we probably want to apply an additional grammar to
		// properly tokenize this where fragment into constituent parts
		// focused on the operators embedded within the fragment.
		SqlFragment discrimNode = ( SqlFragment ) create( SQL_TOKEN, whereFragment );

		JoinProcessor.processDynamicFilterParameters(
				whereFragment,
				discrimNode,
				hqlSqlWalker
		);

		if ( statement.getWhereClause().getNumberOfChildren() == 0 ) {
			statement.getWhereClause().setFirstChild( discrimNode );
		}
		else {
			AST and = create( AND, "{and}" );
			AST currentFirstChild = statement.getWhereClause().getFirstChild();
			and.setFirstChild( discrimNode );
			and.addChild( currentFirstChild );
			statement.getWhereClause().setFirstChild( and );
		}
	}
}
