/***********************************************************************************************
 * File Info: $Id: Chart.java,v 1.12 2003/06/29 13:28:10 nathaniel_auvil Exp $
 * Copyright (C) 2002
 * Author: Nathaniel G. Auvil
 * Contributor(s): Sandor Dornbush
 *
 * Copyright 2002 (C) Nathaniel G. Auvil. All Rights Reserved.
 *
 * Redistribution and use of this software and associated documentation ("Software"), with or
 * without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright statements and notices.
 * 	Redistributions must also contain a copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
 * 	conditions and the following disclaimer in the documentation and/or other materials
 * 	provided with the distribution.
 *
 * 3. The name "jCharts" or "Nathaniel G. Auvil" must not be used to endorse or promote
 * 	products derived from this Software without prior written permission of Nathaniel G.
 * 	Auvil.  For written permission, please contact nathaniel_auvil@users.sourceforge.net
 *
 * 4. Products derived from this Software may not be called "jCharts" nor may "jCharts" appear
 * 	in their names without prior written permission of Nathaniel G. Auvil. jCharts is a
 * 	registered trademark of Nathaniel G. Auvil.
 *
 * 5. Due credit should be given to the jCharts Project (http://jcharts.sourceforge.net/).
 *
 * THIS SOFTWARE IS PROVIDED BY Nathaniel G. Auvil AND CONTRIBUTORS ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * jCharts OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
 ************************************************************************************************/

package org.jCharts;


import org.jCharts.imageMap.ImageMap;
import org.jCharts.properties.*;
import org.jCharts.test.HTMLGenerator;
import org.jCharts.chartData.ChartDataException;

import java.awt.*;
import java.awt.font.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.Serializable;
import java.text.AttributedString;


/***********************************************************************************************
 * Base class of all charts.
 *
 ***********************************************************************************************/
abstract public class Chart implements Serializable
{
	//---Be aware that the calls BufferedImage.getGraphics() and BufferedImage.createGraphics(), actually
	//--- create a new Graphics2D Object on each invocation.  So keep a reference here.
	private Graphics2D graphics2D;

	private int width;
	private int height;

	private Legend legend;

	//---general properties of all chart types.
	private ChartProperties chartProperties;


	//---used to generate an image map for the image
	private boolean generateImageMap = false;
	private ImageMap imageMap = null;
	private BufferedImage bufferedImage = null;


	/******************************************************************************************
	 * Constructor
	 *
	 * @param legendProperties
	 * @param chartProperties
	 * @param pixelWidth
	 * @param pixelHeight
	 *******************************************************************************************/
	public Chart( LegendProperties legendProperties, ChartProperties chartProperties, int pixelWidth, int pixelHeight )
	{
		this.width = pixelWidth;
		this.height = pixelHeight;
		this.chartProperties = chartProperties;

		if( legendProperties != null )
		{
			this.legend = new Legend( this, legendProperties );
                        legendProperties.setSize(new Dimension(width, height));
		}
	}


	/******************************************************************************************
	 * Returns flag indicating whether to generate an ImageMap
	 *
	 * @return boolean
	 *******************************************************************************************/
	public boolean getGenerateImageMapFlag()
	{
		return this.generateImageMap;
	}


	/******************************************************************************************
	 * Returns the BufferedImage used to generate the ImageMap.  Only should be called on
	 * 	binary format images, such as PNG and JPG, as it will not work on SVG.
	 *
	 * This is a HACK and the design of jCharts should do better than this!!!!!!
	 *
	 * @return BufferedImage
	 *******************************************************************************************/
	public BufferedImage getBufferedImage()
	{
		return this.bufferedImage;
	}


	/******************************************************************************************
	 * Renders the chart into a BufferedImage so that we can calculate all the Image Map
	 *	coordinates.
	 *
	 * @throws ChartDataException
	 * @throws PropertyException
	 *******************************************************************************************/
	public void renderWithImageMap() throws ChartDataException, PropertyException
	{
		this.bufferedImage = new BufferedImage( this.getImageWidth(), this.getImageHeight(), BufferedImage.TYPE_INT_RGB );
		this.setGraphics2D( this.bufferedImage.createGraphics() );
		this.generateImageMap = true;

		this.render();
	}


	/******************************************************************************************
	 * Call this to kick off rendering of the chart
	 *
	 * @throws ChartDataException
	 * @throws PropertyException
	 *******************************************************************************************/
	public void render() throws ChartDataException, PropertyException
	{
		Rectangle2D.Float rectangle = new Rectangle2D.Float( 0, 0, this.width, this.height );

		//---fill the background
		this.graphics2D.setPaint( this.chartProperties.getBackgroundPaint() );
		this.graphics2D.fill( rectangle );

		//---draw a border around the chart if desired
		if( this.chartProperties.getBorderStroke() != null )
		{
			//---there is a one pixel difference when you fill versus draw a rectangle
			rectangle.width -= 1;
			rectangle.height -= 1;
			this.chartProperties.getBorderStroke().draw( this.graphics2D, rectangle );
		}

		//---draw the chart
		this.renderChart();
	}


	/*************************************************************************************************
	 * Displays the chart title and returns the height of the title PLUS title padding.
	 *
	 * @param chartTitle
	 * @param fontRenderContext
	 * @return float the height required by the title. If no title is displayed, zero is returned.
	 **************************************************************************************************/
	protected float renderChartTitle( String chartTitle, FontRenderContext fontRenderContext )
	{
		float height = 0;

		if( chartTitle != null )
		{
			//---the y value of where to write the current line
			float currentLine = this.getChartProperties().getEdgePadding();

			// change title into an AttributedString with the font as an attribute
			AttributedString s = new AttributedString( chartTitle );
			s.addAttribute( TextAttribute.FONT, this.getChartProperties().getTitleFont().getFont() );

			//---get LineBreakMeasurer on the attributed string...it will break the text for us
			LineBreakMeasurer measurer = new LineBreakMeasurer( s.getIterator(), fontRenderContext );

			//---set the text color
			this.getGraphics2D().setPaint( this.getChartProperties().getTitleFont().getPaint() );

			//---draw each title line.  Subtract padding from each side for printable width.
			float wrappingWidth= this.getImageWidth() - ( this.getChartProperties().getEdgePadding() * 2 );

			TextLayout titleTextLayout= null;
			while( ( titleTextLayout = measurer.nextLayout( wrappingWidth ) ) != null )
			{
				//---set the current line to where the title will be written
				currentLine += titleTextLayout.getAscent();

				titleTextLayout.draw( this.getGraphics2D(),
											 ( ( this.getImageWidth() - titleTextLayout.getAdvance() ) / 2 ),
											 currentLine );

				//---keep track of total height of all the title lines
				height +=  titleTextLayout.getAscent() + titleTextLayout.getDescent();
			}

			//---add in the padding between the title and the top of the chart.
			height += this.getChartProperties().getTitlePadding();
		}

		return height;
	}


	/******************************************************************************************
	 *
	 * @throws ChartDataException
	 * @throws PropertyException
	 *******************************************************************************************/
	abstract protected void renderChart() throws ChartDataException, PropertyException;


	/******************************************************************************************
	 * Returns the BufferedImage width
	 *
	 * @return int
	 *******************************************************************************************/
	public final int getImageWidth()
	{
		return this.width;
	}


	/******************************************************************************************
	 * Returns the BufferedImage height
	 *
	 * @return int
	 *******************************************************************************************/
	public final int getImageHeight()
	{
		return this.height;
	}


	/******************************************************************************************
	 * Returns the general properties Object.
	 *
	 * @return ChartProperties
	 *******************************************************************************************/
	public final ChartProperties getChartProperties()
	{
		return this.chartProperties;
	}


	/******************************************************************************************
	 * Returns the Legend.  Will be NULL if no Legend is desired.
	 *
	 * @return Legend
	 *******************************************************************************************/
	protected final Legend getLegend()
	{
		return this.legend;
	}


	/******************************************************************************************
	 * Returns flag indicating if there is a Legend.
	 *
	 * @return boolean
	 *******************************************************************************************/
	public final boolean hasLegend()
	{
		return this.legend != null;
	}


	/******************************************************************************************
	 * Sets the graphics object to render the chart on by the encoder.
	 *
	 * @param graphics2D
	 *******************************************************************************************/
	public final void setGraphics2D( Graphics2D graphics2D )
	{
		this.graphics2D = graphics2D;
	}


	/******************************************************************************************
	 * Shortcut method to get Graphics2D. Be aware that the call BufferedImage.getGraphics()
	 *  and BufferedImage.createGraphics(), actually create a new Grpahics2D Object on each
	 *  invocation. This returns the member reference so calls to this are not creating a new
	 *  Object each time.
	 *
	 * @return Graphics2D
	 *******************************************************************************************/
	public final Graphics2D getGraphics2D()
	{
		return this.graphics2D;
	}


	/**********************************************************************************************
	 * To optimze performance of the ImageMap Object, we create it once we know how many data
	 *  elements are in the chart which is dependent on the AxisChart or PieChart2D
	 *
	 * @param imageMap
	 **********************************************************************************************/
	public final void setImageMap( ImageMap imageMap )
	{
		this.imageMap = imageMap;
	}


	/**********************************************************************************************
	 *
	 * @return imageMap
	 **********************************************************************************************/
	public final ImageMap getImageMap()
	{
		return this.imageMap;
	}


	/**********************************************************************************************
	 * Enables the testing routines to display the contents of this Object.
	 *
	 * @param htmlGenerator
	 * @param imageFileName
	 * @param imageMap if this is NULL we are not creating image map data in html
	 **********************************************************************************************/
	public void toHTML( HTMLGenerator htmlGenerator, String imageFileName, ImageMap imageMap )
	{
		htmlGenerator.chartTableStart( this.getClass().getName(), imageFileName, imageMap );

		htmlGenerator.chartTableRowStart();
		this.chartProperties.toHTML( htmlGenerator );
		htmlGenerator.chartTableRowEnd();

		if( this.legend != null )
		{
			htmlGenerator.chartTableRowStart();
			this.getLegend().toHTML( htmlGenerator );
			htmlGenerator.chartTableRowEnd();
		}

		htmlGenerator.chartTableEnd();
	}


}
