/*  Monte-Carlo simulation code for statistical physics
    Copyright (C) 2001-2005  Sylvain Reynal
    
    Département de Physique
    Ecole Nationale Supérieure de l'Electronique et de ses Applications (ENSEA)
    6, avenue du Ponceau,    
    F-95014 CERGY CEDEX

    et
    
    Laboratoire de Physique Théorique et Modélisation (LPTM)
    Université de Cergy-Pontoise - Site de Neuville
    F-95031 CERGY CEDEX
    
	
    Tel : 00 +33 130 736 245
    Fax : 00 +33 130 736 667
    e-mail : reynal@ensea.fr
    web page : http://www.ensea.fr/staff/reynal/
*/

package fr.ensea.montecarlo.data;

import fr.ensea.chart.*;
import JSci.awt.*;

import java.awt.*; import javax.swing.*;
import java.text.*;

/**
 * A one-dimensional histogram with fixed bin width.
 * Plug on top of a Samples object to compute a histogram of its data.
 */
public class Histogram implements Curve2D {
	
	private Samples data;
	private int nbBins;
	private Point2D[] bins; // (bin-center, bin-height) for each bin
	private double minX, maxX, maxY, binWidth; // minY=0
	
	/**
	 * Construct an histogram of the given data, with nbBins bins
	 */
	protected Histogram(Samples data, int nbBins){
		if (nbBins<2) throw new IllegalArgumentException("There must be more than one bin !");
		this.nbBins = nbBins;
		this.data = data;
		update();
	}
	
	/**
	 * Recomputes all bin entries. Invokes when data have changed.
	 */
	protected synchronized void update(){
		bins = new Point2D[nbBins];
		binWidth = (data.getMax(Y_AXIS) - data.getMin(Y_AXIS))/(nbBins-1);
		minX = data.getMin(Y_AXIS) - binWidth/2.0;
		maxX = data.getMax(Y_AXIS) + binWidth/2.0;
		// create axis:
		double binCenter = data.getMin(Y_AXIS);
		for (int binIdx=0; binIdx < nbBins; binIdx++){
			bins[binIdx] = new Point2D(binCenter,0);
			binCenter += binWidth;
		}
		// fill bins:
		for (int i=0; i<data.getNbPoints(); i++){
			int binIdx = coordToIndex(data.getSample(i));
			bins[binIdx].y += 1.0;
		}
		// compute min/max bin entries:
		maxY = bins[0].y; 
		for (int binIdx=1; binIdx < nbBins; binIdx++){
			double entries = bins[binIdx].y;
			if (entries > maxY) maxY = entries;
		}
	}
	
	/**
	 * To be invoked when a single sample has been added to the data since the last call to either
	 * update() or updateIncremental().
	 * This may reduce overhead if "sample" lies within current histogram bounds. 
	 */
	protected synchronized void updateIncremental(double sample){
		if (sample < minX || sample > maxX) update();
		else {
			int binIdx = coordToIndex(sample);
			bins[binIdx].y += 1.0;
			if (bins[binIdx].y > maxY) maxY = bins[binIdx].y;
		}
	}
	
	/**
	 * Returns the number of bins
	 */
	public int bins(){
		return nbBins;
	}
	
	/**
	 * Returns the width of a bin
	 */
	public double binWidth(){
		return binWidth;
	}
	
	/**
	 * Returns the center of the ith bin
	 */
	public synchronized double binCenter(int i){
		return bins[i].x;
	}
	
	/**
	 * Returns the number of entries in the ith bin
	 */
	public synchronized double binEntries(int i){
		return bins[i].y;
	}
	
	//////////////////////////////////////////////////////////
	//// curve2D interface
	//////////////////////////////////////////////////////////

	public synchronized Point2D getPoint(int i){
		return bins[i];
	}
	
	public int getNbPoints(){
		return nbBins;
	}
	
	public double getMin(int axis){
		switch (axis){
			case X_AXIS: return minX;
			case Y_AXIS: return 0;
			default: throw new IllegalArgumentException("Wrong axis index : "+axis);
		}
	}
	
	public double getMax(int axis){
		switch (axis){
			case X_AXIS: return maxX;
			case Y_AXIS: return maxY;
			default: throw new IllegalArgumentException("Wrong axis index : "+axis);
		}
	}
	
	//////////////////////////////////////////////////////////
	//// utilities
	//////////////////////////////////////////////////////////
	
	// return the bin index to which the given value belongs
	private int coordToIndex(double val){
		if (binWidth==0) return 0;
		int i = (int)Math.floor((val-minX)/binWidth);
		if (i>=nbBins) i = nbBins-1;
		if (i<0) i=0;
		return i;
	}
	
	/////////////////////////////////////////////////////////
	//// test
	/////////////////////////////////////////////////////////
	
	public String toString(){
		NumberFormat nf = NumberFormat.getNumberInstance();
		nf.setMaximumFractionDigits(2);
		String s = "Histogram:\n";
		s += "* nbBins="+nbBins+"\n";
		s += "* binWidth="+binWidth+"\n";
		s += "* x-axis=("+minX+","+maxX+")\n";
		s += "* y-axis=(0,"+maxY+")\n";
		s += "* center/height:\n";
		for (int binIdx=0; binIdx < nbBins; binIdx++){
			s += "[" + nf.format(bins[binIdx].x) + ", " + bins[binIdx].y + "] ";
		}
		return s;
	}
		
	public static void main(String[] args){
		JFrame frame = new JFrame("test");
		frame.setSize(400,400);

		Samples samples = new Samples();
		for (int i = 0; i < 200; i++) samples.addSample(100.0 * Math.random() - 50.0);
		
		System.out.println(samples);
		Histogram histo = new Histogram(samples, 101);
		System.out.println(histo);
		
		Plot2D plot = new Plot2D(Color.black, Color.gray, Color.darkGray);
		plot.addCurve(histo,"N(E)",Color.green, DecoratedCurve2D.BARS_FILLED);
		plot.setTitle("Histogram");
		plot.setXaxisLabel("E");
		frame.getContentPane().add(plot);
		frame.show();
	}
		
} 
