/*******************************************************************************
 * Copyright (c) 2004 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.dscache;

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

import org.eclipse.birt.core.data.DataType;
import org.eclipse.birt.data.engine.api.querydefn.ComputedColumn;
import org.eclipse.birt.data.engine.core.DataException;
import org.eclipse.birt.data.engine.executor.DataSetCacheManager;
import org.eclipse.birt.data.engine.executor.IDataSetCacheObject;
import org.eclipse.birt.data.engine.executor.ResultClass;
import org.eclipse.birt.data.engine.executor.ResultFieldMetadata;
import org.eclipse.birt.data.engine.executor.ResultObject;
import org.eclipse.birt.data.engine.impl.DataEngineSession;
import org.eclipse.birt.data.engine.odaconsumer.ResultSet;
import org.eclipse.birt.data.engine.odi.ICustomDataSet;
import org.eclipse.birt.data.engine.odi.IDataSetPopulator;
import org.eclipse.birt.data.engine.odi.IResultClass;
import org.eclipse.birt.data.engine.odi.IResultObject;

/**
 * Any data set result is supported to cache data into a temp file. In the first
 * time, the data can be retrieved from ODA driver. and then be stored into
 * cache file. In the second time or later, the data can be directly retrieved
 * from cache file rather than ODA driver to save time.
 */
public class DataSetResultCache
{
	//----------save time--------------
	
	// oda result set and metadata
	private IResultClass rsMeta;
	
	// oda data set or custom data set
	private ResultSet odaDataSet;
	private ICustomDataSet customDataSet;
	private IDataSetPopulator populator;
	
	// cache requirement
	private int cacheCount;
		
	// save util instance
	private ISaveUtil saveUtil;
	private boolean isSaved;
	
	//----------load time--------------
	private ILoadUtil loadUtil;
	
	// share info
	private boolean isLoad;
	
	// for computed column
	private List addedTempComputedColumn;
	private int realColumnCount;
	private DataEngineSession session;
	
	/**
	 * @param odaDataSet
	 * @param resultSet
	 */
	public DataSetResultCache( ResultSet odaDataSet, IResultClass rsMeta, DataEngineSession session )
	{
		assert odaDataSet != null;
		assert rsMeta != null;

		this.odaDataSet = odaDataSet;
		
		this.init( rsMeta, session );
	}
	
	/**
	 * @param customDataSet
	 * @param rsMeta
	 */
	public DataSetResultCache( IDataSetPopulator populator, IResultClass rsMeta, DataEngineSession session )
	{
		assert populator!=null;
		assert rsMeta != null;
		
		this.populator = populator;
		
		this.init( rsMeta, session );
	}
	
	/**
	 * @param customDataSet
	 * @param rsMeta
	 */
	public DataSetResultCache( ICustomDataSet customDataSet, IResultClass rsMeta, DataEngineSession session )
	{
		assert customDataSet!=null;
		assert rsMeta != null;
		
		this.customDataSet = customDataSet;
		this.init( rsMeta, session );
	}
	
	/**
	 * Initialize
	 */
	private void init( IResultClass rsMeta, DataEngineSession session )
	{
		this.rsMeta = rsMeta;
		this.isLoad = false;
		this.isSaved = false;
		this.session = session;
		this.cacheCount = getCacheCount( );
	}
	
	/**
	 * when loaded data, this constuction is used
	 */
	public DataSetResultCache(  DataEngineSession session )
	{
		this.isLoad = true;
		this.session = session;
	}
	
	/**
	 * set new temp computed columns
	 * @param addedTempComputedColumn
	 */
	public void setTempComputedColumn( List addedTempComputedColumn )
	{
		this.addedTempComputedColumn = addedTempComputedColumn;
	}
	
	/**
	 * @return next data
	 * @throws DataException 
	 */
	public IResultObject fetch( ) throws DataException
	{
		if ( isLoad == false )
		{
			if ( cacheCount <= 0 )
			{
				if ( odaDataSet != null )
					return odaDataSet.fetch( );
				else if ( customDataSet != null )
					return customDataSet.fetch( );
				else
					return populator.next( );
			}
			else
			{
				if ( isSaved == false )
				{
					cacheDataSet( );
					isSaved = true;
				}
				return loadObject( );
			}
		}		
		else
		{
			return loadObject( );
		}
	}
	
	/**
	 * @throws DataException
	 */
	private void cacheDataSet( ) throws DataException
	{
		int index = 0;
		IResultObject resultObject = null;

		this.saveInit( );
		
		do
		{
			if ( odaDataSet != null )
				resultObject = odaDataSet.fetch( );
			else if ( customDataSet != null )
				resultObject = customDataSet.fetch( );
			else
				resultObject = this.populator.next( );
			
			if ( resultObject != null )
			{
				saveUtil.saveObject( resultObject );
				index++;
				if ( index >= cacheCount )
				{
					break;
				}
			}
		} while ( resultObject != null );
		
		this.saveClose( );
	}
	
	/**
	 * @return result class
	 * @throws DataException 
	 */
	public IResultClass getResultClass( ) throws DataException
	{
		if ( isLoad == true && rsMeta == null )
		{
			rsMeta = loadResultClass( );
			if ( addedTempComputedColumn != null
					&& addedTempComputedColumn.size( ) > 0 )
				processResultClass( );
		}

		return rsMeta;
	}
	
	/**
	 * Remove old temp computed column metadatas from cache file and add new metadatas.
	 * @throws DataException
	 */
	private void processResultClass( ) throws DataException
	{
		List metadataList = new ArrayList( );
		this.realColumnCount = 0;
		
		ResultFieldMetadata metadata = null;
		int i = 0;
		for ( i = 0; i < rsMeta.getFieldCount( ); i++ )
		{
			if ( !isTempComputedColumn( rsMeta.getFieldName( i + 1 ) ) )
			{
				metadata = new ResultFieldMetadata( 0,
						rsMeta.getFieldName( i + 1 ),
						rsMeta.getFieldLabel( i + 1 ),
						rsMeta.getFieldValueClass( i + 1 ),
						rsMeta.getFieldNativeTypeName( i + 1 ),
						rsMeta.isCustomField( i + 1 ) );
				metadata.setAlias( rsMeta.getFieldAlias( i + 1 ) );
				metadataList.add( metadata );
				realColumnCount++;
			}
		}
		
		ComputedColumn tempComputedColumn = null;
		for ( i = 0; i < addedTempComputedColumn.size( ); i++ )
		{
			tempComputedColumn = (ComputedColumn) ( addedTempComputedColumn.get( i ) );
			metadata = new ResultFieldMetadata( 0,
					tempComputedColumn.getName( ),
					null,
					DataType.getClass( tempComputedColumn.getDataType( ) ),
					null,
					true );
			metadataList.add( metadata );
		}
		
		rsMeta = new ResultClass( metadataList );
	}
	
	/**
	 * Return whether a clumn is a temp computed column.
	 * @param name
	 * @return
	 */
	private boolean isTempComputedColumn(String name)
	{
		return ( name.matches( "\\Q_{$TEMP_GROUP_\\E\\d*\\Q$}_\\E" )
				|| name.matches( "\\Q_{$TEMP_SORT_\\E\\d*\\Q$}_\\E" )
				|| name.matches( "\\Q_{$TEMP_FILTER_\\E\\d*\\Q$}_\\E" ));
	}
	
	/**
	 * @throws DataException 
	 */
	public void close( ) throws DataException
	{
		// when in save status, close might be done automatically
		if ( loadUtil != null )
		{
			loadUtil.close( );
			loadUtil = null;
		}
	}
	
	/**
	 * Init save util
	 */
	private void saveInit( )
	{
		saveUtil = CacheUtilFactory.createSaveUtil( getCacheObject(), this.rsMeta );
	}
	
	/**
	 * @throws DataException
	 */
	private void saveClose( ) throws DataException
	{
		if ( saveUtil != null )
		{
			saveUtil.close( );
			saveUtil = null;
		}
	}
	
	/**
	 * @return cached object
	 * @throws DataException
	 */
	private IResultObject loadObject( ) throws DataException
	{
		if ( loadUtil == null )
			loadUtil = CacheUtilFactory.createLoadUtil( getCacheObject() );

		IResultObject cacheObject = loadUtil.loadObject( );

		if ( cacheObject == null )
		{
			return cacheObject;
		}

		if ( addedTempComputedColumn != null
				&& addedTempComputedColumn.size( ) > 0 )
		{
			ResultObject resultObject = new ResultObject( getResultClass( ),
					getAllObjects( cacheObject ) );

			return resultObject;
		}
		else
		{
			return cacheObject;
		}
	}
	
	/**
	 * get all new field objects from a cacheObject
	 * @param cacheObject
	 * @return
	 * @throws DataException
	 */
	private Object[] getAllObjects( IResultObject cacheObject )
			throws DataException
	{
		Object[] objects = new Object[realColumnCount
				+ addedTempComputedColumn.size( )];
		for ( int i = 0; i < realColumnCount; i++ )
		{
			objects[i] = cacheObject.getFieldValue( i + 1 );
		}
		return objects;
	}
	
	/**
	 * @return IResultClass
	 * @throws DataException
	 */
	private IResultClass loadResultClass( ) throws DataException
	{
		if ( loadUtil == null )
			loadUtil = CacheUtilFactory.createLoadUtil( getCacheObject() );

		return loadUtil.loadResultClass( );
	}

	/**
	 * 
	 * @return
	 */
	private DataSetCacheManager getDataSetCacheManager( )
	{
		return this.session.getDataSetCacheManager( );
	}
	
	/**
	 * @return
	 */
	private int getCacheCount()
	{
		return getDataSetCacheManager( ).getCacheRowCount( );
	}
	
	/** 
	 * @return
	 */
	private IDataSetCacheObject getCacheObject()
	{
		return getDataSetCacheManager( ).getCacheObject( );
	}
}
