/*******************************************************************************
 * 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.impl.document.viewing;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

import org.eclipse.birt.core.util.IOUtil;
import org.eclipse.birt.data.engine.api.IBaseExpression;
import org.eclipse.birt.data.engine.api.IBaseQueryDefinition;
import org.eclipse.birt.data.engine.api.IBaseTransform;
import org.eclipse.birt.data.engine.api.IConditionalExpression;
import org.eclipse.birt.data.engine.api.IFilterDefinition;
import org.eclipse.birt.data.engine.api.IGroupDefinition;
import org.eclipse.birt.data.engine.api.IScriptExpression;
import org.eclipse.birt.data.engine.api.ISortDefinition;
import org.eclipse.birt.data.engine.api.querydefn.ConditionalExpression;
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.QueryDefinition;
import org.eclipse.birt.data.engine.api.querydefn.ScriptExpression;
import org.eclipse.birt.data.engine.api.querydefn.SortDefinition;
import org.eclipse.birt.data.engine.core.DataException;
import org.eclipse.birt.data.engine.i18n.ResourceConstants;

/**
 * Save and load transform information of base query
 */
public class TransformUtil
{
	private static TransformUtil instance = new TransformUtil();
	
	//----------------------save base query information-----------------
	
	/**
	 * @param queryDefn
	 * @param outputStream
	 * @throws DataException
	 */
	public static void saveBaseQuery( IBaseQueryDefinition queryDefn,
			OutputStream outputStream ) throws DataException
	{
		DataOutputStream dos = new DataOutputStream( outputStream );

		try
		{
			// save transform information
			instance.saveTransform( dos, queryDefn );

			// save groups information
			instance.saveGroupInfo( dos, queryDefn.getGroups( ) );
			dos.close( );
		}
		catch ( IOException e )
		{
			throw new DataException( ResourceConstants.RD_SAVE_ERROR, e );
		}
	}
	
	/**
	 * @param dos
	 * @param transform
	 * @throws DataException
	 * @throws IOException
	 */
	private void saveTransform( DataOutputStream dos, IBaseTransform transform )
			throws DataException, IOException
	{
		List filterList = transform.getFilters( );
		if ( filterList == null || filterList.size( ) == 0 )
		{
			IOUtil.writeInt( dos, 0 );
		}
		else
		{
			int size = filterList.size( );
			IOUtil.writeInt( dos, size );

			for ( int i = 0; i < size; i++ )
			{
				this.saveExpression( dos,
						( (IFilterDefinition) filterList.get( i ) ).getExpression( ) );
			}
		}

		// save sorts information
		List sortList = transform.getSorts( );
		if ( sortList == null || sortList.size( ) == 0 )
		{
			IOUtil.writeInt( dos, 0 );
		}
		else
		{
			int size = sortList.size( );
			IOUtil.writeInt( dos, size );

			for ( int i = 0; i < size; i++ )
			{
				ISortDefinition sd = (ISortDefinition) sortList.get( i );
				IOUtil.writeString( dos, sd.getColumn( ) );
				this.saveExpression( dos, sd.getExpression( ) );
				IOUtil.writeInt( dos, sd.getSortDirection( ) );
			}
		}
	}
	
	/**
	 * @param dos
	 * @param groupDefn
	 * @throws DataException
	 * @throws IOException
	 */
	private void saveGroupInfo( DataOutputStream dos, List groupList )
			throws DataException, IOException
	{
		if ( groupList == null || groupList.size( ) == 0 )
		{
			IOUtil.writeInt( dos, 0 );
		}
		else
		{
			int size = groupList.size( );
			IOUtil.writeInt( dos, size );
			
			for ( int i = 0; i < size; i++ )
			{
				IGroupDefinition groupDefn = (IGroupDefinition) groupList.get( i );
				IOUtil.writeInt( dos, groupDefn.getInterval( ) );
				IOUtil.writeInt( dos, groupDefn.getSortDirection( ) );
				IOUtil.writeDouble( dos, groupDefn.getIntervalRange( ) );
				IOUtil.writeObject( dos, groupDefn.getIntervalStart( ) );
				IOUtil.writeString( dos, groupDefn.getKeyColumn( ) );
				IOUtil.writeString( dos, groupDefn.getKeyExpression( ) );
				this.saveTransform( dos, groupDefn );
			}
		}
	}
	
	/**
	 * @param dos
	 * @param expr
	 * @throws IOException 
	 */
	private void saveExpression( DataOutputStream dos, IBaseExpression expr )
			throws IOException
	{
		if ( expr == null )
		{
			IOUtil.writeBool( dos, false );
			return;
		}
		else
		{
			IOUtil.writeBool( dos, true );
		}

		if ( expr instanceof IScriptExpression )
		{
			IOUtil.writeInt( dos, ExprMetaInfo.SCRIPT_EXPRESSION );
			
			IScriptExpression se = (IScriptExpression) expr;
			IOUtil.writeInt( dos, se.getDataType( ) );
			IOUtil.writeString( dos, se.getText( ) );
		}
		else if ( expr instanceof IConditionalExpression )
		{
			IOUtil.writeInt( dos, ExprMetaInfo.CONDITIONAL_EXPRESSION );
			
			IConditionalExpression ce = (IConditionalExpression) expr;
			IOUtil.writeInt( dos, ce.getDataType( ) );
			this.saveExpression( dos, ce.getExpression( ) );
			IOUtil.writeInt( dos, ce.getOperator( ) );
			this.saveExpression( dos, ce.getOperand1( ) );
			this.saveExpression( dos, ce.getOperand2( ) );
		}
		else
		{
			assert false;
		}
	}
	
	//----------------------load base query information-----------------
	
	/**
	 * Load the original query transform information
	 * 
	 * @throws DataException
	 * @throws IOException
	 */
	public static IBaseQueryDefinition loadBaseQuery( InputStream inputStream )
			throws DataException
	{
		QueryDefinition queryDefn = new QueryDefinition( );

		DataInputStream dis = new DataInputStream( inputStream );
		
		try
		{
			// load transform information
			instance.loadTransform( dis, queryDefn );

			// load groups information
			instance.loadGroupInfo( dis, queryDefn );
			
			dis.close( );
		}
		catch ( IOException e )
		{
			throw new DataException( ResourceConstants.RD_LOAD_ERROR, e );
		}		

		return queryDefn;
	}

	/**
	 * @param is
	 * @param transform
	 * @throws IOException
	 */
	private void loadTransform( DataInputStream dis, IBaseTransform transform )
			throws IOException
	{
		// load filter information
		int size = IOUtil.readInt( dis );
		if ( size > 0 )
		{
			for ( int i = 0; i < size; i++ )
			{
				transform.getFilters( )
						.add( new FilterDefinition( this.loadExpression( dis ) ) );
			}
		}

		// load sorts information
		size = IOUtil.readInt( dis );
		if ( size > 0 )
		{
			for ( int i = 0; i < size; i++ )
			{
				SortDefinition sd = new SortDefinition( );
				sd.setColumn( IOUtil.readString( dis ) );
				sd.setExpression( ( (IScriptExpression) this.loadExpression( dis ) ).getText( ) );
				sd.setSortDirection( IOUtil.readInt( dis ) );

				transform.getSorts( ).add( sd );
			}
		}
	}

	/**
	 * @param is
	 * @param queryDefn
	 * @throws IOException
	 */
	private void loadGroupInfo( DataInputStream dis, IBaseQueryDefinition queryDefn )
			throws IOException
	{
		int size = IOUtil.readInt( dis );
		if ( size > 0 )
		{
			for ( int i = 0; i < size; i++ )
			{
				GroupDefinition gd = new GroupDefinition( );
				gd.setInterval( IOUtil.readInt( dis ) );
				gd.setSortDirection( IOUtil.readInt( dis ) );
				gd.setIntervalRange( IOUtil.readDouble( dis ) );
				gd.setIntervalStart( IOUtil.readObject( dis ) );
				gd.setKeyColumn( IOUtil.readString( dis ) );
				gd.setKeyExpression( IOUtil.readString( dis ) );
				this.loadTransform( dis, gd );

				queryDefn.getGroups( ).add( queryDefn );
			}
		}
	}
	
	/**
	 * @param dis
	 * @return
	 * @throws IOException
	 */
	private IBaseExpression loadExpression( DataInputStream dis )
			throws IOException
	{
		if ( IOUtil.readBool( dis ) == false )
			return null;

		IBaseExpression expr = null;
		int type = IOUtil.readInt( dis );
		if ( type == ExprMetaInfo.SCRIPT_EXPRESSION )
		{
			int dataType = IOUtil.readInt( dis );
			String text = IOUtil.readString( dis );

			ScriptExpression se = new ScriptExpression( text );
			se.setDataType( dataType );

			expr = se;
		}
		else if ( type == ExprMetaInfo.CONDITIONAL_EXPRESSION )
		{
			int dataType = IOUtil.readInt( dis );
			IScriptExpression be = (IScriptExpression) this.loadExpression( dis );
			int oper = IOUtil.readInt( dis );
			IScriptExpression operand1Expr = (IScriptExpression) this.loadExpression( dis );
			IScriptExpression operand2Expr = (IScriptExpression) this.loadExpression( dis );

			ConditionalExpression ce = new ConditionalExpression( be,
					oper,
					operand1Expr,
					operand2Expr );
			ce.setDataType( dataType );

			expr = ce;

		}
		else
		{
			assert false;
		}

		return expr;
	}
	
}
