/*******************************************************************************
 * 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.chart.render;

import org.eclipse.birt.chart.computation.DataPointHints;
import org.eclipse.birt.chart.computation.withaxes.SeriesRenderingHints;
import org.eclipse.birt.chart.datafeed.StockEntry;
import org.eclipse.birt.chart.device.IPrimitiveRenderer;
import org.eclipse.birt.chart.device.IStructureDefinitionListener;
import org.eclipse.birt.chart.engine.extension.i18n.Messages;
import org.eclipse.birt.chart.event.EventObjectCache;
import org.eclipse.birt.chart.event.InteractionEvent;
import org.eclipse.birt.chart.event.LineRenderEvent;
import org.eclipse.birt.chart.event.PolygonRenderEvent;
import org.eclipse.birt.chart.event.RectangleRenderEvent;
import org.eclipse.birt.chart.event.StructureSource;
import org.eclipse.birt.chart.event.WrappedStructureSource;
import org.eclipse.birt.chart.exception.ChartException;
import org.eclipse.birt.chart.log.ILogger;
import org.eclipse.birt.chart.log.Logger;
import org.eclipse.birt.chart.model.ChartWithAxes;
import org.eclipse.birt.chart.model.attribute.Bounds;
import org.eclipse.birt.chart.model.attribute.ChartDimension;
import org.eclipse.birt.chart.model.attribute.ColorDefinition;
import org.eclipse.birt.chart.model.attribute.Fill;
import org.eclipse.birt.chart.model.attribute.LegendItemType;
import org.eclipse.birt.chart.model.attribute.LineAttributes;
import org.eclipse.birt.chart.model.attribute.Location;
import org.eclipse.birt.chart.model.attribute.impl.BoundsImpl;
import org.eclipse.birt.chart.model.attribute.impl.ColorDefinitionImpl;
import org.eclipse.birt.chart.model.attribute.impl.LocationImpl;
import org.eclipse.birt.chart.model.data.SeriesDefinition;
import org.eclipse.birt.chart.model.data.Trigger;
import org.eclipse.birt.chart.model.data.impl.TriggerImpl;
import org.eclipse.birt.chart.model.layout.ClientArea;
import org.eclipse.birt.chart.model.layout.Legend;
import org.eclipse.birt.chart.model.layout.Plot;
import org.eclipse.birt.chart.model.type.StockSeries;
import org.eclipse.birt.chart.plugin.ChartEngineExtensionPlugin;
import org.eclipse.birt.chart.script.ScriptHandler;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.util.EcoreUtil;

/**
 * Stock
 */
public final class Stock extends AxesRenderer
{

	private static ILogger logger = Logger.getLogger( "org.eclipse.birt.chart.engine.extension/render" ); //$NON-NLS-1$

	/**
	 * The constructor.
	 */
	public Stock( )
	{
		super( );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.chart.render.AxesRenderer#renderSeries(org.eclipse.birt.chart.output.IRenderer,
	 *      Chart.Plot)
	 */
	public void renderSeries( IPrimitiveRenderer ipr, Plot p,
			ISeriesRenderingHints isrh ) throws ChartException
	{
		final ChartWithAxes cwa = (ChartWithAxes) getModel( );
		if ( cwa.getDimension( ) != ChartDimension.TWO_DIMENSIONAL_LITERAL )
		{
			throw new ChartException( ChartEngineExtensionPlugin.ID,
					ChartException.RENDERING,
					"exception.stock.dimension", //$NON-NLS-1$
					new Object[]{
						cwa.getDimension( ).getName( )
					},
					Messages.getResourceBundle( getRunTimeContext( ).getULocale( ) ) );
		}

		// OBTAIN AN INSTANCE OF THE CHART (TO RETRIEVE GENERAL CHART PROPERTIES
		// IF ANY)
		final SeriesRenderingHints srh = (SeriesRenderingHints) isrh;

		// VALIDATE CONSISTENT DATASET COUNT BETWEEN BASE AND ORTHOGONAL
		try
		{
			validateDataSetCount( isrh );
		}
		catch ( ChartException vex )
		{
			throw new ChartException( ChartEngineExtensionPlugin.ID,
					ChartException.RENDERING,
					vex );
		}

		// SCALE VALIDATION
		if ( !srh.isCategoryScale( ) )
		{
			throw new ChartException( ChartEngineExtensionPlugin.ID,
					ChartException.RENDERING,
					"exception.xvalue.scale.stock", //$NON-NLS-1$
					Messages.getResourceBundle( getRunTimeContext( ).getULocale( ) ) );
		}

		final ScriptHandler sh = getRunTimeContext( ).getScriptHandler( );

		logger.log( ILogger.INFORMATION,
				Messages.getString( "info.render.series", //$NON-NLS-1$
						new Object[]{
								getClass( ).getName( ),
								new Integer( iSeriesIndex + 1 ),
								new Integer( iSeriesCount )
						},
						getRunTimeContext( ).getULocale( ) ) );

		// OBTAIN AN INSTANCE OF THE SERIES MODEL
		final StockSeries ss = (StockSeries) getSeries( );

		// TEST VISIBILITY
		if ( !ss.isSetVisible( ) )
		{
			throw new ChartException( ChartEngineExtensionPlugin.ID,
					ChartException.RENDERING,
					"exception.series.visibility", //$NON-NLS-1$ 
					new Object[]{
						ss
					},
					Messages.getResourceBundle( getRunTimeContext( ).getULocale( ) ) );
		}
		if ( !ss.isVisible( ) )
		{
			return;
		}

		// SETUP VARS USED IN RENDERING
		final double dSeriesThickness = srh.getSeriesThickness( );
		final DataPointHints[] dpha = srh.getDataPoints( );
		// !Already checked done by the stock entry.
		// validateNullDatapoint(dpha);

		final LineAttributes lia = ss.getLineAttributes( );
		final double dUnitSpacing = ( !cwa.isSetUnitSpacing( ) ) ? 50
				: cwa.getUnitSpacing( ); // AS A PERCENTAGE OF A
		// QUARTER OF A UNIT
		// SIZE
		double dX = 0, dY = 0, dWidth = 0, dHeight = 0, dSpacing = 0;
		double dLow, dHigh, dOpen, dClose;
		Location lo, loStart = null, loEnd = null, loUpper = LocationImpl.create( 0,
				0 ), loLower = LocationImpl.create( 0, 0 );
		Location[] loaFrontFace = null;
		LineRenderEvent lre;

		// SETUP THE MARKER FILL COLOR FROM THE SERIES DEFINITION PALETTE (BY
		// CATEGORIES OR BY SERIES)
		final SeriesDefinition sd = getSeriesDefinition( );
		final EList elPalette = sd.getSeriesPalette( ).getEntries( );
		final int iPaletteSize = elPalette.size( );
		if ( iPaletteSize == 0 )
		{
			throw new ChartException( ChartEngineExtensionPlugin.ID,
					ChartException.RENDERING,
					"exception.empty.palette", //$NON-NLS-1$ 
					new Object[]{
						ss
					},
					Messages.getResourceBundle( getRunTimeContext( ).getULocale( ) ) );
		}

		final boolean bPaletteByCategory = ( cwa.getLegend( )
				.getItemType( )
				.getValue( ) == LegendItemType.CATEGORIES );
		int iThisSeriesIndex = -1;
		Fill fPaletteEntry = null;
		if ( !bPaletteByCategory )
		{
			iThisSeriesIndex = sd.getRunTimeSeries( ).indexOf( ss );
			if ( iThisSeriesIndex < 0 )
			{
				throw new ChartException( ChartEngineExtensionPlugin.ID,
						ChartException.RENDERING,
						"exception.missing.series.for.palette.index", //$NON-NLS-1$ 
						new Object[]{
								ss, sd
						},
						Messages.getResourceBundle( getRunTimeContext( ).getULocale( ) ) );
			}
			fPaletteEntry = (Fill) EcoreUtil.copy( (Fill) elPalette.get( iThisSeriesIndex
					% iPaletteSize ) );
		}

		double[] faX = new double[dpha.length];
		double[] faY = new double[dpha.length];

		// RENDER THE RISERS (BAR, TRIANGLE, ETC) FOR EACH DATA ELEMENT IN THE
		// SERIES
		for ( int i = 0; i < dpha.length; i++ )
		{
			StockEntry se = (StockEntry) dpha[i].getOrthogonalValue( );
			if ( !isValidEntry( se ) ) // NULL VALUE HANDLING
			{
				faX[i] = Double.NaN;
				faY[i] = Double.NaN;
				continue;
			}
			
			if ( bPaletteByCategory )
			{
				fPaletteEntry = (Fill) EcoreUtil.copy( (Fill) elPalette.get( i
						% iPaletteSize ) );
			}
			else
			{
				fPaletteEntry = (Fill) EcoreUtil.copy( (Fill) elPalette.get( iThisSeriesIndex
						% iPaletteSize ) );
			}
			updateTranslucency( fPaletteEntry, ss );

			ScriptHandler.callFunction( sh,
					ScriptHandler.BEFORE_DRAW_ELEMENT,
					dpha[i],
					fPaletteEntry );
			ScriptHandler.callFunction( sh,
					ScriptHandler.BEFORE_DRAW_DATA_POINT,
					dpha[i],
					fPaletteEntry,
					getRunTimeContext( ).getScriptContext( ) );
			getRunTimeContext( ).notifyStructureChange( IStructureDefinitionListener.BEFORE_DRAW_ELEMENT,
					dpha[i] );
			getRunTimeContext( ).notifyStructureChange( IStructureDefinitionListener.BEFORE_DRAW_DATA_POINT,
					dpha[i] );

			// OBTAIN THE CO-ORDINATES OF THE DATA POINT
			lo = dpha[i].getLocation( );
			dX = lo.getX( );
			dY = lo.getY( );

			try
			{
				dLow = srh.getLocationOnOrthogonal( new Double( se.getLow( ) ) );
				dHigh = srh.getLocationOnOrthogonal( new Double( se.getHigh( ) ) );
				dOpen = srh.getLocationOnOrthogonal( new Double( se.getOpen( ) ) );
				dClose = srh.getLocationOnOrthogonal( new Double( se.getClose( ) ) );
			}
			catch ( Exception ex )
			{
				logger.log( ex );
				continue;
			}

			// ADJUST CO-ORDINATES BASED ON EACH UNIT SHARE (DUE TO MULTIPLE
			// SERIES)
			dSpacing = ( dpha[i].getSize( ) * dUnitSpacing ) / 200;
			if ( loStart == null ) // ONE TIME CREATION
			{
				loStart = LocationImpl.create( 0, 0 );
				loEnd = LocationImpl.create( 0, 0 );
				loaFrontFace = new Location[4];
				for ( int j = 0; j < 4; j++ )
				{
					loaFrontFace[j] = LocationImpl.create( 0, 0 );
				}
			}

			if ( ss.isShowAsBarStick( ) )
			{
				int stickLength = ss.getStickLength( );

				Location loStart2 = LocationImpl.create( 0, 0 ), loEnd2 = LocationImpl.create( 0,
						0 );

				// BRANCH OFF HERE IF THE PLOT IS TRANSPOSED (AXES ARE SWAPPED)
				if ( cwa.isTransposed( ) )
				{
					dHeight = dpha[i].getSize( );
					dY += dHeight / 2;

					loStart.set( dOpen, dY + stickLength );
					loEnd.set( dClose, dY - stickLength );

					loStart2.set( dOpen, dY );
					loEnd2.set( dClose, dY );

					loUpper.set( dHigh > dLow ? dLow : dHigh, dY );
					loLower.set( dHigh < dLow ? dLow : dHigh, dY );

					loaFrontFace[0].set( dHigh, dY - stickLength );
					loaFrontFace[1].set( dLow, dY - stickLength );
					loaFrontFace[2].set( dLow, dY + stickLength );
					loaFrontFace[3].set( dHigh, dY + stickLength );
				}
				else
				// STANDARD PROCESSING FOR REGULAR NON-TRANSPOSED AXES (NOTE:
				// SYMMETRIC CODE)
				{
					dWidth = dpha[i].getSize( );
					dX += dWidth / 2;

					loStart.set( dX - stickLength, dOpen );
					loEnd.set( dX + stickLength, dClose );

					loStart2.set( dX, dOpen );
					loEnd2.set( dX, dClose );

					loUpper.set( dX, dHigh > dLow ? dHigh : dLow );
					loLower.set( dX, dHigh < dLow ? dHigh : dLow );

					loaFrontFace[0].set( dX - stickLength, dHigh );
					loaFrontFace[1].set( dX - stickLength, dLow );
					loaFrontFace[2].set( dX + stickLength, dLow );
					loaFrontFace[3].set( dX + stickLength, dHigh );
				}

				// UPPER-LOWER SEGMENT
				lre = (LineRenderEvent) ( (EventObjectCache) ipr ).getEventObject( WrappedStructureSource.createSeriesDataPoint( ss, dpha[ i ] ),
						LineRenderEvent.class );
				lre.setLineAttributes( lia );
				lre.setStart( loUpper );
				lre.setEnd( loLower );
				ipr.drawLine( lre );

				// OPEN SEGMENT
				lre.setStart( loStart );
				lre.setEnd( loStart2 );
				ipr.drawLine( lre );

				// CLOSE SEGMENT
				lre.setStart( loEnd );
				lre.setEnd( loEnd2 );
				ipr.drawLine( lre );
			}
			else
			{
				// BRANCH OFF HERE IF THE PLOT IS TRANSPOSED (AXES ARE SWAPPED)
				if ( cwa.isTransposed( ) )
				{
					dHeight = dpha[i].getSize( );
					dHeight -= 2 * dSpacing;
					dY += dSpacing + dHeight / 2;

					loStart.set( dLow, dY );
					loEnd.set( dHigh, dY );

					loaFrontFace[0].set( dOpen, dY - dHeight / 2 );
					loaFrontFace[1].set( dClose, dY - dHeight / 2 );
					loaFrontFace[2].set( dClose, dY + dHeight / 2 );
					loaFrontFace[3].set( dOpen, dY + dHeight / 2 );

					loUpper.set( dOpen > dClose ? dClose : dOpen, dY );
					loLower.set( dOpen < dClose ? dClose : dOpen, dY );
				}
				else
				// STANDARD PROCESSING FOR REGULAR NON-TRANSPOSED AXES (NOTE:
				// SYMMETRIC CODE)
				{
					dWidth = dpha[i].getSize( );
					dWidth -= 2 * dSpacing;
					dX += dSpacing + dWidth / 2;

					loStart.set( dX, dLow );
					loEnd.set( dX, dHigh );

					loaFrontFace[0].set( dX - dWidth / 2, dOpen );
					loaFrontFace[1].set( dX - dWidth / 2, dClose );
					loaFrontFace[2].set( dX + dWidth / 2, dClose );
					loaFrontFace[3].set( dX + dWidth / 2, dOpen );

					loUpper.set( dX, dOpen > dClose ? dOpen : dClose );
					loLower.set( dX, dOpen < dClose ? dOpen : dClose );
				}

				// UPPER SEGMENT
				lre = (LineRenderEvent) ( (EventObjectCache) ipr ).getEventObject( WrappedStructureSource.createSeriesDataPoint( ss, dpha[ i ] ),
						LineRenderEvent.class );
				lre.setLineAttributes( lia );
				lre.setStart( loStart );
				lre.setEnd( loUpper );
				ipr.drawLine( lre );

				// LOWER SEGMENT
				lre.setStart( loLower );
				lre.setEnd( loEnd );
				ipr.drawLine( lre );

				// RENDER THE RECTANGLE (EXTRUDED IF > 2D)
				renderPlane( ipr,
						WrappedStructureSource.createSeriesDataPoint( ss, dpha[ i ] ),
						loaFrontFace,
						( ( dClose > dOpen && !cwa.isTransposed( ) ) || ( dClose < dOpen && cwa.isTransposed( ) ) )
								? fPaletteEntry : ColorDefinitionImpl.WHITE( ),
						lia,
						cwa.getDimension( ),
						dSeriesThickness,
						false );
			}

			if ( cwa.isTransposed( ) )
			{
				faX[i] = ( ( loUpper.getX( ) + loLower.getX( ) ) / 2 );
				faY[i] = loUpper.getY( );
			}
			else
			{
				faX[i] = loLower.getX( );
				faY[i] = ( ( loUpper.getY( ) + loLower.getY( ) ) / 2 );
			}

			if ( isInteractivityEnabled( ) )
			{
				final EList elTriggers = ss.getTriggers( );
				if ( !elTriggers.isEmpty( ) )
				{
					final StructureSource iSource = WrappedStructureSource.createSeriesDataPoint( ss,
							dpha[i] );
					final InteractionEvent iev = (InteractionEvent) ( (EventObjectCache) ipr ).getEventObject( iSource,
							InteractionEvent.class );
					Trigger tg;
					for ( int t = 0; t < elTriggers.size( ); t++ )
					{
						tg = TriggerImpl.copyInstance( (Trigger) elTriggers.get( t ) );
						processTrigger( tg, iSource );
						iev.addTrigger( tg );
					}

					final PolygonRenderEvent pre = (PolygonRenderEvent) ( (EventObjectCache) ipr ).getEventObject( StructureSource.createSeries( ss ),
							PolygonRenderEvent.class );
					pre.setPoints( loaFrontFace );
					iev.setHotSpot( pre );
					ipr.enableInteraction( iev );
				}
			}

			ScriptHandler.callFunction( sh,
					ScriptHandler.AFTER_DRAW_ELEMENT,
					dpha[i],
					fPaletteEntry );
			ScriptHandler.callFunction( sh,
					ScriptHandler.AFTER_DRAW_DATA_POINT,
					dpha[i],
					fPaletteEntry,
					getRunTimeContext( ).getScriptContext( ) );
			getRunTimeContext( ).notifyStructureChange( IStructureDefinitionListener.AFTER_DRAW_ELEMENT,
					dpha[i] );
			getRunTimeContext( ).notifyStructureChange( IStructureDefinitionListener.AFTER_DRAW_DATA_POINT,
					dpha[i] );
		}

		// Render the fitting curve.
		if ( getSeries( ).getCurveFitting( ) != null )
		{
			Location[] larray = new Location[faX.length];
			for ( int i = 0; i < larray.length; i++ )
			{
				larray[i] = LocationImpl.create( faX[i], faY[i] );
			}
			larray = filterNull( larray );
			renderFittingCurve( ipr,
					larray,
					getSeries( ).getCurveFitting( ),
					false,
					true );
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.chart.render.BaseRenderer#renderLegendGraphic(org.eclipse.birt.chart.device.IPrimitiveRenderer,
	 *      org.eclipse.birt.chart.model.layout.Legend,
	 *      org.eclipse.birt.chart.model.attribute.Fill,
	 *      org.eclipse.birt.chart.model.attribute.Bounds)
	 */
	public void renderLegendGraphic( IPrimitiveRenderer ipr, Legend lg,
			Fill fPaletteEntry, Bounds bo ) throws ChartException
	{
		final ClientArea ca = lg.getClientArea( );
		final StockSeries ss = (StockSeries) getSeries( );

		RectangleRenderEvent rre = (RectangleRenderEvent) ( (EventObjectCache) ipr ).getEventObject( StructureSource.createLegend( lg ),
				RectangleRenderEvent.class );
		rre.setBackground( ca.getBackground( ) );
		rre.setBounds( bo );
		ipr.fillRectangle( rre );

		final LineAttributes lia = ss.getLineAttributes( );
		if ( !lia.isSetVisible( ) || !lia.isVisible( ) )
		{
			throw new ChartException( ChartEngineExtensionPlugin.ID,
					ChartException.RENDERING,
					"exception.stock.lineattr.visibility", //$NON-NLS-1$ 
					Messages.getResourceBundle( getRunTimeContext( ).getULocale( ) ) );
		}

		final ChartWithAxes cwa = (ChartWithAxes) getModel( );
		final LineRenderEvent lre = (LineRenderEvent) ( (EventObjectCache) ipr ).getEventObject( StructureSource.createLegend( lg ),
				LineRenderEvent.class );
		rre = (RectangleRenderEvent) ( (EventObjectCache) ipr ).getEventObject( StructureSource.createLegend( lg ),
				RectangleRenderEvent.class );
		lre.setLineAttributes( lia );
		rre.setBackground( fPaletteEntry );
		rre.setOutline( lia );

		if ( cwa.isTransposed( ) ) // TURN THE CANDLE
		{
			// DRAW THE LINE
			lre.setStart( LocationImpl.create( bo.getLeft( ), bo.getTop( )
					+ bo.getHeight( )
					/ 2 ) );
			lre.setEnd( LocationImpl.create( bo.getLeft( ) + bo.getWidth( ),
					bo.getTop( ) + bo.getHeight( ) / 2 ) );

			// DEFINE THE RECTANGLE
			rre.setBounds( BoundsImpl.create( bo.getLeft( )
					+ bo.getWidth( )
					/ 4,
					bo.getTop( ) + 1,
					bo.getWidth( ) / 2,
					bo.getHeight( ) - 2 ) );
		}
		else
		// STRAIGHT CANDLE
		{
			// DEFINE THE LINE
			lre.setStart( LocationImpl.create( bo.getLeft( )
					+ bo.getWidth( )
					/ 2, bo.getTop( ) + bo.getHeight( ) ) );
			lre.setEnd( LocationImpl.create( bo.getLeft( ) + bo.getWidth( ) / 2,
					bo.getTop( ) ) );

			// DEFINE THE RECTANGLE
			rre.setBounds( BoundsImpl.create( bo.getLeft( ) + 1, bo.getTop( )
					+ bo.getHeight( )
					/ 4, bo.getWidth( ) - 2, bo.getHeight( ) / 2 ) );
		}
		ipr.drawLine( lre );
		ipr.fillRectangle( rre );
		ipr.drawRectangle( rre );
	}

	private boolean isValidEntry( StockEntry entry )
	{
		return ( entry != null
				&& !Double.isNaN( entry.getHigh( ) )
				&& !Double.isNaN( entry.getLow( ) )
				&& !Double.isNaN( entry.getClose( ) ) && !Double.isNaN( entry.getOpen( ) ) );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.chart.render.BaseRenderer#compute(org.eclipse.birt.chart.model.attribute.Bounds,
	 *      org.eclipse.birt.chart.model.layout.Plot,
	 *      org.eclipse.birt.chart.render.ISeriesRenderingHints)
	 */
	public void compute( Bounds bo, Plot p, ISeriesRenderingHints isrh )
			throws ChartException
	{
		// NOTE: This method is not used by the Stock Renderer
	}

}