/***************************************************************************
    Copyright          : (C) 2002 by Neoworks Limited. All rights reserved
    URL                : http://www.neoworks.com
 ***************************************************************************/
/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

/*
 * Expression.java
 *
 * Created on 17 April 2002, 14:12
 */

package com.neoworks.jukex.query;

import java.util.Set;
import java.util.Iterator;

import java.sql.*;
import org.apache.log4j.Category;

import com.neoworks.connectionpool.PoolManager;
import com.neoworks.jukex.sqlimpl.JukeXTrackStore;
import com.neoworks.jukex.Attribute;
import com.neoworks.jukex.TrackStore;
import com.neoworks.jukex.TrackStoreFactory;

/**
 *
 * @author  nick
 */
public class JukeXExpression
{
	
	/**
	 * Private constructor
	 */
	private JukeXExpression()
	{
	}

	/**
	 * Null operation class
	 */
	public static class NullOp implements Expression
	{
		/**
		 * Public constructor
		 */
		public NullOp()
		{
		}

		/**
		 * Get the generated SQL for this expression
		 *
		 * @param buffer The StringBuffer to populate with the SQL.
		 */
		public void getSQL(StringBuffer buffer)
		{
			return;
		}
	}
	
	/**
	 * Infix expression, such as <i>x AND y</i>
	 */
	public static class Infix implements Expression
	{
		Expression left = null;
		Expression right = null;
		String operator = null;

		/**
		 * Public constructor
		 *
		 * @param left lvalue expression
		 * @param operator The operator
		 * @param right rvalue expression
		 */
		public Infix( Expression left , String operator, Expression right )
		{
			this.left = left;
			this.operator = operator;
			this.right = right;
		}

		/**
		 * Get the generated SQL for this expression
		 *
		 * @param buffer The StringBuffer to populate with the SQL.
		 */
		public void getSQL(StringBuffer buffer)
		{
			buffer.append( '(' );
			left.getSQL( buffer );
			buffer.append( ' ' ).append( operator ).append( ' ' );
			right.getSQL( buffer );
			buffer.append( ')' );
		}
	}
	
	/**
	 * Prefix expression, such as <i>NOT x</i>
	 */
	public static abstract class Prefix implements Expression
	{
		Expression expression = null;
		
		public Prefix( Expression expression )
		{
			this.expression = expression;
		}
		
		/**
		 * Get the generated SQL for this expression
		 *
		 * @param buffer The StringBuffer to populate with the SQL.
		 */
		public abstract void getSQL(StringBuffer buffer);
	}

	/**
	 * NOT expression, negates the expression that it prefixes
	 */
	public static class Not extends Prefix
	{
		/**
		 * Public constructor
		 *
		 * @param expression The Expression to negate
		 */
		public Not( Expression expression )
		{
			super( expression );
		}
		
		/**
		 * Get the generated SQL for this expression
		 *
		 * @param buffer The StringBuffer to populate with the SQL.
		 */
		public void getSQL(StringBuffer buffer)
		{
			buffer.append( "(NOT " );
			expression.getSQL( buffer );
			buffer.append( ')' );
		}
	}

	/**
	 * Relational operator expression. E.g. x &gt; y
	 */
	public static class Relop implements Expression
	{
		private static final Category log = Category.getInstance(Relop.class.getName());
		private static final boolean logDebugEnabled = log.isDebugEnabled();
		private static final boolean logInfoEnabled = log.isInfoEnabled();
		
		private static String SQL_FALSE = "( 1 = 0 )";		// An SQL expression which evalutates to FALSE in order to allow clauses to be removed (see class Relop)
		
		Variable variable = null;
		Literal literal = null;
		String operator = null;

		/**
		 * Public constructor
		 *
		 * @param variable lvalue
		 * @param operator The relational operator
		 * @param literal rvalue
		 */
		public Relop( Variable variable , String operator , Literal literal )
		{
			this.variable = variable;
			this.operator = operator;
			this.literal = literal;
		}
		
		/**
		 * Get the generated SQL for this expression
		 *
		 * @param buffer The StringBuffer to populate with the SQL.
		 */
		public void getSQL(StringBuffer buffer)
		{	
			// First find out which type of Attribute we're searching for, to
			// see if it's necessary to prequery
			Attribute attr = TrackStoreFactory.getTrackStore().getAttribute( variable.val );
						
			if ( attr.getType() == Attribute.TYPE_STRING )
			{
				Connection conn = null;
				StringBuffer possValues = new StringBuffer();
				try
				{
					conn = PoolManager.getInstance().getConnection( JukeXTrackStore.DB_NAME );
					StringBuffer sql= new StringBuffer().append( "SELECT AttributeEnum.id, Attribute.name, Attribute.type, AttributeEnum.value FROM Attribute, AttributeEnum WHERE Attribute.id = AttributeEnum.attributeid AND " );
					//TODO: Numeric value change op
					if ( literal.val instanceof String )
					{
						sql.append( "( Attribute.name=" ).append( JukeXExpression.escapeString( variable.val ) ).append( " AND AttributeEnum.value ").append(operator).append( JukeXExpression.escapeString( (String) literal.val ) ).append( " )" );
					}
					else
					{
						sql.append( "( Attribute.name=" ).append( JukeXExpression.escapeString( variable.val ) ).append( " AND AttributeEnum.value" ).append(operator).append( JukeXExpression.escapeString( literal.val.toString() ) ).append( " )" );
					}
					ResultSet rs = conn.createStatement().executeQuery( sql.toString() );

					if ( rs.next() )
					{
						possValues.append( rs.getLong(1) );
						while ( rs.next() )
						{
							possValues.append(',').append( rs.getLong(1) );
						}
					}
					else
					{
						// This query has failed, exclude the clause an exit
						buffer.append( this.SQL_FALSE );
						return;
					}
				}
				catch ( Exception e )
				{
					log.error("Explosion whilst getting possible values for literal '"+literal.val+"'",e);
				}
				finally
				{
					try { conn.close(); } catch ( SQLException ignore ) { }
				}


				buffer.append( "( bind_" ).append( variable.val ).append( ".attributeenumid " );
				buffer.append("IN (").append( possValues ).append(") ");
				buffer.append( ')' );
			}
			else if ( attr.getType() == Attribute.TYPE_INT )
			{
				int intval = ((Integer)literal.val).intValue();
				
				buffer.append( "( bind_" ).append( variable.val ).append( ".numericvalue" );
				buffer.append( operator ).append( intval ).append( ')' );
			}
			else
			{
				log.error( "Attribute "+attr.getName()+" is of an unknown type" );
			}
		}
	}
	
	/**
	 * This is the left hand side of a Relop (lvalue). E.g. in the 
	 * expression <i>Year &gt; 1974</i> the variable is 'Year'.
	 */
	public static class Variable implements Expression
	{
		String val = null;

		/**
		 * Public constructor
		 *
		 * @param val The value
		 */
		public Variable( String val )
		{
			this.val = val;
		}
		
		/**
		 * Get the generated SQL for this expression
		 *
		 * @param buffer The StringBuffer to populate with the SQL.
		 */
		public void getSQL(StringBuffer buffer)
		{
			// Not called == Not implemented ;)
		}
	}

	/**
	 * String literal.
	 */
	public static class Literal implements Expression
	{
		Object val = null;

		/**
		 * Public constructor
		 *
		 * @param val The value of this literal
		 */
		public Literal( Object val )
		{
			this.val = val;
		}
		
		/**
		 * Get the generated SQL for this expression
		 *
		 * @param buffer The StringBuffer to populate with the SQL.
		 */
		public void getSQL(StringBuffer buffer)
		{
			boolean isString = ( val instanceof String );
			if (isString)
			{
				buffer.append( '"' ).append( escapeString( (String) val ) ).append( '"' );
			}
			else
			{
				buffer.append( val );
			}	
		}		
	}

	/**
	 * Quote a string literal
	 */
	public static String escapeString( String s )
	{
		// TODO: Escape the string ;0
		return '\''+s+'\'';
	}
}
