/*******************************************************************************
 * Copyright (c) 2004, 2005 Actuate Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *  Actuate Corporation  - initial API and implementation
 *******************************************************************************/
package org.eclipse.birt.data.engine.impl;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.data.engine.api.APITestCase;
import org.eclipse.birt.data.engine.api.IGroupDefinition;
import org.eclipse.birt.data.engine.api.IJoinCondition;
import org.eclipse.birt.data.engine.api.IJointDataSetDesign;
import org.eclipse.birt.data.engine.api.IPreparedQuery;
import org.eclipse.birt.data.engine.api.IQueryResults;
import org.eclipse.birt.data.engine.api.querydefn.ColumnDefinition;
import org.eclipse.birt.data.engine.api.querydefn.ComputedColumn;
import org.eclipse.birt.data.engine.api.querydefn.FilterDefinition;
import org.eclipse.birt.data.engine.api.querydefn.GroupDefinition;
import org.eclipse.birt.data.engine.api.querydefn.JoinCondition;
import org.eclipse.birt.data.engine.api.querydefn.JointDataSetDesign;
import org.eclipse.birt.data.engine.api.querydefn.OdaDataSetDesign;
import org.eclipse.birt.data.engine.api.querydefn.QueryDefinition;
import org.eclipse.birt.data.engine.api.querydefn.ScriptExpression;
import org.eclipse.birt.data.engine.core.DataException;
import org.eclipse.birt.data.engine.executor.ResultClass;
import org.eclipse.birt.data.engine.executor.ResultFieldMetadata;
import org.eclipse.birt.data.engine.impl.jointdataset.JoinConditionMatcher;
import org.eclipse.birt.data.engine.impl.jointdataset.JointDataSetPopulatorFactory;
import org.eclipse.birt.data.engine.impl.jointdataset.JointResultMetadata;
import org.eclipse.birt.data.engine.odi.IDataSetPopulator;
import org.eclipse.birt.data.engine.odi.IResultClass;
import org.eclipse.birt.data.engine.odi.IResultIterator;
import org.eclipse.birt.data.engine.odi.IResultObject;

import testutil.ConfigText;

/**
 * 
 */
public class JointDataSetTest extends APITestCase
{
	private static int CARTESIAN_POPULATOR = 0;
	private static int BINARY_TREE_POPULATOR = 1;
	
	/*
	 * @see org.eclipse.birt.data.engine.api.APITestCase#getDataSourceInfo()
	 */
	protected DataSourceInfo getDataSourceInfo( )
	{
		return new DataSourceInfo( ConfigText.getString( "Impl.TestJointDataSet.TableName" ),
				ConfigText.getString( "Impl.TestJointDataSet.TableSQL" ),
				ConfigText.getString( "Impl.TestJointDataSet.TestDataFileName" ) );
	}

	// Basic test to get MD for all columns
	public void testInnerJoin_DEFAULT( ) throws Exception
	{
		String s = basicJoinTest( IJointDataSetDesign.INNER_JOIN, CARTESIAN_POPULATOR );
		this.testPrint( s );
		checkOutputFile( );
	}

	public void testLeftOuterJoin_DEFAULT( ) throws Exception
	{
		
		String s = basicJoinTest( IJointDataSetDesign.LEFT_OUTER_JOIN, CARTESIAN_POPULATOR );
		this.testPrint( s );
		checkOutputFile( );
	}
	
	public void testRightOuterJoin_DEFAULT( ) throws Exception
	{
		
		String s = basicJoinTest( IJointDataSetDesign.RIGHT_OUTER_JOIN, CARTESIAN_POPULATOR );
		this.testPrint( s );
		checkOutputFile( );
	}
	
	public void testFullOuterJoin_DEFAULT( ) throws Exception
	{
		
		String s = basicJoinTest( IJointDataSetDesign.FULL_OUTER_JOIN, CARTESIAN_POPULATOR );
		this.testPrint( s );
		checkOutputFile( );
	}
	
	// Basic test to get MD for all columns
	public void testInnerJoin_BINARY( ) throws Exception
	{
		
		String s = basicJoinTest( IJointDataSetDesign.INNER_JOIN, BINARY_TREE_POPULATOR );
		this.testPrint( s );
		checkOutputFile( );
	}

	public void testLeftOuterJoin_BINARY( ) throws Exception
	{
		
		String s = basicJoinTest( IJointDataSetDesign.LEFT_OUTER_JOIN, BINARY_TREE_POPULATOR );
		this.testPrint( s );
		checkOutputFile( );
	}
	
	public void testRightOuterJoin_BINARY( ) throws Exception
	{
		
		String s = basicJoinTest( IJointDataSetDesign.RIGHT_OUTER_JOIN, BINARY_TREE_POPULATOR );
		this.testPrint( s );
		checkOutputFile( );
	}
	
	public void testComplexInnerJoin( ) throws Exception
	{
		String s = complexJoinTest( IJointDataSetDesign.INNER_JOIN );
		this.testPrint( s );
		checkOutputFile();
	}

	public void testComplexLeftOuterJoin() throws Exception
	{
		String s = complexJoinTest( IJointDataSetDesign.LEFT_OUTER_JOIN);
		this.testPrint( s );
		checkOutputFile();
	}
	
	public void testComplexRightOuterJoin() throws Exception
	{
		String s = complexJoinTest( IJointDataSetDesign.RIGHT_OUTER_JOIN );
		this.testPrint( s );
		checkOutputFile();
	}
	
	public void testComplexFullOuterJoin() throws Exception
	{
		String s = complexJoinTest( IJointDataSetDesign.FULL_OUTER_JOIN );
		this.testPrint( s );
		checkOutputFile();
	}
	
	/**
	 * 
	 * @param joinType
	 * @return
	 * @throws Exception
	 */
	private String complexJoinTest( int joinType ) throws Exception
	{
		OdaDataSetDesign dset1 = newDataSet( "dset1", "Select ID, CITY, STORE FROM "
				+ this.getTestTableName( ) + " where ID > 4 and ID <> 7 and ID <> 9 order by ID asc");

		OdaDataSetDesign dset2 = newDataSet( "dset2", "Select ID, SKU, CATEGORY, PRICE FROM "
				+ this.getTestTableName( ) + " where ID < 20 and ID <> 10 and ID <> 13 order by ID asc");
		
		/*dset1.addComputedColumn( new ComputedColumn( "CP1", "row.ID + 10") );
		dset2.addComputedColumn( new ComputedColumn( "CP2", "row.ID + 20") );*/
		List a = new ArrayList();
		a.add( new JoinCondition(new ScriptExpression("dataSetRow.ID"),new ScriptExpression("dataSetRow.ID"),IJoinCondition.OP_EQ) );
	
		JointDataSetDesign dset3 = new JointDataSetDesign( "dset3", dset1.getName( ), dset2.getName( ), joinType, a );
		dset3.addComputedColumn( new ComputedColumn( "group1.sum", "Total.sum(row[\"dset2::PRICE\"],null,0)") );
		dset3.addComputedColumn( new ComputedColumn( "group2.sum", "Total.sum(row[\"dset2::PRICE\"],null,0)") );
		dset3.addComputedColumn( new ComputedColumn( "cp3", "row[\"dset2::PRICE\"]+100") );
		dset3.addFilter(new FilterDefinition( new ScriptExpression( "row[\"dset1::ID\"]!=15")));
		dset3.addFilter(new FilterDefinition( new ScriptExpression( "row[\"dset1::ID\"]!=16")));
		dataEngine.defineDataSet( dset3 );
	
		List b = new ArrayList();
		b.add( new JoinCondition(new ScriptExpression("dataSetRow[\"dset1::ID\"]"),new ScriptExpression("dataSetRow[\"ID\"]"),IJoinCondition.OP_EQ) );
	
		JointDataSetDesign dset4 = new JointDataSetDesign( "dset4", dset3.getName( ), dset1.getName( ), joinType, b );
		dset4.addComputedColumn( new ComputedColumn( "CC", "\"I am grand Dset\"") );
		dataEngine.defineDataSet( dset4 );
		
		QueryDefinition query = this.newReportQuery( dset4 );
		
		GroupDefinition group1 = new GroupDefinition();
		group1.setInterval( IGroupDefinition.NUMERIC_INTERVAL );
		group1.setIntervalRange( 10 );
		group1.setIntervalStart( new Integer(5) );
		group1.setKeyExpression( "row.G1" );
		String G1 = "G1";
		ScriptExpression be1 = new ScriptExpression( "dataSetRow[\"dset1::ID\"]" );	
		GroupDefinition group2 = new GroupDefinition();
		group2.setKeyExpression( "row.G2" );
		String G2 = "G2";
		ScriptExpression be2 = new ScriptExpression( "dataSetRow[\"dset1::CITY\"]" );	
	
		query.addGroup( group1 );
		query.addGroup( group2 );
		query.addResultSetExpression(G1, be1);
		query.addResultSetExpression(G2, be2);
		IPreparedQuery preparedQuery = this.dataEngine.prepare( query ); 
		IQueryResults qr = preparedQuery.execute( null );
		IResultIterator ri = ((ResultIterator)qr.getResultIterator( )).getOdiResult( );
				
		IResultObject ro;
		String s = "";
		for( int i = 0; i < ri.getResultClass( ).getFieldCount( ); i++ )
		{
			s += ri.getResultClass( ).getFieldName( i+1 ) + "\t\t\t";
		}
		s += "\n";
		long start = System.currentTimeMillis( );
		int count = 0;
		do 
		{
			ro = ri.getCurrentResult( );
			count++;
			if( count == 49)
				System.out.print( "ar" );
			for( int i = 0; i < ri.getResultClass( ).getFieldCount( ); i++)
			{
				s += ro.getFieldValue( i+1 ) + "\t\t\t";
			}
			s += "\n";
		}while ( ri.next( ));
		System.out.println(count + ":" + (System.currentTimeMillis( ) - start));
		return s;
	}

	public void testSelfInnerJoin( ) throws Exception
	{
		String s = selfJoinTest( IJointDataSetDesign.INNER_JOIN );
		this.testPrint( s );
		checkOutputFile();
	}

	public void testSelfLeftOuterJoin() throws Exception
	{
		String s = selfJoinTest( IJointDataSetDesign.LEFT_OUTER_JOIN);
		this.testPrint( s );
		checkOutputFile();
	}
	
	public void testSelfRightOuterJoin() throws Exception
	{
		String s = selfJoinTest( IJointDataSetDesign.RIGHT_OUTER_JOIN );
		this.testPrint( s );
		checkOutputFile();
	}

	
	private String selfJoinTest(  int joinType ) throws Exception
	{
		OdaDataSetDesign dset = newDataSet( "dset", "Select ID, CITY, STORE FROM "
				+ this.getTestTableName( ) +" order by ID asc");
		
		/*dset1.addComputedColumn( new ComputedColumn( "CP1", "row.ID + 10") );
		dset2.addComputedColumn( new ComputedColumn( "CP2", "row.ID + 20") );*/
		List a = new ArrayList();
		a.add( new JoinCondition(new ScriptExpression("dataSetRow.ID"),new ScriptExpression("dataSetRow.ID"),IJoinCondition.OP_EQ) );
	
		JointDataSetDesign dset2 = new JointDataSetDesign( "dset2", dset.getName( ), dset.getName( ), joinType, a );
		dataEngine.defineDataSet( dset2 );
		
		QueryDefinition query = this.newReportQuery( dset2 );
		
		IPreparedQuery preparedQuery = this.dataEngine.prepare( query ); 
		IQueryResults qr = preparedQuery.execute( null );
		IResultIterator ri = ((ResultIterator)qr.getResultIterator( )).getOdiResult( );
				
		IResultObject ro;
		String s = "";
		for( int i = 0; i < ri.getResultClass( ).getFieldCount( ); i++ )
		{
			s += ri.getResultClass( ).getFieldName( i+1 ) + "\t\t\t";
		}
		s += "\n";
		long start = System.currentTimeMillis( );
		int count = 0;
		do 
		{
			ro = ri.getCurrentResult( );
			count++;
			if( count == 49)
				System.out.print( "ar" );
			for( int i = 0; i < ri.getResultClass( ).getFieldCount( ); i++)
			{
				s += ro.getFieldValue( i+1 ) + "\t\t\t";
			}
			s += "\n";
		}while ( ri.next( ));
		System.out.println(count + ":" + (System.currentTimeMillis( ) - start));
		return s;
	}
	/**
	 * @return
	 * @throws Exception
	 * @throws BirtException
	 * @throws DataException
	 */
	private String basicJoinTest( int joinType, int populateType ) throws Exception, BirtException, DataException
	{
		OdaDataSetDesign dset1 = newDataSet( "dset1", "Select ID, CITY, STORE FROM "
				+ this.getTestTableName( ) + " where ID > 4 and ID <> 7 and ID <> 9 order by ID asc");

		OdaDataSetDesign dset2 = newDataSet( "dset2", "Select ID, SKU, CATEGORY, PRICE FROM "
				+ this.getTestTableName( ) + " where ID < 20 and ID <> 10 and ID <> 13 order by ID asc");
	
		
		QueryDefinition query1 = this.newReportQuery( dset1 );
		QueryResults qr1 = (QueryResults)this.dataEngine.prepare( query1 ).execute( null );
		
		QueryDefinition query2 = this.newReportQuery( dset2 );
		QueryResults qr2 = (QueryResults)this.dataEngine.prepare( query2 ).execute( null );
	
	
		IResultIterator it1 = ((ResultIterator)qr1.getResultIterator( )).getOdiResult( );
		IResultIterator it2 = ((ResultIterator)qr2.getResultIterator( )).getOdiResult( );
		
		int[] isFromLeft = new int[]{1,1,1,2,2,2,2};
		int[] index = new int[]{1,2,3,1,2,3,4};
		List projectedColumns = new ArrayList();
		projectedColumns.add( new ResultFieldMetadata(1, "dset1::"+it1.getResultClass( ).getFieldName( 1 ), it1.getResultClass( ).getFieldName( 1 ), it1.getResultClass( ).getFieldValueClass( 1 ), it1.getResultClass( ).getFieldNativeTypeName( 1 ), false)); 
		projectedColumns.add( new ResultFieldMetadata(3, "dset1::"+it1.getResultClass( ).getFieldName( 2 ), it1.getResultClass( ).getFieldName( 2 ), it1.getResultClass( ).getFieldValueClass( 2 ), it1.getResultClass( ).getFieldNativeTypeName( 2 ), false));
		projectedColumns.add( new ResultFieldMetadata(4, "dset1::"+it1.getResultClass( ).getFieldName( 3 ), it1.getResultClass( ).getFieldName( 3 ), it1.getResultClass( ).getFieldValueClass( 3 ), it1.getResultClass( ).getFieldNativeTypeName( 3 ), false));
		
		projectedColumns.add( new ResultFieldMetadata(2, "dset2::"+it2.getResultClass( ).getFieldName( 1 ), it2.getResultClass( ).getFieldName( 1 ), it2.getResultClass( ).getFieldValueClass( 1 ), it2.getResultClass( ).getFieldNativeTypeName( 1 ), false));		 
		projectedColumns.add( new ResultFieldMetadata(5, "dset2::"+it2.getResultClass( ).getFieldName( 2 ), it2.getResultClass( ).getFieldName( 2 ), it2.getResultClass( ).getFieldValueClass( 2 ), it2.getResultClass( ).getFieldNativeTypeName( 2 ), false));
		projectedColumns.add( new ResultFieldMetadata(6, "dset2::"+it2.getResultClass( ).getFieldName( 3 ), it2.getResultClass( ).getFieldName( 3 ), it2.getResultClass( ).getFieldValueClass( 3 ), it2.getResultClass( ).getFieldNativeTypeName( 3 ), false));
		projectedColumns.add( new ResultFieldMetadata(7, "dset2::"+it2.getResultClass( ).getFieldName( 4 ), it2.getResultClass( ).getFieldName( 4 ), it2.getResultClass( ).getFieldValueClass( 4 ), it2.getResultClass( ).getFieldNativeTypeName( 4 ), false));
		
		ResultClass resultClass = new ResultClass( projectedColumns );
		JointResultMetadata meta = new JointResultMetadata( resultClass, isFromLeft, index );
		//JoinConditionMatcher matcher = new JoinConditionMatcher( qr1.getResultIterator( ).getScope( ), qr2.getResultIterator( ).getScope( ), new JoinConditionExpression(new ScriptExpression("row.ID"),new ScriptExpression("row.ID"),0));
		List a = new ArrayList();
		a.add( new JoinCondition(new ScriptExpression("dataSetRow.ID"),new ScriptExpression("dataSetRow.ID"),IJoinCondition.OP_EQ) );
		JoinConditionMatcher matcher = new JoinConditionMatcher( ((ResultIterator)qr1.getResultIterator( )).getOdiResult( ),((ResultIterator)qr2.getResultIterator( )).getOdiResult( ),qr1.getQueryScope( ), qr2.getQueryScope( ), a);
		IDataSetPopulator populator = null;
		if( populateType == BINARY_TREE_POPULATOR) 
			populator = JointDataSetPopulatorFactory.getBinaryTreeDataSetPopulator(it1, it2, meta, matcher, joinType, ((DataEngineImpl)this.dataEngine).getSession( ),0);
		else 
			populator = JointDataSetPopulatorFactory.getCartesianJointDataSetPopulator(it1, it2, meta, matcher, joinType,  ((DataEngineImpl)this.dataEngine).getSession( ),0);
		
		IResultObject ro;
		String s = "";
		for( int i = 0; i < resultClass.getFieldCount( ); i++ )
		{
			s += resultClass.getFieldName( i+1 ) + "\t\t\t";
		}
		s += "\n";
		long start = System.currentTimeMillis( );
		int count = 0;
		while ( (ro = populator.next( ))!= null)
		{
			count++;
			if( count == 49)
				System.out.print( "ar" );
			for( int i = 0; i < resultClass.getFieldCount( ); i++)
			{
				s += ro.getFieldValue( i+1 ) + "\t\t\t";
			}
			s += "\n";
		}
		System.out.println(count + ":" + (System.currentTimeMillis( ) - start));
		return s;
	}
	
	public void testAlias( ) throws Exception
	{
		String fieldName = "dset2::CITY";
		OdaDataSetDesign dset = newDataSet( "dset",
				"Select ID, CITY, STORE FROM "
						+ this.getTestTableName( ) + " order by ID asc" );

		List a = new ArrayList( );
		a.add( new JoinCondition( new ScriptExpression( "dataSetRow.ID" ),
				new ScriptExpression( "dataSetRow.ID" ),
				IJoinCondition.OP_EQ ) );

		JointDataSetDesign dset2 = new JointDataSetDesign( "dset2",
				dset.getName( ),
				dset.getName( ),
				IJointDataSetDesign.INNER_JOIN,
				a );
		ColumnDefinition columnDefinition = new ColumnDefinition( fieldName );
		columnDefinition.setAlias( "Alias" );
		dset2.addResultSetHint( columnDefinition );
		dataEngine.defineDataSet( dset2 );

		QueryDefinition query = this.newReportQuery( dset2 );
		IPreparedQuery preparedQuery = this.dataEngine.prepare( query );
		IQueryResults qr = preparedQuery.execute( null );
		IResultClass resultClass = ( (ResultIterator) qr.getResultIterator( ) ).getOdiResult( )
				.getResultClass( );

		assertEquals( resultClass.getFieldAlias( resultClass.getFieldIndex( fieldName ) ),
				columnDefinition.getAlias( ) );
	}

}
