/*******************************************************************************
 * 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;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

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.IFilterDefinition;
import org.eclipse.birt.data.engine.api.IQueryDefinition;
import org.eclipse.birt.data.engine.api.IScriptExpression;
import org.eclipse.birt.data.engine.api.ISortDefinition;
import org.eclipse.birt.data.engine.api.ISubqueryDefinition;
import org.eclipse.birt.data.engine.api.querydefn.BaseQueryDefinition;
import org.eclipse.birt.data.engine.api.querydefn.QueryDefinition;
import org.eclipse.birt.data.engine.api.querydefn.SortDefinition;
import org.eclipse.birt.data.engine.api.querydefn.SubqueryDefinition;
import org.eclipse.birt.data.engine.core.DataException;
import org.eclipse.birt.data.engine.i18n.ResourceConstants;

/**
 * Save query definition structure into repot document and loading from report
 * document.
 */
public class QueryDefnUtil
{	
	/**
	 * @param outputStream
	 * @param queryDefn
	 * @throws DataException
	 * @throws IOException
	 */
	static void saveBaseQueryDefn( OutputStream outputStream,
			IBaseQueryDefinition queryDefn ) throws DataException
	{
		try
		{
			// binding column definition
			saveExpressions( outputStream, queryDefn.getResultSetExpressions( ) );

			// filter definition
			FilterDefnUtil.saveFilterDefn( outputStream, queryDefn.getFilters( ) );
			// group definition
			GroupDefnUtil.saveGroupDefn( outputStream, queryDefn.getGroups( ) );
			// sort definition
			saveSorts( outputStream, queryDefn.getSorts( ) );

			// misc property: max row, use details
			IOUtil.writeInt( outputStream, queryDefn.getMaxRows( ) );
			IOUtil.writeBool( outputStream, queryDefn.usesDetails( ) );
			
			// sub query name
			saveSubQuery( outputStream, queryDefn.getSubqueries( ) );
		}
		catch ( IOException e )
		{
			throw new DataException( ResourceConstants.RD_SAVE_ERROR, e );
		}
		
	}
	
	/**
	 * @param outputStream
	 * @param exprMap
	 * @throws IOException
	 */
	private static void saveExpressions( OutputStream outputStream, Map exprMap )
			throws IOException
	{
		DataOutputStream dos = new DataOutputStream( outputStream );
		
		if ( exprMap == null )
		{
			IOUtil.writeInt( dos, 0 );
		}
		else
		{
			IOUtil.writeInt( dos, exprMap.size( ) );

			Iterator it = exprMap.entrySet( ).iterator( );
			while ( it.hasNext( ) )
			{
				Map.Entry entry = (Entry) it.next( );
				IOUtil.writeString( dos, (String) entry.getKey( ) );
				ExprUtil.saveBaseExpr( dos, (IBaseExpression) entry.getValue( ) );
			}
		}
		
		dos.flush( );
	}
	
	/**
	 * @param outputStream
	 * @param sorts
	 * @throws IOException 
	 */
	public static void saveSorts( OutputStream outputStream, List sorts )
			throws IOException
	{
		DataOutputStream dos = new DataOutputStream( outputStream );

		if ( sorts == null )
		{
			IOUtil.writeInt( dos, 0 );
		}
		else
		{
			IOUtil.writeInt( dos, sorts.size( ) );

			Iterator it = sorts.iterator( );
			while ( it.hasNext( ) )
			{
				ISortDefinition sortDefn = (ISortDefinition) it.next( );
				IOUtil.writeString( dos, sortDefn.getColumn( ) );
				ExprUtil.saveBaseExpr( dos, sortDefn.getExpression( ) );
				IOUtil.writeInt( dos, sortDefn.getSortDirection( ) );
			}
		}

		dos.flush( );
	}
	
	/**
	 * @throws IOException 
	 * @throws DataException 
	 *
	 */
	static void saveSubQuery( OutputStream outputStream,
			Collection subQuery ) throws DataException, IOException
	{
		DataOutputStream dos = new DataOutputStream( outputStream );

		if ( subQuery == null )
		{
			IOUtil.writeInt( dos, 0 );
		}
		else
		{
			IOUtil.writeInt( dos, subQuery.size( ) );
			
			Iterator it = subQuery.iterator( );
			while ( it.hasNext( ) )
			{
				ISubqueryDefinition subQueryDefn = (ISubqueryDefinition) it.next( );
				IOUtil.writeString( dos, subQueryDefn.getName( ) );
				IOUtil.writeBool( outputStream, subQueryDefn.applyOnGroup( ) );
				saveBaseQueryDefn( outputStream, subQueryDefn );
			}
		}

		dos.flush( );
	}
	
	/**
	 * @param outputStream
	 * @throws DataException
	 * @throws IOException
	 */
	static IQueryDefinition loadQueryDefn( InputStream inputStream )
			throws DataException
	{
		QueryDefinition queryDefn = new QueryDefinition( );
		loadBaseQueryDefn( inputStream, queryDefn );
		return queryDefn;
	}
	
	/**
	 * @param inputStream
	 * @param queryDefn
	 * @throws DataException
	 */
	private static void loadBaseQueryDefn( InputStream inputStream,
			BaseQueryDefinition queryDefn ) throws DataException
	{
		try
		{
			// binding column definition
			queryDefn.getResultSetExpressions( )
					.putAll( loadExprMap( inputStream ) );

			// filter definition
			queryDefn.getFilters( )
					.addAll( FilterDefnUtil.loadFilterDefn( inputStream ) );
			// group definition
			queryDefn.getGroups( )
					.addAll( GroupDefnUtil.loadGroupDefn( inputStream ) );
			// sort definition
			queryDefn.getSorts( ).addAll( loadSorts( inputStream ) );

			// misc property: max row, use details
			queryDefn.setMaxRows( IOUtil.readInt( inputStream ) );
			queryDefn.setUsesDetails( IOUtil.readBool( inputStream ) );
			
			// sub query name
			queryDefn.getSubqueries( ).addAll( loadSubQuery( inputStream ) );
		}
		catch ( IOException e )
		{
			throw new DataException( ResourceConstants.RD_LOAD_ERROR, e );
		}
	}
	
	/**
	 * @param outputStream
	 * @param exprMap
	 * @throws IOException
	 */
	private static Map loadExprMap( InputStream inputStream )
			throws IOException
	{
		DataInputStream dis = new DataInputStream( inputStream );

		Map exprMap = new HashMap( );
		int size = IOUtil.readInt( dis );

		for ( int i = 0; i < size; i++ )
		{
			String exprName = IOUtil.readString( dis );
			IBaseExpression expr = ExprUtil.loadBaseExpr( dis );
			exprMap.put( exprName, expr );
		}

		return exprMap;
	}
	
	/**
	 * @param outputStream
	 * @param sorts
	 * @throws IOException
	 */
	public static List loadSorts( InputStream inputStream ) throws IOException
	{
		DataInputStream dis = new DataInputStream( inputStream );

		List sortList = new ArrayList( );
		int size = IOUtil.readInt( dis );
		for ( int i = 0; i < size; i++ )
		{
			String sortKeyColumn = IOUtil.readString( dis );
			IScriptExpression sortKeyExpr = (IScriptExpression) ExprUtil.loadBaseExpr( dis );
			int direction = IOUtil.readInt( dis );

			SortDefinition sortDefn = new SortDefinition( );
			if ( sortKeyColumn != null )
				sortDefn.setColumn( sortKeyColumn );
			else
				sortDefn.setExpression( sortKeyExpr.getText( ) );
			sortDefn.setSortDirection( direction );
			sortList.add( sortDefn );
		}
		
		return sortList;
	}
	
	/**
	 * @param inputStream
	 * @return
	 * @throws DataException
	 * @throws IOException
	 */
	public static Collection loadSubQuery( InputStream inputStream )
			throws DataException, IOException
	{
		DataInputStream dis = new DataInputStream( inputStream );

		Collection subQuerys = new ArrayList( );
		int size = IOUtil.readInt( dis );
		for ( int i = 0; i < size; i++ )
		{
			SubqueryDefinition subQueryDefn = new SubqueryDefinition( IOUtil.readString( dis ) );
			subQueryDefn.setApplyOnGroupFlag( IOUtil.readBool( dis ) );
			loadBaseQueryDefn( dis, subQueryDefn );
			subQuerys.add( subQueryDefn );
		}

		return subQuerys;
	}
	
	//---------------------------------------------------------------------------
	
	
	/**
	 * @param oldSubQueryDefns
	 * @param newSubQueryDefns
	 * @return
	 */
	public static boolean isCompatibleSQs( Collection oldSubQueryDefns,
			Collection newSubQueryDefns )
	{
		if ( oldSubQueryDefns == newSubQueryDefns )
			return true;
		else if ( oldSubQueryDefns == null )
			return newSubQueryDefns.size( ) == 0;
		else if ( newSubQueryDefns == null )
			return true;

		if ( oldSubQueryDefns.size( ) < newSubQueryDefns.size( ) )
			return false;

		Iterator oldIt = oldSubQueryDefns.iterator( );
		Iterator newIt = newSubQueryDefns.iterator( );
		while ( newIt.hasNext( ) )
		{
			ISubqueryDefinition oldSub = (ISubqueryDefinition) oldIt.next( );
			ISubqueryDefinition newSub = (ISubqueryDefinition) newIt.next( );

			if ( isEqualFilters( oldSub.getFilters( ), newSub.getFilters( ) ) == false )
				return false;

			if ( isEqualSorts( oldSub.getSorts( ), newSub.getSorts( ) ) == false )
				return false;

			if ( GroupDefnUtil.isEqualGroups( oldSub.getGroups( ),
					newSub.getGroups( ) ) == false )
				return false;

			if ( isEqualExprMap( oldSub.getResultSetExpressions( ),
					newSub.getResultSetExpressions( ) ) == false )
				return false;
		}

		return true;
	}
	
	/**
	 * @param oldFilter
	 * @param newFilter
	 * @return
	 */
	private static boolean isEqualFilters( List oldFilter, List newFilter )
	{
		if ( oldFilter == newFilter )
			return true;

		if ( oldFilter.size( ) != newFilter.size( ) )
			return false;

		Iterator oldIt = oldFilter.iterator( );
		Iterator newIt = newFilter.iterator( );
		while ( oldIt.hasNext( ) )
		{
			IFilterDefinition oldDefn = (IFilterDefinition) oldIt.next( );
			IFilterDefinition newDefn = (IFilterDefinition) newIt.next( );
			if ( FilterDefnUtil.isEqualFilter( oldDefn, newDefn ) == false )
				return false;
		}

		return true;
	}
	
	/**
	 * @param oldSorts
	 * @param newSorts
	 * @return
	 */
	public static boolean isEqualSorts( List oldSorts, List newSorts )
	{
		if ( oldSorts == newSorts )
			return true;

		if ( oldSorts.size( ) != newSorts.size( ) )
			return false;

		Iterator oldIt = oldSorts.iterator( );
		Iterator newIt = newSorts.iterator( );
		while ( oldIt.hasNext( ) )
		{
			ISortDefinition oldDefn = (ISortDefinition) oldIt.next( );
			ISortDefinition newDefn = (ISortDefinition) newIt.next( );

			if ( isEqualString( oldDefn.getColumn( ), newDefn.getColumn( ) ) == false
					|| ExprUtil.isEqualExpression( oldDefn.getExpression( ),
							newDefn.getExpression( ) ) == false
					|| oldDefn.getSortDirection( ) != newDefn.getSortDirection( ))
				return false;
		}

		return true;
	}

	/**
	 * @param oldExprMap
	 * @param newExprMap
	 * @return
	 */
	private static boolean isEqualExprMap( Map oldExprMap, Map newExprMap )
	{
		if ( oldExprMap == newExprMap )
			return true;
		else if ( oldExprMap == null )
			return newExprMap.size( ) == 0;
		else if ( oldExprMap == null )
			return newExprMap.size( ) == 0;

		return oldExprMap.size( ) >= newExprMap.size( );
	}
	
	/**
	 * @param ob1
	 * @param ob2
	 * @return
	 */
	private static boolean isEqualString( Object ob1, Object ob2 )
	{
		if ( ob1 == ob2 )
			return true;
		else if ( ob1 == null || ob2 == null )
			return false;

		return ob1.equals( ob2 );
	}
	
}
