
package org.eclipse.birt.report.engine.emitter.excel.chart;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

import org.eclipse.birt.chart.model.component.Series;
import org.eclipse.birt.chart.model.data.SeriesDefinition;
import org.eclipse.birt.report.engine.emitter.excel.Data;

public class TableGen
{
	public static final String STRING = "string";
	public static final String NUMBER = "number";
	public static final String DATETIME = "datetime";

	public TableDataIterator iterator( )
	{
		if ( isNested )
		{
			return new NestTableDataIterator( generateNestTable( ) );
		}
		else
		{
			return new FlatTableDataIterator( );
		}
	}

	public void addSeriesDefinition( SeriesDefinition sd, String title )
	{
		if ( sd == null )
		{
			return;
		}
		
		// If the identifier is found, save the column name accordingly.
		getOptionalGroup( sd );

		boolean first = true;
		// TODO, find a better way to save column position
		int pos = headers.size( );

		// Reuse a helper since a series definition can only generate datasets
		// with one data type
		DataHelper helper = null;

		for ( int i = 0; i < sd.getRunTimeSeries( ).size( ); i++ )
		{
			Series series = (Series) sd.getRunTimeSeries( ).get( i );
			Object value = series.getDataSet( ).getValues( );

			// Make sure adding the header name only once
			if ( first )
			{
				helper = DataHelperFactory.createDataHelper( value );

				String[] names = helper.getColumnHeader( title, series );
				if ( names != null )
				{
					for ( int j = 0; j < names.length; j++ )
					{
						headers.add( names[j] );
					}
				}

				first = false;
			}

			// When do not get a proper helper or the dataset is out of
			// control of the help, current datasets will be ignore.
			if ( helper == null || !helper.analyseData( value ) )
			{
				return;
			}

			String colIdentifier = series.getSeriesIdentifier( ) == null
					? null
					: String.valueOf( series.getSeriesIdentifier( ) );

			TableDataSet[] cols = helper.getTableDataSets( pos, colIdentifier );

			// If base dataset is empty, current dataset set to base dataset.
			// We suppose that the base data set has only one dataset.
			// If current datasets size is over 1, Other datasets except the
			// first one
			// will be discarded.
			// TODO, refactory this logic is ugly
			if ( base == null )
			{
				base = cols[0];
			}
			else
			{
				for ( int j = 0; j < cols.length; j++ )
				{
					datasets.add( cols[j] );
				}
			}
		}
	}

	private void getOptionalGroup( SeriesDefinition sd )
	{
		if ( !isNested )
		{
			if ( sd.getQuery( ) != null )
			{
				String opCol = sd.getQuery( ).getDefinition( );				

				if ( opCol != null && !"".equals( opCol ) && !"Base Series".equals(opCol))
				{
					// Add an optional group column
					isNested = true;
					headers.add( opCol );
				}
			}
		}
	}

	private Map generateNestTable( )
	{
		HashMap table = new HashMap( );

		int width = headers.size( );

		for ( int i = 0; i < base.cells.size( ); i++ )
		{
			table.put( new Integer( i ), new TableRows( base.getCell( i ),
					width ) );
		}

		// Mapping datas with identifier to proper base row
		for ( int i = 0; i < datasets.size( ); i++ )
		{
			TableDataSet column = (TableDataSet) datasets.get( i );

			for ( int j = 0; j < column.cells.size( ); j++ )
			{
				if ( column.cells.get( j ) != null )
				{
					TableRows grows = (TableRows) table.get( new Integer( j ) );
					Data[] cells = grows.getRow( column.identifier );
					cells[column.pos] = column.getCell( j );
				}
			}
		}

		return table;
	}

	private Data[] getEmptyRow( )
	{
		return new Data[headers.size( )];
	}

	private Data[] getTableHeaderCell( )
	{
		Data[] row = getEmptyRow( );

		for ( int i = 0; i < headers.size( ); i++ )
		{
			row[i] = AbstractDataHelper.createData( (String) headers.get( i ),
					Data.STRING );
		}

		return row;
	}

	private TableDataSet base = null;
	private Vector headers = new Vector( );
	private Vector datasets = new Vector( );
	boolean isNested = false;

	class FlatTableDataIterator implements TableDataIterator
	{
		public boolean hasNext( )
		{
			if ( null == base )
			{
				return false;
			}	
			return curPos < base.cells.size( );
		}

		public Data[] nextRow( )
		{
			Data[] row = null;
			curPos++;
			
			if ( curPos == 0 )
			{
				row = getTableHeaderCell( );
			}
			else
			{
				//Skip the header row
				row = getRow( curPos - 1);
			}			
			
			return row;
		}

		private Data[] getRow( int pos )
		{
			Data[] row = getEmptyRow( );

			row[0] = base.getCell( pos );

			for ( int i = 0; i < row.length - 1; i++ )
			{				
				// for ( int j = 0; j < datasets.size( ); j++ )
				// {
				// row[i] = ( (TableDataSet) datasets.get( j ) ).getCell( pos );
				//				
				if(i < datasets.size()) {
					row[i + 1] = ( (TableDataSet) datasets.get( i ) ).getCell( pos );
				}
			}

			return row;
		}

		private int curPos = -1;
	}

	class NestTableDataIterator implements TableDataIterator
	{

		public NestTableDataIterator( Map data )
		{
			Vector rows = new Vector( );
			Iterator iter = data.values( ).iterator( );

			while ( iter.hasNext( ) )
			{
				rows.addAll( ( (TableRows) iter.next( ) ).getAllRows( ) );
			}

			this.iter = rows.iterator( );
		}

		public boolean hasNext( )
		{
			if ( first )
			{
				return true;
			}
			else
			{
				return iter.hasNext( );
			}
		}

		public Data[] nextRow( )
		{
			if ( first )
			{
				first = false;
				return getTableHeaderCell( );
			}
			else
			{
				return getRow( );
			}
		}

		private Data[] getRow( )
		{
			return (Data[]) iter.next( );
		}

		private boolean first = true;
		private Iterator iter = null;
	}
}

class TableDataSet
{
	TableDataSet( int pos )
	{
		this.pos = pos;
	}

	void addCell( Data cell )
	{
		cells.add( cell );
	}

	Data getCell( int pos )
	{
		return (Data) cells.get( pos );
	}
	
	int getLeght()
	{
		return cells.size( );
	}

	int pos;
	Vector cells = new Vector( );
	String identifier = null;
}

class TableRows
{
	TableRows( Data baseCell, int length )
	{
		base = baseCell;
		rowlen = length;
	}

	Data[] getRow( String identifier )
	{
		if ( grow.get( identifier ) != null )
		{
			return (Data[]) grow.get( identifier );
		}
		else
		{
			Data[] crow = new Data[rowlen];
			crow[0] = base;
			crow[1] = AbstractDataHelper.createData( identifier, Data.STRING );
			grow.put( identifier, crow );

			return crow;
		}
	}

	int getRowCount( )
	{
		return grow.size( );
	}

	Collection getAllRows( )
	{
		return grow.values( );
	}

	Data base = null;
	int rowlen;
	HashMap grow = new HashMap( );
}
