package fr.ensea.chart;

import java.text.NumberFormat;
import java.util.Locale;
import java.awt.*;
import java.awt.event.*;

/**
 * Represents an Axis for the Plot2D viewer
 */
public class Axis {
	double min; // minimum
	double max; // maximum
	private boolean autoAxisBounds; // whether min and max should be computed automatically from curves bounds
	double inc; // space between ticks
	private int axisIndex; // either Curve2D.X_AXIS or Y_AXIS
	NumberFormat numberingsFormatter=NumberFormat.getNumberInstance(Locale.US); // formatter for axis numberings (aka ticks labels)
	int maxNumberingWidthPx; // maximum width (in pixels) of  labels (updated by dataChanged())

	/** 
	 * constructor with automatic axis bounds on
	 * @param axisIndex either Curve2D.X_AXIS or Y_AXIS
	 */
	Axis(int axisIndex){
		min=-0.5;
		max=0.5;
		autoAxisBounds=true;
		this.axisIndex=axisIndex;
		numberingsFormatter.setGroupingUsed(false);
	}
	
	/** 
	 * default constructor with automatic axis bounds off
	 * @param axisIndex either Curve2D.X_AXIS or Y_AXIS
	 */
	Axis(int axisIndex, double mini, double maxi){
		min=mini;
		max=maxi;
		autoAxisBounds=false;
		this.axisIndex=axisIndex;
		numberingsFormatter.setGroupingUsed(false);
	}

	/**
	 * Toggles the automatic computing of axis bounds on and off ; default is on at <init> time
	 */
	public void setAutoAxisBounds(boolean b){
		this.autoAxisBounds=b;
	}

	/**
	 * Manually sets bounds for the axis ; see resets the automatic bound mode
	 */
	public void setManualAxisBounds(double mini, double maxi){
		this.min = mini;
		this.max = maxi;
		if (mini >= maxi) throw new IllegalArgumentException("Setting manual axis bounds : max MUST be greater than min !");
		this.autoAxisBounds=false;
	}
	
	/* package access from Plot2D.dataChanged() */ 
	void dataChanged(Plot2D plot2D){
		if (autoAxisBounds) autoComputeAxisBounds(plot2D.curveArray);
		configureNumberingsFormater(plot2D.fontMetrics);
	}
	
	private void configureNumberingsFormater(FontMetrics fontMetrics){
		// recompute nb of digits for the NumberFormat : (see java.text.NumberFormat)
		double d = Math.max(Math.abs(min), Math.abs(max));
		numberingsFormatter.setMaximumFractionDigits((int)(2 - Math.log(d)/Math.log(10))); // i.e. 230., 15.2 and 1.00 => 4 significant digits
		maxNumberingWidthPx = fontMetrics.stringWidth(numberingsFormatter.format(-d)); // minus sign is important to get a precise estimate of the maximum label width
	}	
	
	private void autoComputeAxisBounds(DecoratedCurve2D[] curveArray){
			min=-0.5;
			max=0.5;
			if (curveArray.length==0) return;
			else {
				int i=0;
				while (i<curveArray.length && !curveArray[i].isVisible()) i++; // skip this one
				if (i==curveArray.length) return; // none visible
				Curve2D curve = curveArray[i].curve;
				min = curve.getMin(axisIndex);
				max = curve.getMax(axisIndex);
				for (; i<curveArray.length; i++){
					if (!curveArray[i].isVisible()) continue;
					curve = curveArray[i].curve;
					min = Math.min(curve.getMin(axisIndex), min);
					max = Math.max(curve.getMax(axisIndex), max);
				}
				if (min==max){
					min -= 0.5;
					max += 0.5;
				}
				double gap = max-min;
				max += gap*0.1;
				min -= gap*0.1;
			}
			if (Double.isNaN(min)) min=-0.5;
			if (Double.isNaN(max)) max=0.5;
	}
	
	// returns true iff the given value lies within the axis range (that is, lies inside the frame and should be painted if any)
	boolean isWithinAxisRange(double x){
		if (x <= max && x >= min) return true;
		return false;
	}

	// clip x to the axis range
	double clip(double x){
		if (x > max) x=max;
		else if (x<min) x=min;
		return x;
	}
	
	////////////////////////////////////////////////////////
	/// customizing
	////////////////////////////////////////////////////////
	
	/**
 	 * Creates a Panel suited for editing this curve
	 */
	public Panel createCustomizer(Plot2D plot){
		return new Customizer(plot);
	}
	
	class Customizer extends Panel implements ItemListener, ActionListener {
		TextField minTF, maxTF;
		Checkbox autoAxisCB;
		Plot2D plot; // so that we can call dataChanged()

		Customizer(Plot2D plot){
			super(new GridLayout(1,6,5,5));
			this.plot=plot;
			add(new Label("min:"));
			add(minTF=new TextField(Double.toString(min),10));
			minTF.addActionListener(this);
			add(new Label("max:"));
			add(maxTF=new TextField(Double.toString(max),10));
			maxTF.addActionListener(this);
			add(new Label());
			add(autoAxisCB=new Checkbox("Auto axis bounds",autoAxisBounds));
			autoAxisCB.addItemListener(this);
		}

		public void actionPerformed(ActionEvent evt){
			//System.out.println("size="+getSize().width+","+getSize().height);
			if (evt.getSource() == minTF){
				double x = Double.valueOf(minTF.getText()).doubleValue();
				setManualAxisBounds(x, max);
				plot.dataChanged();
			}
			else if (evt.getSource() == maxTF){
				double x = Double.valueOf(maxTF.getText()).doubleValue();
				setManualAxisBounds(min, x);
				plot.dataChanged();
			}
		}
		public void itemStateChanged(ItemEvent evt){
			if (evt.getSource() == autoAxisCB){
				setAutoAxisBounds(autoAxisCB.getState());
				plot.dataChanged();
			}
		}
	}

	///////////////////////////////////////////////////////////////////////////////////////////
	//// Test
	///////////////////////////////////////////////////////////////////////////////////////////

	public String toString(){
		String s = "Axis: ";
		s += (axisIndex==0 ? "X_AXIS" : "Y_AXIS") + "\n";
		s += "* min="+min+", max="+max+"\n";
		s += "* maxNumberingWidthPx: " + maxNumberingWidthPx + "\n";
		return s;
	}

	
}

