/*******************************************************************************
 * 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.executor.transform.group;

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

import org.eclipse.birt.core.data.ExpressionUtil;
import org.eclipse.birt.data.engine.api.querydefn.SortDefinition;
import org.eclipse.birt.data.engine.core.DataException;
import org.eclipse.birt.data.engine.executor.cache.ResultSetCache;
import org.eclipse.birt.data.engine.executor.transform.IExpressionProcessor;
import org.eclipse.birt.data.engine.executor.transform.OrderingInfo;
import org.eclipse.birt.data.engine.executor.transform.ResultSetPopulator;
import org.eclipse.birt.data.engine.odi.IQuery.GroupSpec;
import org.mozilla.javascript.Context;

/**
 * The class which is used to process groups with intervals.
 */
public class GroupWithIntervalsProcessor
{

	private ResultSetPopulator populator;
	private GroupProcessorManager groupProcessor;
	private GroupInstanceSorter giSorter;

	GroupWithIntervalsProcessor( GroupProcessorManager gp )
	{
		this.populator = gp.getResultSetPopulator( );
		this.groupProcessor = gp;
		this.giSorter = new GroupInstanceSorter( this.groupProcessor );
	}

	/**
	 * Process the groups with intervals. The groups are splited to different
	 * levels according to the whether intervals are used. Each group of leveled
	 * groups only contains no more than group with interval.
	 * 
	 * @param groups
	 * @param leveledGroup
	 * @throws DataException
	 */
	public void processGroupWithIntervals( ResultSetCache rsCache,
			GroupSpec[] groups ) throws DataException
	{
		this.populator.setCache( rsCache );

		List leveledGroup = populateLeveledGroups( groups );
		if ( leveledGroup.size( ) > 0 )
		{
			Context cx = Context.enter( );
			try
			{
				this.groupProcessor.getGroupCalculationUtil( )
						.getQuery( )
						.setGrouping( null );

				for ( int i = 0; i < leveledGroup.size( ) - 1; i++ )
				{
					GroupSpec immediatelyNextGroupSpec = ( (GroupSpec[]) leveledGroup.get( i + 1 ) )[0];
					SortDefinition sd = new SortDefinition( );
					if ( immediatelyNextGroupSpec.getKeyColumn( ) != null )
						sd.setExpression( ExpressionUtil.createJSDataSetRowExpression( immediatelyNextGroupSpec.getKeyColumn( ) ) );
					else
						sd.setExpression( "dataSetRow[\""
								+ immediatelyNextGroupSpec.getKeyIndex( )
								+ "\"]" );
			/*		sd.setSortDirection( immediatelyNextGroupSpec.getSortDirection( ) == -1
							? 0 : immediatelyNextGroupSpec.getSortDirection( ) );*/
					List sorts = new ArrayList( );
					sorts.add( sd );
					immediatelyNextGroupSpec.setSorts( sorts );

					List tempGroups = new ArrayList( );
					for ( int k = 0; k < leveledGroup.size( ); k++ )
					{
						GroupSpec[] tempGroupsArray = (GroupSpec[]) leveledGroup.get( k );
						for ( int j = 0; j < tempGroupsArray.length; j++ )
						{
							tempGroups.add( tempGroupsArray[j] );
						}
					}
					this.groupProcessor.getGroupCalculationUtil( )
							.getQuery( )
							.setGrouping( tempGroups );
					this.populator.getCache( ).next( );
					List groupLevels = new ArrayList( );
					List expressionList = new ArrayList( );
					this.giSorter.populateGroupSortExpressions( expressionList,
							groupLevels );
					this.groupProcessor.calculateExpressionList( expressionList,
							groupLevels,
							IExpressionProcessor.SORT_ON_GROUP_EXPR );
					// this.groupCalculationUtil.doGrouping( );

					// The groupArray gotten here has not be sorted yet.
					List[] groupArray = this.groupProcessor.getGroupCalculationUtil( )
							.getGroupInformationUtil( )
							.getGroupBoundaryInfos( );
					this.giSorter.populateGroupBoundaryInfosSortings( cx,
							groupArray );
					this.groupProcessor.getGroupCalculationUtil( )
							.sortGroupBoundaryInfos( groupArray );
					OrderingInfo odInfo = this.groupProcessor.getGroupCalculationUtil( )
							.getGroupInformationUtil( )
							.getOrderingInfo( groupArray );
					this.populator.reSetCache( odInfo );
					immediatelyNextGroupSpec.setSorts( new ArrayList( ) );
					this.groupProcessor.getGroupCalculationUtil( )
							.getGroupInformationUtil( )
							.doGrouping( );
				}
				this.groupProcessor.getGroupCalculationUtil( )
						.getQuery( )
						.setGrouping( getListFromArray( groups ) );
			}
			finally
			{
				Context.exit( );
			}
		}
	}

	/**
	 * Populate the leveled groups. Each leveled group, except the last one,
	 * must end with a group with intervals.
	 * 
	 * @param groups
	 * @return
	 */
	private List populateLeveledGroups( GroupSpec[] groups )
	{
		List leveledGroup = new ArrayList( );
		if ( groups != null )
		{
			int last = -1;
			for ( int i = 0; i < groups.length - 1; i++ )
			{
				if ( groups[i].getInterval( ) != 0 )
				{
					GroupSpec[] gs = getOneLevelOfGroupSpecs( groups, last, i );
					last = i;
					leveledGroup.add( gs );
				}
			}
			if ( last > -1 )
			{
				leveledGroup.add( getOneLevelOfGroupSpecs( groups,
						last,
						groups.length - 1 ) );
			}
		}
		return leveledGroup;
	}

	/**
	 * 
	 * @param o
	 * @return
	 */
	private List getListFromArray( Object[] o )
	{
		List l = new ArrayList( );
		if ( o == null )
			return l;

		for ( int i = 0; i < o.length; i++ )
		{
			l.add( o[i] );
		}
		return l;
	}

	/**
	 * @param groups
	 * @param last
	 * @param i
	 * @return
	 */
	private GroupSpec[] getOneLevelOfGroupSpecs( GroupSpec[] groups, int last,
			int i )
	{
		GroupSpec[] gs = new GroupSpec[i - last];
		for ( int j = last + 1; j <= i; j++ )
		{
			GroupSpec gps = new GroupSpec( groups[j].getKeyIndex( ),
					groups[j].getKeyColumn( ) );
			gps.setDataType( groups[j].getDataType( ) );
			gps.setInterval( groups[j].getInterval( ) );
			gps.setIntervalRange( groups[j].getIntervalRange( ) );
			gps.setIntervalStart( groups[j].getIntervalStart( ) );
			gps.setIsComplexExpression( groups[j].isCompleteExpression( ) );
			gps.setName( groups[j].getName( ) );
			gps.setSortDirection( groups[j].getSortDirection( ) );

			gs[j - last - 1] = gps;

		}
		return gs;
	}

}
