/***********************************************************************************************
 * File Info: $Id: ValueLabelRenderer.java,v 1.12 2003/04/19 13:45:45 nathaniel_auvil Exp $
 * Copyright (C) 2002
 * Author: Nathaniel G. Auvil, John Thomsen
 * Contributor(s):
 *
 * 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.axisChart.customRenderers.axisValue.renderers;


import org.jCharts.axisChart.AxisChart;
import org.jCharts.axisChart.customRenderers.axisValue.AxisValueRenderEvent;
import org.jCharts.axisChart.customRenderers.axisValue.PostAxisValueRenderListener;
import org.jCharts.chartData.interfaces.IAxisChartDataSet;
import org.jCharts.chartText.NumericTagGroup;
import org.jCharts.chartText.TextTag;
import org.jCharts.properties.util.ChartFont;

import java.awt.*;
import java.text.NumberFormat;


public class ValueLabelRenderer implements PostAxisValueRenderListener
{

	private NumberFormat numberFormat;

	private ChartFont valueChartFont = ChartFont.DEFAULT_AXIS_VALUE;

	//---holds the derived Font if needed so don't have to recalculate it each time.
	private Font derivedFont;

	//---vertical labels are only used when plotting on vertical charts; not used for horizontal bar plots.
	private boolean isLabelVertical = false;

	private ValueLabelPosition valueLabelPosition = ValueLabelPosition.ON_TOP;

	private int pixelValuePadding = 4;


	/**********************************************************************************
	 *
	 * @param isCurrency
	 * @param showGrouping
	 * @param roundingPowerOfTen
	 * @deprecated
	 **********************************************************************************/
	public ValueLabelRenderer( boolean isCurrency, boolean showGrouping, int roundingPowerOfTen )
	{
		this.numberFormat = NumericTagGroup.getNumberFormatInstance( isCurrency, false, showGrouping, roundingPowerOfTen );
	}


	/**********************************************************************************
	 *
	 * @param isCurrency
	 * @param isPercent
	 * @param showGrouping
	 * @param roundingPowerOfTen
	 **********************************************************************************/
	public ValueLabelRenderer( boolean isCurrency, boolean isPercent, boolean showGrouping, int roundingPowerOfTen )
	{
		this.numberFormat = NumericTagGroup.getNumberFormatInstance( isCurrency, isPercent, showGrouping, roundingPowerOfTen );
	}


	/************************************************************************************
	 * Sets where you would like to position the label
	 *
	 * @param valueLabelPosition
	 ***********************************************************************************/
	public void setValueLabelPosition( ValueLabelPosition valueLabelPosition )
	{
		this.valueLabelPosition = valueLabelPosition;
	}


	/************************************************************************************
	 *
	 * @param valueChartFont
	 ***********************************************************************************/
	public void setValueChartFont( ChartFont valueChartFont )
	{
		this.valueChartFont = valueChartFont;
	}


	/***********************************************************************************
	 *
	 * @param useVerticalLabels
	 **********************************************************************************/
	public void useVerticalLabels( boolean useVerticalLabels )
	{
		this.isLabelVertical = useVerticalLabels;

		//---set this here so can reuse same font
		if( this.isLabelVertical )
		{
			this.derivedFont = this.valueChartFont.deriveFont();
		}
	}


	/***********************************************************************************
	 * The pixel padding between the label and the data point.
	 *
	 * @param pixelValuePadding
	 **********************************************************************************/
	public void setPixelValuePadding( int pixelValuePadding )
	{
		this.pixelValuePadding = pixelValuePadding;
	}


	/***********************************************************************************
	 *
	 * @param axisValueRenderEvent
	 ***********************************************************************************/
	public void postRender( AxisValueRenderEvent axisValueRenderEvent )
	{
		AxisChart axisChart = (AxisChart) axisValueRenderEvent.getSource();
		TextTag valueTag;
		float x;
		float y;


		if( axisValueRenderEvent.getiAxisPlotDataSet() instanceof IAxisChartDataSet )
		{
			IAxisChartDataSet iAxisChartDataSet = (IAxisChartDataSet) axisValueRenderEvent.getiAxisPlotDataSet();
			double value = iAxisChartDataSet.getValue( axisValueRenderEvent.getDataSetIndex(), axisValueRenderEvent.getValueIndex() );

			valueTag = new TextTag( this.numberFormat.format( value ),
											this.valueChartFont.getFont(),
											this.derivedFont,
											axisValueRenderEvent.getFontRenderContext() );

			if( axisChart.getAxisProperties().isPlotHorizontal() )
			{
				x = this.calculateXHorizontalPlot( axisValueRenderEvent, valueTag, (value < 0) );
				y = this.calculateYHorizontalPlot( axisValueRenderEvent, valueTag );
			}
			else
			{
				x = this.calculateXVerticalPlot( axisValueRenderEvent, valueTag );
				y = this.calculateYVerticalPlot( axisValueRenderEvent, valueTag, (value < 0) );
			}
		}
		else
		{
//todo scatter and hi/low
			valueTag = null;
			x = 100;
			y = 100;

			throw new RuntimeException( "Axis Values not yet implemented for this type of chart." );
		}


/*
		Line2D.Float line= new Line2D.Float( x, y, x, y -20 );
		axisValueRenderEvent.getGraphics2D().draw( line );
*/

		valueTag.setXPosition( x );
		valueTag.setYPosition( y );
		valueTag.render( axisValueRenderEvent.getGraphics2D(), this.valueChartFont.getPaint() );
	}


	/*************************************************************************************************
	 * Calculates the label x so that the label is centered on the scale item.
	 *
	 * @param axisValueRenderEvent
	 * @param formattedTextTag
	 * @return float
	 ************************************************************************************************/
	private float calculateXVerticalPlot( AxisValueRenderEvent axisValueRenderEvent, TextTag formattedTextTag )
	{
		float x = axisValueRenderEvent.getValueX();

		if( this.isLabelVertical )
		{
			x += formattedTextTag.getFontDescent();
		}
		else
		{
			x -= (formattedTextTag.getWidth() / 2);
		}

		return x;
	}


	/*************************************************************************************************
	 *
	 * @param axisValueRenderEvent
	 * @param formattedTextTag
	 * @return float
	 ************************************************************************************************/
	private float calculateYHorizontalPlot( AxisValueRenderEvent axisValueRenderEvent,
														 TextTag formattedTextTag )
	{
		float y = axisValueRenderEvent.getValueY();

		if( this.isLabelVertical )
		{
			y += (formattedTextTag.getWidth() / 2);
		}
		else
		{
			y += formattedTextTag.getFontDescent();
		}

		return y;
	}


	/*************************************************************************************************
	 *
	 * @param axisValueRenderEvent
	 * @param formattedTextTag
	 * @param isNegative
	 * @return
	 ************************************************************************************************/
	private float calculateXHorizontalPlot( AxisValueRenderEvent axisValueRenderEvent,
														 TextTag formattedTextTag,
														 boolean isNegative )
	{
		float x = axisValueRenderEvent.getValueX();

		if( this.valueLabelPosition.equals( ValueLabelPosition.ON_TOP ) )
		{
			//---if the value is negative, 'top' is to the left
			if( isNegative )
			{
				x -= (this.isLabelVertical) ? 0 : formattedTextTag.getWidth();
				x -= this.pixelValuePadding;
			}
			else
			{
				x += (this.isLabelVertical) ? formattedTextTag.getFontAscent() : 0;
				x += this.pixelValuePadding;
			}
		}
		else if( this.valueLabelPosition.equals( ValueLabelPosition.AT_TOP ) )
		{
			if( isNegative )
			{
				x += (this.isLabelVertical) ? formattedTextTag.getFontAscent() : 0;
				x += this.pixelValuePadding;
			}
			else
			{
				x -= (this.isLabelVertical) ? formattedTextTag.getFontDescent() : formattedTextTag.getWidth();
				x -= this.pixelValuePadding;
			}
		}
		else if( this.valueLabelPosition.equals( ValueLabelPosition.ABOVE_ZERO_LINE ) )
		{
			x = axisValueRenderEvent.getZeroLineCoordinate();

			if( isNegative )
			{
				x += (this.isLabelVertical) ? formattedTextTag.getFontAscent() : 0;
				x += this.pixelValuePadding;
			}
			else
			{
				x -= (this.isLabelVertical) ? formattedTextTag.getFontDescent() : formattedTextTag.getWidth();
				x -= this.pixelValuePadding;
			}
		}


		else if( this.valueLabelPosition.equals( ValueLabelPosition.AXIS_TOP ) )
		{
			x = axisValueRenderEvent.getTotalItemAxisArea().x + axisValueRenderEvent.getTotalItemAxisArea().width;

			x -= (this.isLabelVertical) ? 0 : formattedTextTag.getWidth();
			x -= this.pixelValuePadding;
		}
		else if( this.valueLabelPosition.equals( ValueLabelPosition.AXIS_BOTTOM ) )
		{
			x = axisValueRenderEvent.getTotalItemAxisArea().x;
			x += (this.isLabelVertical) ? formattedTextTag.getFontAscent() : 0;
			x += this.pixelValuePadding;
		}


		//VALIDATION - force labels into plot area, in case there is a user defined scale.
//todo could we skip this validation for non-user defined scales?
		//---if label goes off the right edge, force it to stay in the plot area.
		if( (x + formattedTextTag.getWidth())
			> (axisValueRenderEvent.getTotalItemAxisArea().x + axisValueRenderEvent.getTotalItemAxisArea().width) )
		{
			x = axisValueRenderEvent.getTotalItemAxisArea().x + axisValueRenderEvent.getTotalItemAxisArea().width;
			x -= formattedTextTag.getWidth();
			x -= this.pixelValuePadding;
		}
		//---if label goes off left edge, force to the right
		else if( x < axisValueRenderEvent.getTotalItemAxisArea().x )
		{
			x = axisValueRenderEvent.getTotalItemAxisArea().x;
			x += this.pixelValuePadding;
		}

		return x;
	}


	/*************************************************************************************************
	 *
	 * @param axisValueRenderEvent
	 * @param formattedTextTag
	 * @param isNegative
	 * @return
	 ************************************************************************************************/
	private float calculateYVerticalPlot( AxisValueRenderEvent axisValueRenderEvent,
													  TextTag formattedTextTag,
													  boolean isNegative )
	{
		float y = axisValueRenderEvent.getValueY();

		if( this.valueLabelPosition.equals( ValueLabelPosition.ON_TOP ) )
		{
			//---if the value is negative, 'top' is to the bottom
			if( isNegative )
			{
				y += (this.isLabelVertical) ? formattedTextTag.getWidth() : formattedTextTag.getHeight();
				y += this.pixelValuePadding;
			}
			else
			{
				y -= this.pixelValuePadding;
			}
		}
		else if( this.valueLabelPosition.equals( ValueLabelPosition.AT_TOP ) )
		{
			//---if the value is negative, 'top' is to the bottom
			if( isNegative )
			{
				y -= this.pixelValuePadding;
			}
			else
			{
				y += (this.isLabelVertical) ? formattedTextTag.getWidth() : formattedTextTag.getHeight();
				y += this.pixelValuePadding;
			}
		}
		else if( this.valueLabelPosition.equals( ValueLabelPosition.ABOVE_ZERO_LINE ) )
		{
			y = axisValueRenderEvent.getZeroLineCoordinate();

			//---if the value is negative, 'top' is to the bottom
			if( isNegative )
			{
				y -= this.pixelValuePadding;
			}
			else
			{
				y += (this.isLabelVertical) ? formattedTextTag.getWidth() : formattedTextTag.getHeight();
				y += this.pixelValuePadding;
			}
		}

		else if( this.valueLabelPosition.equals( ValueLabelPosition.AXIS_TOP ) )
		{
			y = axisValueRenderEvent.getTotalItemAxisArea().y;
			y += (this.isLabelVertical) ? formattedTextTag.getWidth() : formattedTextTag.getHeight();
			y += this.pixelValuePadding;
		}
		else if( this.valueLabelPosition.equals( ValueLabelPosition.AXIS_BOTTOM ) )
		{
			y = axisValueRenderEvent.getTotalItemAxisArea().y + axisValueRenderEvent.getTotalItemAxisArea().height;
			y -= this.pixelValuePadding;
		}



		//VALIDATION - force labels into plot area, in case there is a user defined scale.
		if( isLabelVertical )
		{
			//---if label goes off the bottom edge, force it to stay in the plot area.
			if( ( y - formattedTextTag.getWidth() ) < axisValueRenderEvent.getTotalItemAxisArea().y )
			{
				y = axisValueRenderEvent.getTotalItemAxisArea().y;
				y += formattedTextTag.getWidth();
				y += this.pixelValuePadding;
			}
			//---if label goes off bottom edge, force to the up
			else if( y > axisValueRenderEvent.getTotalItemAxisArea().y + axisValueRenderEvent.getTotalItemAxisArea().height )
			{
				y = axisValueRenderEvent.getTotalItemAxisArea().y + axisValueRenderEvent.getTotalItemAxisArea().height;
				y -= this.pixelValuePadding;
			}
		}
		else
		{
			//---if label goes off the top edge, force it to stay in the plot area.
			if( ( y - formattedTextTag.getHeight() ) < axisValueRenderEvent.getTotalItemAxisArea().y )
			{
				y = axisValueRenderEvent.getTotalItemAxisArea().y;
				y += formattedTextTag.getHeight();
				y += this.pixelValuePadding;
			}
			//---if label goes off bottom edge, force to the up
			else if( y > axisValueRenderEvent.getTotalItemAxisArea().y + axisValueRenderEvent.getTotalItemAxisArea().height )
			{
				y = axisValueRenderEvent.getTotalItemAxisArea().y + axisValueRenderEvent.getTotalItemAxisArea().height;
				y -= this.pixelValuePadding;
			}
		}

		return y;
	}
}
