/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2016 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.com
 *
 * Unless you have purchased a commercial license agreement from Jaspersoft,
 * the following license terms apply:
 *
 * This program is part of JasperReports.
 *
 * JasperReports is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JasperReports is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
 */
package net.sf.jasperreports.engine.fill;

import java.awt.Color;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.SortedSet;
import java.util.TimeZone;

import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.MeterInterval;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.Range;
import org.jfree.data.general.Dataset;

import net.sf.jasperreports.charts.ChartContext;
import net.sf.jasperreports.charts.ChartTheme;
import net.sf.jasperreports.charts.JRAreaPlot;
import net.sf.jasperreports.charts.JRBar3DPlot;
import net.sf.jasperreports.charts.JRBarPlot;
import net.sf.jasperreports.charts.JRBubblePlot;
import net.sf.jasperreports.charts.JRCandlestickPlot;
import net.sf.jasperreports.charts.JRCategoryDataset;
import net.sf.jasperreports.charts.JRChartAxis;
import net.sf.jasperreports.charts.JRDataRange;
import net.sf.jasperreports.charts.JRGanttDataset;
import net.sf.jasperreports.charts.JRHighLowDataset;
import net.sf.jasperreports.charts.JRHighLowPlot;
import net.sf.jasperreports.charts.JRLinePlot;
import net.sf.jasperreports.charts.JRMeterPlot;
import net.sf.jasperreports.charts.JRMultiAxisPlot;
import net.sf.jasperreports.charts.JRPie3DPlot;
import net.sf.jasperreports.charts.JRPieDataset;
import net.sf.jasperreports.charts.JRPiePlot;
import net.sf.jasperreports.charts.JRScatterPlot;
import net.sf.jasperreports.charts.JRThermometerPlot;
import net.sf.jasperreports.charts.JRTimePeriodDataset;
import net.sf.jasperreports.charts.JRTimeSeriesDataset;
import net.sf.jasperreports.charts.JRTimeSeriesPlot;
import net.sf.jasperreports.charts.JRValueDataset;
import net.sf.jasperreports.charts.JRXyDataset;
import net.sf.jasperreports.charts.JRXyzDataset;
import net.sf.jasperreports.charts.fill.JRFillCategoryDataset;
import net.sf.jasperreports.charts.fill.JRFillChartAxis;
import net.sf.jasperreports.charts.fill.JRFillGanttDataset;
import net.sf.jasperreports.charts.fill.JRFillHighLowDataset;
import net.sf.jasperreports.charts.fill.JRFillMultiAxisPlot;
import net.sf.jasperreports.charts.fill.JRFillPieDataset;
import net.sf.jasperreports.charts.fill.JRFillTimePeriodDataset;
import net.sf.jasperreports.charts.fill.JRFillTimeSeriesDataset;
import net.sf.jasperreports.charts.fill.JRFillXyDataset;
import net.sf.jasperreports.charts.fill.JRFillXyzDataset;
import net.sf.jasperreports.charts.type.AxisPositionEnum;
import net.sf.jasperreports.charts.type.EdgeEnum;
import net.sf.jasperreports.charts.util.CategoryChartHyperlinkProvider;
import net.sf.jasperreports.charts.util.ChartHyperlinkProvider;
import net.sf.jasperreports.charts.util.ChartUtil;
import net.sf.jasperreports.charts.util.HighLowChartHyperlinkProvider;
import net.sf.jasperreports.charts.util.JRMeterInterval;
import net.sf.jasperreports.charts.util.MultiAxisChartHyperlinkProvider;
import net.sf.jasperreports.charts.util.PieChartHyperlinkProvider;
import net.sf.jasperreports.charts.util.TimePeriodChartHyperlinkProvider;
import net.sf.jasperreports.charts.util.TimeSeriesChartHyperlinkProvider;
import net.sf.jasperreports.charts.util.XYChartHyperlinkProvider;
import net.sf.jasperreports.engine.JRAbstractChartCustomizer;
import net.sf.jasperreports.engine.JRChart;
import net.sf.jasperreports.engine.JRChartCustomizer;
import net.sf.jasperreports.engine.JRChartDataset;
import net.sf.jasperreports.engine.JRChartPlot;
import net.sf.jasperreports.engine.JRChartPlot.JRSeriesColor;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExpression;
import net.sf.jasperreports.engine.JRExpressionCollector;
import net.sf.jasperreports.engine.JRFont;
import net.sf.jasperreports.engine.JRGroup;
import net.sf.jasperreports.engine.JRHyperlinkParameter;
import net.sf.jasperreports.engine.JRLineBox;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintHyperlinkParameters;
import net.sf.jasperreports.engine.JRPrintImage;
import net.sf.jasperreports.engine.JRPropertiesUtil;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JRVisitor;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.NamedChartCustomizer;
import net.sf.jasperreports.engine.base.JRBaseChart;
import net.sf.jasperreports.engine.type.EvaluationTimeEnum;
import net.sf.jasperreports.engine.type.HyperlinkTypeEnum;
import net.sf.jasperreports.engine.type.ModeEnum;
import net.sf.jasperreports.engine.util.JRClassLoader;
import net.sf.jasperreports.engine.util.JRStringUtil;
import net.sf.jasperreports.engine.util.StyleUtil;
import net.sf.jasperreports.export.HtmlReportConfiguration;
import net.sf.jasperreports.renderers.Renderable;


/**
 * @author Teodor Danciu (teodord@users.sourceforge.net)
 * @author Some enhancements by Barry Klawans (bklawans@users.sourceforge.net)
 */
public class JRFillChart extends JRFillElement implements JRChart
{
	public static final String EXCEPTION_MESSAGE_KEY_CUSTOMIZER_INSTANCE_ERROR = "charts.customizer.instance.error";
	public static final String EXCEPTION_MESSAGE_KEY_MULTIAXIS_PLOT_TYPES_MIX_NOT_ALLOWED = "charts.multiaxis.plot.types.mix.not.allowed";
	public static final String EXCEPTION_MESSAGE_KEY_MULTIAXIS_PLOT_NOT_SUPPORTED = "charts.multiaxis.plot.not.supported";

	/**
	 *
	 */
	protected byte chartType;

	/**
	 *
	 */
	protected JRFont titleFont;
	protected JRFont subtitleFont;
	protected JRFont legendFont;

	protected final JRLineBox initLineBox;
	protected JRLineBox lineBox;

	/**
	 *
	 */
	protected JRGroup evaluationGroup;

	protected JRFillChartDataset dataset;
	protected JRChartPlot plot;

	protected Renderable renderer;
	private String anchorName;
	private String hyperlinkReference;
	private Boolean hyperlinkWhen;
	private String hyperlinkAnchor;
	private Integer hyperlinkPage;
	private String hyperlinkTooltip;
	private JRPrintHyperlinkParameters hyperlinkParameters;

	protected List<JRChartCustomizer> chartCustomizers;
	
	protected String renderType;
	protected String themeName;

	protected JFreeChart jfreeChart;
	protected ChartHyperlinkProvider chartHyperlinkProvider;

	/**
	 *
	 */
	protected JRFillChart(
		JRBaseFiller filler,
		JRChart chart,
		JRFillObjectFactory factory
		)
	{
		super(filler, chart, factory);

		/*   */
		chartType = chart.getChartType();

		switch(chartType) {
			case CHART_TYPE_AREA:
				dataset = (JRFillChartDataset) factory.getCategoryDataset((JRCategoryDataset) chart.getDataset());
				plot = factory.getAreaPlot((JRAreaPlot) chart.getPlot());
				break;
			case CHART_TYPE_BAR:
				dataset = (JRFillChartDataset) factory.getCategoryDataset((JRCategoryDataset) chart.getDataset());
				plot = factory.getBarPlot((JRBarPlot) chart.getPlot());
				break;
			case CHART_TYPE_BAR3D:
				dataset = (JRFillChartDataset) factory.getCategoryDataset((JRCategoryDataset) chart.getDataset());
				plot = factory.getBar3DPlot((JRBar3DPlot) chart.getPlot());
				break;
			case CHART_TYPE_BUBBLE:
				dataset = (JRFillChartDataset) factory.getXyzDataset((JRXyzDataset) chart.getDataset());
				plot = factory.getBubblePlot((JRBubblePlot) chart.getPlot());
				break;
			case CHART_TYPE_CANDLESTICK:
				dataset = (JRFillChartDataset) factory.getHighLowDataset((JRHighLowDataset) chart.getDataset());
				plot = factory.getCandlestickPlot((JRCandlestickPlot) chart.getPlot());
				break;
			case CHART_TYPE_HIGHLOW:
				dataset = (JRFillChartDataset) factory.getHighLowDataset((JRHighLowDataset) chart.getDataset());
				plot = factory.getHighLowPlot((JRHighLowPlot) chart.getPlot());
				break;
			case CHART_TYPE_LINE:
				dataset = (JRFillChartDataset) factory.getCategoryDataset((JRCategoryDataset) chart.getDataset());
				plot = factory.getLinePlot((JRLinePlot) chart.getPlot());
				break;
			case CHART_TYPE_METER:
				dataset = (JRFillChartDataset) factory.getValueDataset((JRValueDataset) chart.getDataset());
				plot = factory.getMeterPlot((JRMeterPlot) chart.getPlot());
				break;
			case CHART_TYPE_MULTI_AXIS:
				plot = factory.getMultiAxisPlot((JRMultiAxisPlot) chart.getPlot());
				dataset = ((JRFillMultiAxisPlot)plot).getMainDataset();
				break;
			case CHART_TYPE_PIE:
				dataset = (JRFillChartDataset) factory.getPieDataset((JRPieDataset) chart.getDataset());
				plot = factory.getPiePlot((JRPiePlot) chart.getPlot());
				break;
			case CHART_TYPE_PIE3D:
				dataset = (JRFillChartDataset) factory.getPieDataset((JRPieDataset) chart.getDataset());
				plot = factory.getPie3DPlot((JRPie3DPlot) chart.getPlot());
				break;
			case CHART_TYPE_SCATTER:
				dataset = (JRFillChartDataset) factory.getXyDataset((JRXyDataset) chart.getDataset());
				plot = factory.getScatterPlot((JRScatterPlot) chart.getPlot());
				break;
			case CHART_TYPE_STACKEDBAR:
				dataset = (JRFillChartDataset) factory.getCategoryDataset((JRCategoryDataset) chart.getDataset());
				plot = factory.getBarPlot((JRBarPlot) chart.getPlot());
				break;
			case CHART_TYPE_STACKEDBAR3D:
				dataset = (JRFillChartDataset) factory.getCategoryDataset((JRCategoryDataset) chart.getDataset());
				plot = factory.getBar3DPlot((JRBar3DPlot) chart.getPlot());
				break;
			case CHART_TYPE_THERMOMETER:
				dataset = (JRFillChartDataset) factory.getValueDataset((JRValueDataset) chart.getDataset());
				plot = factory.getThermometerPlot((JRThermometerPlot) chart.getPlot());
				break;
			case CHART_TYPE_TIMESERIES:
				dataset = (JRFillChartDataset) factory.getTimeSeriesDataset((JRTimeSeriesDataset)chart.getDataset());
				plot = factory.getTimeSeriesPlot((JRTimeSeriesPlot)chart.getPlot());
				break;
			case CHART_TYPE_XYAREA:
				dataset = (JRFillChartDataset) factory.getXyDataset((JRXyDataset) chart.getDataset());
				plot = factory.getAreaPlot((JRAreaPlot) chart.getPlot());
				break;
			case CHART_TYPE_XYBAR:
				switch (chart.getDataset().getDatasetType()){
					case JRChartDataset.TIMESERIES_DATASET:
						dataset = (JRFillChartDataset) factory.getTimeSeriesDataset( (JRTimeSeriesDataset)chart.getDataset() );
						break;
					case JRChartDataset.TIMEPERIOD_DATASET:
						dataset = (JRFillChartDataset) factory.getTimePeriodDataset((JRTimePeriodDataset) chart.getDataset() );
						break;
					case JRChartDataset.XY_DATASET:
						dataset = (JRFillChartDataset) factory.getXyDataset( (JRXyDataset)chart.getDataset() );
						break;
				}

				plot = factory.getBarPlot((JRBarPlot) chart.getPlot());
				break;
			case CHART_TYPE_XYLINE:
				dataset = (JRFillChartDataset) factory.getXyDataset((JRXyDataset) chart.getDataset());
				plot = factory.getLinePlot((JRLinePlot) chart.getPlot());
				break;
			case CHART_TYPE_STACKEDAREA:
				dataset = (JRFillChartDataset) factory.getCategoryDataset((JRCategoryDataset) chart.getDataset());
				plot = factory.getAreaPlot((JRAreaPlot) chart.getPlot());
				break;
			case CHART_TYPE_GANTT:
				dataset = (JRFillChartDataset) factory.getGanttDataset((JRGanttDataset) chart.getDataset());
				plot = factory.getBarPlot((JRBarPlot) chart.getPlot());
				break;
			default:
				throw 
					new JRRuntimeException(
						JRBaseChart.EXCEPTION_MESSAGE_KEY_CHART_TYPE_NOT_SUPPORTED,  
						new Object[]{getChartType()} 
						);
		}

		titleFont = factory.getFont(chart, chart.getTitleFont());
		subtitleFont = factory.getFont(chart, chart.getSubtitleFont());
		//FIXME this is inconsistent with the lines above
		legendFont =  factory.getFont(this, chart.getLegendFont());
		
		initLineBox = chart.getLineBox().clone(this);

		evaluationGroup = factory.getGroup(chart.getEvaluationGroup());

		chartCustomizers = new ArrayList<JRChartCustomizer>();
		JRChartCustomizer chartCustomizer = createAndInitCustomizer(chart.getCustomizerClass(), null);
		if (chartCustomizer != null)
		{
			chartCustomizers.add(chartCustomizer);
		}

		List<JRPropertiesUtil.PropertySuffix> properties = 
			JRPropertiesUtil.getProperties(
				chart.getPropertiesMap(), 
				NamedChartCustomizer.CUSTOMIZER_CLASS_PROPERTY_PREFIX
				);
		for (JRPropertiesUtil.PropertySuffix prop : properties) 
		{
			chartCustomizer = createAndInitCustomizer(prop.getValue(), prop.getSuffix());
			if (chartCustomizer != null)
			{
				chartCustomizers.add(chartCustomizer);
			}
		}
		
		renderType = chart.getRenderType();
		if(renderType == null)
		{
			renderType = filler.getPropertiesUtil().getProperty(getParentProperties(), JRChart.PROPERTY_CHART_RENDER_TYPE);
		}
		
		themeName = chart.getTheme();
		if(themeName == null)
		{
			themeName = filler.getPropertiesUtil().getProperty(getParentProperties(), JRChart.PROPERTY_CHART_THEME);
		}
	}

	/**
	 *
	 */
	protected JRChartCustomizer createAndInitCustomizer(
		String customizerClassName,
		String customizerName
		)
	{
		JRChartCustomizer customizer = null;

		if (customizerClassName != null && customizerClassName.length() > 0) 
		{
			try 
			{
				Class<?> customizerClass = JRClassLoader.loadClassForName(customizerClassName);
				customizer = (JRChartCustomizer) customizerClass.newInstance();
			}
			catch (Exception e) 
			{
				throw 
					new JRRuntimeException(
						EXCEPTION_MESSAGE_KEY_CUSTOMIZER_INSTANCE_ERROR,
						(Object[])null,
						e);
			}

			if (customizer instanceof JRAbstractChartCustomizer)
			{
				((JRAbstractChartCustomizer) customizer).init(this);
			}

			if (customizer instanceof NamedChartCustomizer)
			{
				((NamedChartCustomizer) customizer).setName(customizerName);
			}
		}
		
		return customizer;
	}

	@Override
	public void setBand(JRFillBand band)
	{
		super.setBand(band);
		
		dataset.setBand(band);
		((JRFillChartPlot) plot).setBand(band);
	}


	@Override
	protected void evaluateStyle(
		byte evaluation
		) throws JRException
	{
		super.evaluateStyle(evaluation);

		lineBox = null;
		
		if (providerStyle != null)
		{
			lineBox = initLineBox.clone(this);
			StyleUtil.appendBox(lineBox, providerStyle.getLineBox());
		}
	}


	@Override
	public ModeEnum getModeValue()
	{
		return getStyleResolver().getMode(this, ModeEnum.TRANSPARENT);
	}

	@Override
	public Boolean getShowLegend()
	{
		return ((JRChart)parent).getShowLegend();
	}

	@Override
	public void setShowLegend(Boolean isShowLegend)
	{
	}

	@Override
	public String getRenderType()
	{
		return renderType;
	}

	@Override
	public void setRenderType(String renderType)
	{
	}

	@Override
	public String getTheme()
	{
		return themeName;
	}

	@Override
	public void setTheme(String theme)
	{
	}

	@Override
	public EvaluationTimeEnum getEvaluationTimeValue()
	{
		return ((JRChart)parent).getEvaluationTimeValue();
	}

	@Override
	public JRGroup getEvaluationGroup()
	{
		return evaluationGroup;
	}

	@Override
	public JRLineBox getLineBox()
	{
		return lineBox == null ? initLineBox : lineBox;
	}

	@Override
	public JRFont getTitleFont()
	{
		return titleFont;
	}

	@Override
	public EdgeEnum getTitlePositionValue()
	{
		return ((JRChart)parent).getTitlePositionValue();
	}

	@Override
	public void setTitlePosition(EdgeEnum titlePosition)
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public Color getTitleColor()
	{
		return getStyleResolver().getTitleColor(this);
	}

	@Override
	public Color getOwnTitleColor()
	{
		return ((JRChart)parent).getOwnTitleColor();
	}

	@Override
	public void setTitleColor(Color titleColor)
	{
	}

	@Override
	public JRFont getSubtitleFont()
	{
		return subtitleFont;
	}

	@Override
	public Color getOwnSubtitleColor()
	{
		return ((JRChart)parent).getOwnSubtitleColor();
	}

	@Override
	public Color getSubtitleColor()
	{
		return getStyleResolver().getSubtitleColor(this);
	}

	@Override
	public void setSubtitleColor(Color subtitleColor)
	{
	}

	/**
	 * Returns the color to use for text in the legend.
	 *
	 * @return the color to use for text in the legend
	 */
	@Override
	public Color getOwnLegendColor()
	{
		return ((JRChart)parent).getOwnLegendColor();
	}

	/**
	 * Returns the inherited color to use for text in the legend.
	 *
	 * @return the color to use for text in the legend
	 */
	@Override
	public Color getLegendColor()
	{
		return getStyleResolver().getLegendColor(this);
	}

	/**
	 * Sets the color to use for text in the legend.
	 *
	 * @param legendColor the color to use for text in the legend
	 */
	@Override
	public void setLegendColor(Color legendColor)
	{
	}

	/**
	 * Returns the color to use as the background of the legend.
	 *
	 * @return the color to use as the background of the legend
	 */
	@Override
	public Color getOwnLegendBackgroundColor()
	{
		return ((JRChart)parent).getOwnLegendBackgroundColor();
	}

	/**
	 * Returns the color to use as the background of the legend.
	 *
	 * @return the color to use as the background of the legend
	 */
	@Override
	public Color getLegendBackgroundColor()
	{
		return getStyleResolver().getLegendBackgroundColor(this);
	}

	/**
	 * Sets the color to use for the background of the legend.
	 *
	 * @param legendBackgroundColor the color to use for the background of the legend
	 */
	@Override
	public void setLegendBackgroundColor(Color legendBackgroundColor)
	{
	}

	/**
	 * Returns the font to use in the legend.
	 *
	 * @return the font to use in the legend
	 */
	@Override
	public JRFont getLegendFont()
	{
		return legendFont;
	}

	@Override
	public EdgeEnum getLegendPositionValue()
	{
		return ((JRChart)parent).getLegendPositionValue();
	}

	@Override
	public void setLegendPosition(EdgeEnum legendPosition)
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public JRExpression getTitleExpression()
	{
		return ((JRChart)parent).getTitleExpression();
	}

	@Override
	public JRExpression getSubtitleExpression()
	{
		return ((JRChart)parent).getSubtitleExpression();
	}

	/**
	 * @deprecated Replaced by {@link #getHyperlinkTypeValue()}.
	 */
	public byte getHyperlinkType()
	{
		return getHyperlinkTypeValue().getValue();
	}

	@Override
	public HyperlinkTypeEnum getHyperlinkTypeValue()
	{
		return ((JRChart)parent).getHyperlinkTypeValue();
	}

	@Override
	public byte getHyperlinkTarget()
	{
		return ((JRChart)parent).getHyperlinkTarget();
	}

	@Override
	public JRExpression getAnchorNameExpression()
	{
		return ((JRChart)parent).getAnchorNameExpression();
	}

	@Override
	public JRExpression getHyperlinkReferenceExpression()
	{
		return ((JRChart)parent).getHyperlinkReferenceExpression();
	}

	@Override
	public JRExpression getHyperlinkWhenExpression()
	{
		return ((JRChart)parent).getHyperlinkWhenExpression();
	}

	@Override
	public JRExpression getHyperlinkAnchorExpression()
	{
		return ((JRChart)parent).getHyperlinkAnchorExpression();
	}

	@Override
	public JRExpression getHyperlinkPageExpression()
	{
		return ((JRChart)parent).getHyperlinkPageExpression();
	}


	/**
	 *
	 */
	public Locale getLocale()
	{
		return filler.getLocale();
	}

	public TimeZone getTimeZone()
	{
		return filler.getTimeZone();
	}

	@Override
	public JRChartDataset getDataset()
	{
		return dataset;
	}

	/**
	 *
	 */
	public void setDataset(JRFillChartDataset dataset)
	{
		this.dataset = dataset;
	}

	@Override
	public JRChartPlot getPlot()
	{
		return plot;
	}

	/**
	 *
	 */
	protected Renderable getRenderable()
	{
		return renderer;
	}

	/**
	 *
	 */
	protected String getAnchorName()
	{
		return anchorName;
	}

	/**
	 *
	 */
	protected String getHyperlinkReference()
	{
		return hyperlinkReference;
	}

	/**
	 *
	 */
	protected String getHyperlinkAnchor()
	{
		return hyperlinkAnchor;
	}

	/**
	 *
	 */
	protected Integer getHyperlinkPage()
	{
		return hyperlinkPage;
	}

	protected String getHyperlinkTooltip()
	{
		return hyperlinkTooltip;
	}

	@Override
	public Color getDefaultLineColor() 
	{
		return getForecolor();
	}


	/**
	 *
	 */
	protected JRTemplateImage getJRTemplateImage()
	{
		return (JRTemplateImage) getElementTemplate();
	}

	@Override
	protected JRTemplateElement createElementTemplate()
	{
		return new JRTemplateImage(getElementOrigin(), 
				filler.getJasperPrint().getDefaultStyleProvider(), this);
	}


	@Override
	protected void rewind()
	{
	}


	@Override
	protected void evaluate(byte evaluation) throws JRException
	{
		reset();

		evaluatePrintWhenExpression(evaluation);

		if (isPrintWhenExpressionNull() || isPrintWhenTrue())
		{
			if (getEvaluationTimeValue() == EvaluationTimeEnum.NOW)
			{
				evaluateRenderer(evaluation);
			}
		}
	}


	/**
	 *
	 */
	protected void evaluateRenderer(byte evaluation) throws JRException
	{
		JFreeChart chart = evaluateChart(evaluation);
		
		Rectangle2D rectangle = new Rectangle2D.Double(0,0,getWidth(),getHeight());

		renderer = 
			ChartUtil.getInstance(filler.getJasperReportsContext()).getChartRenderableFactory(getRenderType()).getRenderable(
				filler.getJasperReportsContext(),
				chart, 
				chartHyperlinkProvider,
				rectangle
				);
	}
	
	protected ChartHyperlinkProvider getHyperlinkProvider()
	{
		return chartHyperlinkProvider;
	}
	
	/**
	 *
	 */
	protected JFreeChart evaluateChart(byte evaluation) throws JRException
	{
		evaluateProperties(evaluation);
		evaluateDatasetRun(evaluation);
		evaluateStyle(evaluation);

		ChartTheme theme = ChartUtil.getInstance(filler.getJasperReportsContext()).getTheme(themeName);
		
		if (getChartType() == JRChart.CHART_TYPE_MULTI_AXIS)
		{
			createMultiAxisChart(evaluation);
		}
		else
		{
			jfreeChart = theme.createChart(new FillChartContext(evaluation));

			chartHyperlinkProvider = createChartHyperlinkProvider();
		}

		for (JRChartCustomizer chartCustomizer : chartCustomizers)
		{
			chartCustomizer.customize(jfreeChart, this);
		}

		anchorName = (String) evaluateExpression(getAnchorNameExpression(), evaluation);
		hyperlinkReference = (String) evaluateExpression(getHyperlinkReferenceExpression(), evaluation);
		hyperlinkWhen = (Boolean) evaluateExpression(getHyperlinkWhenExpression(), evaluation);
		hyperlinkAnchor = (String) evaluateExpression(getHyperlinkAnchorExpression(), evaluation);
		hyperlinkPage = (Integer) evaluateExpression(getHyperlinkPageExpression(), evaluation);
		hyperlinkTooltip = (String) evaluateExpression(getHyperlinkTooltipExpression(), evaluation);
		hyperlinkParameters = JRFillHyperlinkHelper.evaluateHyperlinkParameters(this, expressionEvaluator, evaluation);

		return jfreeChart;
	}


	@Override
	protected boolean prepare(
		int availableHeight,
		boolean isOverflow
		)
	{
		boolean willOverflow = false;

		if (
			isPrintWhenExpressionNull() ||
			( !isPrintWhenExpressionNull() &&
			isPrintWhenTrue() )
			)
		{
			setToPrint(true);
		}
		else
		{
			setToPrint(false);
		}

		if (!isToPrint())
		{
			return willOverflow;
		}

		boolean isToPrint = true;
		boolean isReprinted = false;

		if (getEvaluationTimeValue() == EvaluationTimeEnum.NOW)
		{
			if (isOverflow && isAlreadyPrinted() && !isPrintWhenDetailOverflows())
			{
				isToPrint = false;
			}

			if (
				isToPrint &&
				availableHeight < getRelativeY() + getHeight()
				)
			{
				isToPrint = false;
				willOverflow = true;
			}

			if (
				isToPrint &&
				isOverflow &&
				//(this.isAlreadyPrinted() || !this.isPrintRepeatedValues())
				(isPrintWhenDetailOverflows() && (isAlreadyPrinted() || (!isAlreadyPrinted() && !isPrintRepeatedValues())))
				)
			{
				isReprinted = true;
			}

			if (
				isToPrint &&
				isRemoveLineWhenBlank() &&
				getRenderable() == null
				)
			{
				isToPrint = false;
			}
		}
		else
		{
			if (isOverflow && isAlreadyPrinted() && !isPrintWhenDetailOverflows())
			{
				isToPrint = false;
			}

			if (
				isToPrint &&
				availableHeight < getRelativeY() + getHeight()
				)
			{
				isToPrint = false;
				willOverflow = true;
			}

			if (
				isToPrint &&
				isOverflow &&
				//(this.isAlreadyPrinted() || !this.isPrintRepeatedValues())
				(isPrintWhenDetailOverflows() && (isAlreadyPrinted() || (!isAlreadyPrinted() && !isPrintRepeatedValues())))
				)
			{
				isReprinted = true;
			}
		}

		setToPrint(isToPrint);
		setReprinted(isReprinted);

		return willOverflow;
	}


	@Override
	protected JRPrintElement fill()
	{
		JRTemplatePrintImage printImage = new JRTemplatePrintImage(getJRTemplateImage(), printElementOriginator);

		printImage.setUUID(getUUID());
		printImage.setX(getX());
		printImage.setY(getRelativeY());
		printImage.setWidth(getWidth());
		printImage.setHeight(getStretchHeight());
		printImage.setBookmarkLevel(getBookmarkLevel());
		printImage.getPropertiesMap().setProperty(HtmlReportConfiguration.PROPERTY_EMBED_IMAGE, Boolean.TRUE.toString());
		printImage.getPropertiesMap().setProperty(HtmlReportConfiguration.PROPERTY_EMBEDDED_SVG_USE_FONTS, Boolean.TRUE.toString());

		EvaluationTimeEnum evaluationTime = getEvaluationTimeValue();
		if (evaluationTime == EvaluationTimeEnum.NOW)
		{
			copy(printImage);
		}
		else
		{
			filler.addBoundElement(this, printImage, evaluationTime, getEvaluationGroup(), band);
		}

		return printImage;
	}


	/**
	 *
	 */
	protected void copy(JRPrintImage printImage)
	{
		printImage.setRenderer(getRenderable());
		printImage.setAnchorName(getAnchorName());
		if (getHyperlinkWhenExpression() == null || Boolean.TRUE.equals(hyperlinkWhen))
		{
			printImage.setHyperlinkReference(getHyperlinkReference());
			printImage.setHyperlinkAnchor(getHyperlinkAnchor());
			printImage.setHyperlinkPage(getHyperlinkPage());
			printImage.setHyperlinkTooltip(getHyperlinkTooltip());
			printImage.setHyperlinkParameters(hyperlinkParameters);
		}
		else
		{
			if (printImage instanceof JRTemplatePrintImage)//this is normally the case
			{
				((JRTemplatePrintImage) printImage).setHyperlinkOmitted(true);
			}
			
			printImage.setHyperlinkReference(null);
		}
		transferProperties(printImage);
	}

	@Override
	public byte getChartType()
	{
		return chartType;
	}


	@Override
	public void collectExpressions(JRExpressionCollector collector)
	{
		collector.collect(this);
	}


	@Override
	public void visit(JRVisitor visitor)
	{
		visitor.visitChart(this);
	}

	
	/**
	 *
	 */
	protected ChartHyperlinkProvider createChartHyperlinkProvider()
	{
		ChartHyperlinkProvider chartHyperlinkProvider = null;
		
		switch(getChartType()) 
		{
			case JRChart.CHART_TYPE_AREA:
			case JRChart.CHART_TYPE_BAR:
			case JRChart.CHART_TYPE_BAR3D:
			case JRChart.CHART_TYPE_LINE:
			case JRChart.CHART_TYPE_STACKEDBAR3D:
			case JRChart.CHART_TYPE_STACKEDBAR:
			case JRChart.CHART_TYPE_STACKEDAREA:
				chartHyperlinkProvider = new CategoryChartHyperlinkProvider(((JRFillCategoryDataset)getDataset()).getItemHyperlinks());
				break;
			case JRChart.CHART_TYPE_BUBBLE:
				chartHyperlinkProvider = new XYChartHyperlinkProvider(((JRFillXyzDataset)getDataset()).getItemHyperlinks());
				break;
			case JRChart.CHART_TYPE_SCATTER:
			case JRChart.CHART_TYPE_XYAREA:
			case JRChart.CHART_TYPE_XYLINE:
				chartHyperlinkProvider = new XYChartHyperlinkProvider(((JRFillXyDataset)getDataset()).getItemHyperlinks());
				break;
			case JRChart.CHART_TYPE_CANDLESTICK:
			case JRChart.CHART_TYPE_HIGHLOW:
				chartHyperlinkProvider = new HighLowChartHyperlinkProvider(((JRFillHighLowDataset)getDataset()).getItemHyperlinks());
				break;
			case JRChart.CHART_TYPE_MULTI_AXIS:
				//multi-axis charts are dealt with in createMultiAxisChart
				break;
			case JRChart.CHART_TYPE_PIE:
			case JRChart.CHART_TYPE_PIE3D:
				chartHyperlinkProvider = new PieChartHyperlinkProvider(((JRFillPieDataset)getDataset()).getSectionHyperlinks());
				break;
			case JRChart.CHART_TYPE_TIMESERIES:
				chartHyperlinkProvider = new TimeSeriesChartHyperlinkProvider(((JRFillTimeSeriesDataset)getDataset()).getItemHyperlinks());
				break;
			case JRChart.CHART_TYPE_XYBAR:
				switch(getDataset().getDatasetType())
				{
					case JRChartDataset.TIMESERIES_DATASET :
						chartHyperlinkProvider = new TimeSeriesChartHyperlinkProvider(((JRFillTimeSeriesDataset)getDataset()).getItemHyperlinks());
						break;
					case JRChartDataset.TIMEPERIOD_DATASET :
						chartHyperlinkProvider = new TimePeriodChartHyperlinkProvider(((JRFillTimePeriodDataset)getDataset()).getItemHyperlinks());
						break;
					case JRChartDataset.XY_DATASET :
						chartHyperlinkProvider = new XYChartHyperlinkProvider(((JRFillXyDataset)getDataset()).getItemHyperlinks());
						break;
					default:
						break;
				}
				break;
			case JRChart.CHART_TYPE_GANTT:
				chartHyperlinkProvider = new XYChartHyperlinkProvider(((JRFillGanttDataset)getDataset()).getItemHyperlinks());
				break;
			case JRChart.CHART_TYPE_METER:
			case JRChart.CHART_TYPE_THERMOMETER:
				//no item hyperlinks
				break;
			default:
				throw 
					new JRRuntimeException(
						JRBaseChart.EXCEPTION_MESSAGE_KEY_CHART_TYPE_NOT_SUPPORTED,  
						new Object[]{getChartType()} 
						);
		}

		return chartHyperlinkProvider;
	}



	/**
	 * Build and configure a multiple axis chart.  A multiple axis chart support more than
	 * one range axis.  Multiple datasets using different ranges can be displayed as long as
	 * they share a common domain axis.  Each dataset can be rendered differently, so one chart
	 * can contain (for example) two line charts, a bar chart and an area chart.
	 * <br><br>
	 * Multiple axis charts are handled differently than the other chart types.  They do not
	 * have a dataset, as each chart that is added to the multiple axis chart has its own
	 * dataset.  For simplicity, each dataset is treated as its own chart, and in fact we
	 * reuse the design of all the chart types and let JFreeChart actually run them.  Then
	 * we pull out the bits we need and add it to the common chart.  All the plot and chart
	 * options on the nested charts is ignored, and all formatting is controlled by the plot
	 * attached to the multiAxisChart.  The one exception is seriesColor, which can be used in
	 * a nested report to specify a color for a specific series in that report.
	 *
	 * @param evaluation current expression evaluation phase
	 * @throws JRException
	 */
	protected void createMultiAxisChart(byte evaluation) throws JRException
	{
		// A multi axis chart has to have at least one axis and chart specified.
		// Create the first axis as the base plot, and then go ahead and create the
		// charts for any additional axes.  Just take the renderer and data series
		// from those charts and add them to the first one.
		Plot mainPlot = null;

		JRFillMultiAxisPlot jrPlot = (JRFillMultiAxisPlot)getPlot();
		
		// create a multi axis hyperlink provider
		MultiAxisChartHyperlinkProvider multiHyperlinkProvider = new MultiAxisChartHyperlinkProvider();
		
		// Generate the main plot from the first axes specified.
		Iterator<JRChartAxis> iter = jrPlot.getAxes().iterator();
		if (iter.hasNext())
		{
			JRFillChartAxis axis = (JRFillChartAxis)iter.next();
			JRFillChart fillChart = axis.getFillChart();
			
			//a JFreeChart object should be obtained first; the rendering type should be always "vector"
			

			jfreeChart = fillChart.evaluateChart(evaluation);
			//FIXME honor printWhenExpression
			// Override the plot from the first axis with the plot for the multi-axis
			// chart.
			//FIXME is the above comment true? 
			//configureChart(jfreeChart, getPlot(), evaluation);
			mainPlot = jfreeChart.getPlot();
			ChartHyperlinkProvider axisHyperlinkProvider = fillChart.getHyperlinkProvider();

			if (mainPlot instanceof CategoryPlot)
			{
				CategoryPlot categoryPlot = (CategoryPlot) mainPlot;
				categoryPlot.setRangeAxisLocation(0, getChartAxisLocation(axis));
				if (axisHyperlinkProvider != null)
				{
					multiHyperlinkProvider.addHyperlinkProvider(categoryPlot.getDataset(), 
							axisHyperlinkProvider);
				}
			}
			else if (mainPlot instanceof XYPlot)
			{
				XYPlot xyPlot = (XYPlot) mainPlot;
				xyPlot.setRangeAxisLocation(0, getChartAxisLocation(axis));
				if (axisHyperlinkProvider != null)
				{
					multiHyperlinkProvider.addHyperlinkProvider(xyPlot.getDataset(), axisHyperlinkProvider);
				}
			}
		 }

		// Now handle all the extra axes, if any.
		int axisNumber = 0;
		while (iter.hasNext())
		{			
			JRFillChartAxis chartAxis = (JRFillChartAxis)iter.next();
			JRFillChart fillChart = chartAxis.getFillChart();

			fillChart.evaluatePrintWhenExpression(evaluation);
			if (!(fillChart.isPrintWhenExpressionNull() || fillChart.isPrintWhenTrue()))
			{
				continue;
			}

			axisNumber++;
			JFreeChart axisChart = fillChart.evaluateChart(evaluation);
			ChartHyperlinkProvider axisHyperlinkProvider = fillChart.getHyperlinkProvider();

			// In JFreeChart to add a second chart type to an existing chart
			// you need to add an axis, a data series and a renderer.  To
			// leverage existing code we simply create a new chart for the
			// axis and then pull out the bits we need and add them to the multi
			// chart.  Currently JFree only supports category plots and xy plots
			// in a multi-axis chart, and you can not mix the two.
			if (mainPlot instanceof CategoryPlot)
			{
				CategoryPlot mainCatPlot = (CategoryPlot)mainPlot;
				if (!(axisChart.getPlot() instanceof CategoryPlot))
				{
					throw 
						new JRException(
							EXCEPTION_MESSAGE_KEY_MULTIAXIS_PLOT_TYPES_MIX_NOT_ALLOWED,  
							(Object[])null 
							);
				}

				// Get the axis and add it to the multi axis chart plot
				CategoryPlot axisPlot = (CategoryPlot)axisChart.getPlot();
				mainCatPlot.setRangeAxis(axisNumber, axisPlot.getRangeAxis());
				mainCatPlot.setRangeAxisLocation(axisNumber, getChartAxisLocation(chartAxis));

				// Add the data set and map it to the recently added axis
				mainCatPlot.setDataset(axisNumber, axisPlot.getDataset());
				mainCatPlot.mapDatasetToRangeAxis(axisNumber, axisNumber);

				// Set the renderer to use to draw the dataset.
				mainCatPlot.setRenderer(axisNumber, axisPlot.getRenderer());

				// Handle any color series for this chart
				configureAxisSeriesColors(axisPlot.getRenderer(), fillChart.getPlot());
				
				if (axisHyperlinkProvider != null)
				{
					multiHyperlinkProvider.addHyperlinkProvider(axisPlot.getDataset(), 
							axisHyperlinkProvider);
				}
			}
			else if (mainPlot instanceof XYPlot)
			{
				XYPlot mainXyPlot = (XYPlot)mainPlot;
				if (!(axisChart.getPlot() instanceof XYPlot))
				{
					throw 
						new JRException(
							EXCEPTION_MESSAGE_KEY_MULTIAXIS_PLOT_TYPES_MIX_NOT_ALLOWED,  
							(Object[])null 
							);
				}

				// Get the axis and add it to the multi axis chart plot
				XYPlot axisPlot = (XYPlot)axisChart.getPlot();
				mainXyPlot.setRangeAxis(axisNumber, axisPlot.getRangeAxis());
				mainXyPlot.setRangeAxisLocation(axisNumber, getChartAxisLocation(chartAxis));

				// Add the data set and map it to the recently added axis
				mainXyPlot.setDataset(axisNumber, axisPlot.getDataset());
				mainXyPlot.mapDatasetToRangeAxis(axisNumber, axisNumber);

				// Set the renderer to use to draw the dataset.
				mainXyPlot.setRenderer(axisNumber, axisPlot.getRenderer());

				// Handle any color series for this chart
				configureAxisSeriesColors(axisPlot.getRenderer(), fillChart.getPlot());
				
				if (axisHyperlinkProvider != null)
				{
					multiHyperlinkProvider.addHyperlinkProvider(axisPlot.getDataset(), 
							axisHyperlinkProvider);
				}
			}
			else
			{
				throw 
					new JRException(
						EXCEPTION_MESSAGE_KEY_MULTIAXIS_PLOT_NOT_SUPPORTED,  
						(Object[])null 
						);
			}
		}

		//set the multi hyperlink provider
		chartHyperlinkProvider = multiHyperlinkProvider;
	}


	/**
	 * The series colors set in the main plot of a multiple axis chart are used for
	 * all the rendered charts in the plot.  This is a problem with multiple line
	 * charts, using different scales and thus different axis.  All the lines will
	 * be drawn using the first series color (since they are the first series for that
	 * rendered) and it will be impossible to tell them apart.
	 * <br><br>
	 * For this reason we interpret series colors for charts included in a multiple
	 * axis chart as specify absolute series colors for that renderer.
	 *
	 * @param renderer the renderer of the chart being created
	 * @param jrPlot the Jasper view of that plot
	 */
	private void configureAxisSeriesColors(CategoryItemRenderer renderer, JRChartPlot jrPlot)
	{
		SortedSet<JRSeriesColor> seriesColors = jrPlot.getSeriesColors();

		if (seriesColors != null)
		{
			Iterator<JRSeriesColor> iter = seriesColors.iterator();
			while (iter.hasNext())
			{
				JRSeriesColor seriesColor = iter.next();
				renderer.setSeriesPaint(seriesColor.getSeriesOrder(), seriesColor.getColor());
			}
		}
	}

	/**
	 * The series colors set in the main plot of a multiple axis chart are used for
	 * all the rendered charts in the plot.  This is a problem with multiple line
	 * charts, using different scales and thus different axis.  All the lines will
	 * be drawn using the first series color (since they are the first series for that
	 * rendered) and it will be impossible to tell them apart.
	 * <br>
	 * For this reason we interpret series colors for charts included in a multiple
	 * axis chart as specify absolute series colors for that renderer.
	 *
	 * @param renderer the renderer of the chart being created
	 * @param jrPlot the Jasper view of that plot
	 */
	private void configureAxisSeriesColors(XYItemRenderer renderer, JRChartPlot jrPlot)
	{
		SortedSet<JRSeriesColor> seriesColors = jrPlot.getSeriesColors();

		if (seriesColors != null)
		{
			Iterator<JRSeriesColor> iter = seriesColors.iterator();
			while (iter.hasNext())
			{
				JRSeriesColor seriesColor = iter.next();
				renderer.setSeriesPaint(seriesColor.getSeriesOrder(), seriesColor.getColor());
			}
		}
	}

	/**
	 * Converts a JasperReport data range into one understood by JFreeChart.
	 *
	 * @param dataRange the JasperReport version of the range
	 * @param evaluation current expression evaluation phase
	 * @return the JFreeChart version of the range
	 * @throws JRException thrown when the low value of the range is greater than the
	 * 						high value
	 */
	protected Range convertRange(JRDataRange dataRange, byte evaluation) throws JRException
	{
		if (dataRange == null)
		{
			return null;
		}
		Number low = (Number)evaluateExpression(dataRange.getLowExpression(), evaluation);
		Number high = (Number)evaluateExpression(dataRange.getHighExpression(), evaluation);
		return new Range( low != null ? low.doubleValue() : 0.0,
								 high != null ? high.doubleValue() : 100.0);
	}

	/**
	 * Converts a JasperReports meter interval to one that JFreeChart understands.
	 *
	 * @param interval the JasperReports definition of an interval
	 * @param evaluation current evaluation time
	 * @return the JFreeChart version of the same interval
	 * @throws JRException thrown when the interval contains an invalid range
	 */
	protected MeterInterval convertInterval(JRMeterInterval interval, byte evaluation) throws JRException
	{
		String label = interval.getLabel();
		if (label == null)
		{
			label = "";
		}

		Range range = convertRange(interval.getDataRange(), evaluation);

		Color color = interval.getBackgroundColor();
		float[] components = color.getRGBColorComponents(null);
		float alpha = interval.getAlphaDouble() == null ? (float)JRMeterInterval.DEFAULT_TRANSPARENCY : interval.getAlphaDouble().floatValue();

		Color alphaColor = new Color(components[0], components[1], components[2], alpha);

		return new MeterInterval(label, range, alphaColor, null, alphaColor);
	}

	protected AxisLocation getChartAxisLocation(JRFillChartAxis chartAxis)
	{
		return chartAxis.getPositionValue() != null && chartAxis.getPositionValue() == AxisPositionEnum.RIGHT_OR_BOTTOM
				? AxisLocation.BOTTOM_OR_RIGHT 
				: AxisLocation.TOP_OR_LEFT;
	}
	
	@Override
	protected void resolveElement(JRPrintElement element, byte evaluation) throws JRException
	{
		evaluateRenderer(evaluation);

		copy((JRPrintImage) element);
		filler.updateBookmark(element);
	}


	@Override
	public int getBookmarkLevel()
	{
		return ((JRChart)parent).getBookmarkLevel();
	}

	@Override
	public String getCustomizerClass()
	{
		return ((JRChart)parent).getCustomizerClass();
	}


	private void evaluateDatasetRun(byte evaluation) throws JRException
	{
		dataset.evaluateDatasetRun(evaluation);
	}


	@Override
	public JRFillCloneable createClone(JRFillCloneFactory factory)
	{
		//not needed
		return null;
	}


	@Override
	public JRHyperlinkParameter[] getHyperlinkParameters()
	{
		return ((JRChart) parent).getHyperlinkParameters();
	}


	@Override
	public String getLinkType()
	{
		return ((JRChart) parent).getLinkType();
	}

	@Override
	public String getLinkTarget()
	{
		return ((JRChart) parent).getLinkTarget();
	}


	@Override
	public JRExpression getHyperlinkTooltipExpression()
	{
		return ((JRChart) parent).getHyperlinkTooltipExpression();
	}

	class FillChartContext implements ChartContext
	{
		private final byte evaluation; 
		
		protected FillChartContext(byte evaluation)
		{
			this.evaluation = evaluation;
			
			JRFillChart.this.filler.getJasperReportsContext();
		}
		
		@Override
		public JasperReportsContext getJasperReportsContext()
		{
			return JRFillChart.this.filler.getJasperReportsContext();
		}
		
		@Override
		public String evaluateTextExpression(JRExpression expression) throws JRException {
			return JRStringUtil.getString(JRFillChart.this.evaluateExpression(expression, evaluation));
		}

		@Override
		public Object evaluateExpression(JRExpression expression) throws JRException {
			return JRFillChart.this.evaluateExpression(expression, evaluation);
		}

		@Override
		public JRChart getChart() {
			return JRFillChart.this;
		}

		@Override
		public Dataset getDataset() {
			return ((JRFillChartDataset)JRFillChart.this.getDataset()).getDataset();
		}

		@Override
		public Object getLabelGenerator() {
			return ((JRFillChartDataset)JRFillChart.this.getDataset()).getLabelGenerator();
		}

		@Override
		public Locale getLocale() {
			return JRFillChart.this.getLocale();
		}

		@Override
		public TimeZone getTimeZone() {
			return JRFillChart.this.getTimeZone();
		}
	}
}
