/*******************************************************************************
 * 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.ui.swt.wizard.internal;

import org.eclipse.birt.chart.device.IDeviceRenderer;
import org.eclipse.birt.chart.factory.GeneratedChartState;
import org.eclipse.birt.chart.factory.Generator;
import org.eclipse.birt.chart.factory.RunTimeContext;
import org.eclipse.birt.chart.model.Chart;
import org.eclipse.birt.chart.model.attribute.Bounds;
import org.eclipse.birt.chart.model.attribute.impl.BoundsImpl;
import org.eclipse.birt.chart.ui.i18n.Messages;
import org.eclipse.birt.chart.ui.swt.ChartBuilderMessageLookup;
import org.eclipse.birt.chart.ui.swt.wizard.ChartAdapter;
import org.eclipse.birt.chart.ui.swt.wizard.ChartWizardContext;
import org.eclipse.birt.chart.util.PluginSettings;
import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.core.ui.frameworks.taskwizard.WizardBase;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Display;

/**
 * 
 */

public class ChartPreviewPainter implements PaintListener, ControlListener
{

	private transient Canvas preview = null;

	private transient Chart chart = null;

	private transient boolean bIsPainting = false;

	private static transient boolean enableProcessor = true;

	private static transient boolean isLivePreview = false;

	private transient Image buffer;

	private transient ChartWizardContext wizardContext;

	private static int X_OFFSET = 3;
	private static int Y_OFFSET = 3;

	public ChartPreviewPainter( ChartWizardContext wizardContext )
	{
		super( );
		this.wizardContext = wizardContext;
	}

	private void updateBuffer( )
	{
		if ( bIsPainting )
		{
			return;
		}
		Throwable paintError = null;
		if ( chart == null )
		{
			return;
		}

		Rectangle re = preview.getClientArea( );

		final Rectangle adjustedRe = new Rectangle( 0, 0, re.width, re.height );

		if ( adjustedRe.width - 2 * X_OFFSET <= 0
				|| adjustedRe.height - 2 * Y_OFFSET <= 0 )
		{
			if ( buffer != null && !buffer.isDisposed( ) )
			{
				buffer.dispose( );
				buffer = null;
			}
			return;
		}

		bIsPainting = true;

		Image oldBuffer = null;

		if ( buffer == null )
		{
			buffer = new Image( Display.getDefault( ), adjustedRe );
		}
		else
		{
			Rectangle ore = buffer.getBounds( );

			oldBuffer = buffer;

			if ( !adjustedRe.equals( ore ) )
			{
				buffer = new Image( Display.getDefault( ), adjustedRe );
			}
		}

		GC gc = new GC( buffer );

		// fill default backgournd as white.
		gc.setForeground( Display.getDefault( )
				.getSystemColor( SWT.COLOR_WHITE ) );
		gc.fillRectangle( buffer.getBounds( ) );

		final Bounds bo = BoundsImpl.create( X_OFFSET,
				Y_OFFSET,
				adjustedRe.width - 2 * X_OFFSET,
				adjustedRe.height - 2 * Y_OFFSET );

		IDeviceRenderer deviceRenderer = null;

		try
		{
			deviceRenderer = PluginSettings.instance( ).getDevice( "dv.SWT" ); //$NON-NLS-1$
			deviceRenderer.setProperty( IDeviceRenderer.GRAPHICS_CONTEXT, gc );
			bo.scale( 72d / deviceRenderer.getDisplayServer( )
					.getDpiResolution( ) ); // CONVERT
			// TO
			// POINTS

			// GENERATE AND RENDER THE CHART
			final Generator gr = Generator.instance( );

			RunTimeContext rtc = new RunTimeContext( );
			rtc.setScriptingEnabled( false );
			rtc.setMessageLookup( new ChartBuilderMessageLookup( this.wizardContext.getUIServiceProvider( ) ) );
			if ( wizardContext.isRtL( ) )
			{
				rtc.setRightToLeft( true );
			}
			GeneratedChartState state = gr.build( deviceRenderer.getDisplayServer( ),
					chart,
					bo,
					null,
					rtc,
					enableProcessor ? wizardContext.getProcessor( ) : null );
			gr.render( deviceRenderer, state );
		}
		catch ( Exception ex )
		{
			paintError = ex;
		}
		finally
		{
			gc.dispose( );
			if ( deviceRenderer != null )
			{
				deviceRenderer.dispose( );
			}
		}

		if ( paintError != null )
		{
			buffer = oldBuffer;
			// showException( gc, paintError );
			WizardBase.displayException( paintError );
		}

		if ( oldBuffer != null && oldBuffer != buffer )
		{
			oldBuffer.dispose( );
		}
		bIsPainting = false;
	}

	/**
	 * 
	 * @param g2d
	 * @param ex
	 */
	protected final void showException( GC g2d, Throwable ex )
	{
		String sWrappedException = ex.getClass( ).getName( );
		Throwable th = ex;
		while ( ex.getCause( ) != null )
		{
			ex = ex.getCause( );
		}
		String sException = ex.getClass( ).getName( );
		if ( sWrappedException.equals( sException ) )
		{
			sWrappedException = null;
		}

		String sMessage = null;
		if ( th instanceof BirtException )
		{
			sMessage = ( (BirtException) th ).getLocalizedMessage( );
		}
		else
		{
			sMessage = ex.getMessage( );
		}

		if ( sMessage == null )
		{
			sMessage = "<null>"; //$NON-NLS-1$
		}
		StackTraceElement[] stea = ex.getStackTrace( );
		Rectangle rCA = preview.getClientArea( );

		Device dv = Display.getCurrent( );
		Font fo = new Font( dv, "Courier", SWT.BOLD, 16 ); //$NON-NLS-1$
		g2d.setFont( fo );
		FontMetrics fm = g2d.getFontMetrics( );
		g2d.setBackground( dv.getSystemColor( SWT.COLOR_WHITE ) );
		g2d.fillRectangle( 20, 20, rCA.width - 40, rCA.height - 40 );
		g2d.setForeground( dv.getSystemColor( SWT.COLOR_BLACK ) );
		g2d.drawRectangle( 20, 20, rCA.width - 40, rCA.height - 40 );
		g2d.setClipping( 20, 20, rCA.width - 40, rCA.height - 40 );
		int x = 25, y = 20 + fm.getHeight( );
		drawString( g2d,
				Messages.getString( "ChartAdapter.Exception.Exception" ), x, y ); //$NON-NLS-1$
		x += g2d.textExtent( Messages.getString( "ChartAdapter.Exception.Exception" ) ).x + 5; //$NON-NLS-1$
		g2d.setForeground( dv.getSystemColor( SWT.COLOR_RED ) );
		drawString( g2d, sException, x, y );
		x = 25;
		y += fm.getHeight( );
		if ( sWrappedException != null )
		{
			g2d.setForeground( dv.getSystemColor( SWT.COLOR_BLACK ) );
			drawString( g2d,
					Messages.getString( "ChartAdapter.Exception.WrappedIn" ), x, y ); //$NON-NLS-1$
			x += g2d.textExtent( Messages.getString( "ChartAdapter.Exception.WrappedIn" ) ).x + 5; //$NON-NLS-1$
			g2d.setForeground( dv.getSystemColor( SWT.COLOR_RED ) );
			drawString( g2d, sWrappedException, x, y );
			x = 25;
			y += fm.getHeight( );
		}
		g2d.setForeground( dv.getSystemColor( SWT.COLOR_BLACK ) );
		y += 10;
		drawString( g2d,
				Messages.getString( "ChartAdapter.Exception.Message" ), x, y ); //$NON-NLS-1$
		x += g2d.textExtent( Messages.getString( "ChartAdapter.Exception.Message" ) ).x + 5; //$NON-NLS-1$
		g2d.setForeground( dv.getSystemColor( SWT.COLOR_BLUE ) );
		drawString( g2d, sMessage, x, y );
		x = 25;
		y += fm.getHeight( );
		g2d.setForeground( dv.getSystemColor( SWT.COLOR_BLACK ) );
		y += 10;
		drawString( g2d,
				Messages.getString( "ChartAdapter.Exception.Trace" ), x, y ); //$NON-NLS-1$
		x = 40;
		y += fm.getHeight( );
		g2d.setForeground( dv.getSystemColor( SWT.COLOR_DARK_GREEN ) );
		if ( stea == null )
		{
			drawString( g2d, "<null stack trace>", x, y ); //$NON-NLS-1$
		}
		else
		{
			for ( int i = 0; i < stea.length; i++ )
			{
				drawString( g2d, stea[i].getClassName( )
						+ ":" + stea[i].getMethodName( ) + "(...):" //$NON-NLS-1$ //$NON-NLS-2$
						+ stea[i].getLineNumber( ), x, y );
				x = 40;
				y += fm.getHeight( );
			}
		}
		fo.dispose( );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent)
	 */
	public void paintControl( PaintEvent pev )
	{
		GC gc = pev.gc;
		if ( buffer != null )
		{
			gc.drawImage( buffer, 0, 0 );
		}
	}

	private static final void drawString( GC gc, String s, int x, int y )
	{
		if ( s == null )
		{
			s = "<null>"; //$NON-NLS-1$
		}
		gc.drawString( s, x, y );
	}

	/**
	 * @param previewCanvas
	 */
	public void setPreview( Canvas previewCanvas )
	{
		this.preview = previewCanvas;
	}

	public void renderModel( Chart chart )
	{
		if ( chart == null )
		{
			return;
		}
		this.chart = chart;
		ignoreNotifications( true );

		// If not use live preview, use sample data to create runtime series
		if ( !( isLivePreview && isLivePreviewEnabled( ) ) )
		{
			chart.createSampleRuntimeSeries( );
		}

		if ( preview != null && !preview.isDisposed( ) )
		{
			updateBuffer( );
			preview.redraw( );
		}
		ignoreNotifications( false );
	}

	private void ignoreNotifications( boolean bIgnoreNotifications )
	{
		ChartAdapter.ignoreNotifications( bIgnoreNotifications );
	}

	public static void enableProcessor( boolean isEnabled )
	{
		enableProcessor = isEnabled;
	}

	public static boolean isProcessorEnabled( )
	{
		return enableProcessor;
	}

	public void controlMoved( ControlEvent e )
	{

	}

	public void controlResized( ControlEvent e )
	{
		if ( preview != null && !preview.isDisposed( ) )
		{
			updateBuffer( );
			preview.redraw( );
		}

	}

	public void dispose( )
	{
		isLivePreview = false;
		if ( buffer != null )
		{
			buffer.dispose( );
		}
	}

	/**
	 * Checks whether Live Preview is active
	 */
	public static boolean isLivePreviewActive( )
	{
		return isLivePreview;
	}

	/**
	 * Activates Live Preview when the data bindings are complete. The final
	 * result depends on whether Live Preview is enabled.
	 * 
	 * @param canLive
	 *            activate Live Preview or not
	 */
	public static void activateLivePreview( boolean canLive )
	{
		isLivePreview = canLive;
	}

	/**
	 * Checks whether Live Preview is enabled
	 */
	private boolean isLivePreviewEnabled( )
	{
		return wizardContext.getDataServiceProvider( ).isLivePreviewEnabled( );
	}

}
