/*  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.Version;
import fr.ensea.Localizer;
import fr.ensea.chart.*;
import fr.ensea.montecarlo.*;
import fr.ensea.montecarlo.multicanonical.*;
import fr.ensea.montecarlo.model.*;
import fr.ensea.montecarlo.misc.*;

import java.awt.*; 
import javax.swing.*;
import javax.swing.event.*;
import java.awt.event.*;
import java.applet.Applet;
import java.text.NumberFormat;
import java.util.Locale;

/**
 * Holds thermal averages computed from simulation measurements, as well as graphs to display them.
 */
public class ThermalAveragesSet implements ActionListener {

	// --- cached from MCApplet ---
	private AbstractApplet controller;
	private SamplesBag samplesBag;
	private boolean isRGActive;
	private ILattice lattice;
	
	// --- curves ---
	public DefaultCurve2D meanEnergyVsKTCurve; // <E>
	public DefaultCurve2D meanMagVsKTCurve; // <M>
	public DefaultCurve2D susceptibilityVsKTCurve; // chi
	// RG only:
	private DefaultCurve2D meanEnergyRGVsKT; // <E(RG)>
	
	// --- plot2D ---
	private Plot2D graphMeanEnergyVsKT; // <E> and possibly <E(RG)> if applicable
	private Plot2D graphMeanMagVsKT;    // <M>
	private Plot2D graphFluctuations;   // chi
	private FreeEnergyPanel freeEnergyPanel;
	
	// --- widgets ---
	private JButton newAverageCurvesBUT, addAveragesBUT,clearAveragesBUT;
	
	// --- misc ---
	private int averagesSetIdx=0; // index of current averages set (i.e. rightmost charts)
	private final static int MAX_averagesSetIdx = 8;

	public ThermalAveragesSet(ILattice lattice, SamplesBag samplesBag, boolean isRGActive){
		this.lattice = lattice;
		this.samplesBag = samplesBag;
		this.isRGActive = isRGActive;
	}
	
	// --- canonical ---
	
	/**
	 * Sauve les moyennes statistiques pour la température courante dans le graphe "<E>/<M>".
	 */
	public void storeSimulationResults(double kT){
		if (samplesBag.isEmpty()) return;
		double meanE = samplesBag.computeEnergyMean();
		double meanM = samplesBag.computeMagnetizationMean();
		double varM = samplesBag.computeMagnetizationVariance(meanM);
		double chi = lattice.getSpinCount() * varM / kT;
		meanEnergyVsKTCurve.addXY(kT,meanE);
		if (isRGActive){
			double meanERG = samplesBag.computeEnergyRGMean();
			meanEnergyRGVsKT.addXY(kT,meanERG);
		}
		meanMagVsKTCurve.addXY(kT,meanM);
		susceptibilityVsKTCurve.addXY(kT, chi);
		updateGUI();
		//showStatus(Localizer.get("NewPointAdded")+"=" + kT);
	}

	// --- multicanonical ---
	public void storeSimulationResults(WLSimulationThread algoThread){
		if (samplesBag.isEmpty()) return;
		double[] kTarray = algoThread.getKTList();
		int VOL = lattice.getSpinCount();
		WLDOSHistogram histo = algoThread.getWLDOSHistogram();
		new MomentsReweighter(histo, samplesBag, this, kTarray, VOL); // fill meanEnergyVsKTCurve etc... in place
		//updateGUI(); // done from MomentsReweighter
		// free energy:
		if (freeEnergyPanel==null) freeEnergyPanel = new FreeEnergyPanel(kTarray);
		else freeEnergyPanel.setKTList(kTarray);
		freeEnergyPanel.backupEnergyHistogram(); // because it will be reset by WLSimulationThread
		freeEnergyPanel.updateGUI(); // force recomputing F(kT,E)
	}
	

	// --- both ---
	
	public void addNewAveragesSet(){
		//if (meanEnergyVsKTCurve!=null && meanEnergyVsKTCurve.size()==0) return; // no data => no need to add a new set
		averagesSetIdx++;
		if (averagesSetIdx==MAX_averagesSetIdx) {
			graphMeanEnergyVsKT.removeCurves();
			graphMeanMagVsKT.removeCurves();
			graphFluctuations.removeCurves();
			averagesSetIdx=1;
		}
		String idxStr="["+Integer.toString(averagesSetIdx)+"]";
		
		meanEnergyVsKTCurve = new DefaultCurve2D();
		if (isRGActive) meanEnergyRGVsKT = new DefaultCurve2D();
		meanMagVsKTCurve = new DefaultCurve2D();
		susceptibilityVsKTCurve = new DefaultCurve2D();
		
		Color col1 = MCApplet.PREDEFINED_COLORS[averagesSetIdx%MCApplet.PREDEFINED_COLORS.length];
		//Color col2 = PREDEFINED_COLORS[(averagesSetIdx+MAX_averagesSetIdx)%PREDEFINED_COLORS.length];
		graphMeanMagVsKT.addCurve(meanMagVsKTCurve, idxStr, col1, DecoratedCurve2D.DOTS_LINES);
		graphMeanEnergyVsKT.addCurve(meanEnergyVsKTCurve, idxStr, col1, DecoratedCurve2D.DOTS_LINES);
		if (isRGActive) graphMeanEnergyVsKT.addCurve(meanEnergyRGVsKT, idxStr+"RG", col1, DecoratedCurve2D.LINES);
		graphFluctuations.addCurve(susceptibilityVsKTCurve, idxStr, col1, DecoratedCurve2D.DOTS_LINES);
	}


	public void resetAveragesGraphs(){
		graphMeanEnergyVsKT.removeCurves();
		graphMeanMagVsKT.removeCurves();
		graphFluctuations.removeCurves();
		averagesSetIdx=0;
		addNewAveragesSet();
	}

	public void removeLastAverageSet(){
		graphMeanEnergyVsKT.removeLastCurve(); // <E>
		if (isRGActive) graphMeanEnergyVsKT.removeLastCurve(); // <E(RG)>
		graphMeanMagVsKT.removeLastCurve();
		graphFluctuations.removeLastCurve();
		averagesSetIdx--;
		if (averagesSetIdx<0) averagesSetIdx=0;
		//System.out.println("Remove: averagesSetIdx="+averagesSetIdx);
		if (averagesSetIdx==0) addNewAveragesSet();
	}
	
	// --- UI ---
	/**
	 * Create a pane containing graphs of thermodynamic averages  
	 */
	public Box createUI(AbstractApplet applet){

		this.controller = applet;
		//Dimension graphPreferredSize = new Dimension(applet.getSize().width/4, applet.getSize().height/4);
		Dimension graphPreferredSize = new Dimension(200,200);
		
		// --- init graphs ---
		this.graphMeanEnergyVsKT = new Plot2D(AbstractApplet.PANELS_BACKGROUND, AbstractApplet.PLOT2D_FRAME_COLOR2, AbstractApplet.PLOT2D_GRID_COLOR);
		graphMeanEnergyVsKT.setForeground(AbstractApplet.TEXT_COLOR); // title, keys
		graphMeanEnergyVsKT.setTitle("<E/L> vs. kT"); // no x-axis label
		//if (lattice instanceof AFIsingLattice) graphMeanEnergyVsKT.yAxis().setManualAxisBounds(-0.1,2.1);
		//else if (lattice instanceof PottsLattice) graphMeanEnergyVsKT.yAxis().setManualAxisBounds(-2.1,0.1);
		graphMeanEnergyVsKT.setPreferredSize(graphPreferredSize);

		this.graphMeanMagVsKT = new Plot2D(AbstractApplet.PANELS_BACKGROUND, AbstractApplet.PLOT2D_FRAME_COLOR2, AbstractApplet.PLOT2D_GRID_COLOR);
		graphMeanMagVsKT.setForeground(AbstractApplet.TEXT_COLOR); // title, keys
		graphMeanMagVsKT.setTitle("<M> vs. kT"); // no x-axis label
		//graphMeanMagVsKT.yAxis().setManualAxisBounds(-0.1,1.1);
		graphMeanMagVsKT.setPreferredSize(graphPreferredSize);


		this.graphFluctuations = new Plot2D(AbstractApplet.PANELS_BACKGROUND, AbstractApplet.PLOT2D_FRAME_COLOR2, AbstractApplet.PLOT2D_GRID_COLOR);
		//graphFluctuations.addCurve(susceptibilityVsKTCurve, "chi", Color.cyan, Plot2D.DOTS_LINES);
		graphFluctuations.setForeground(AbstractApplet.TEXT_COLOR); // title, keys
		graphFluctuations.setTitle(Localizer.get("Susceptibility")+" vs. kT");
		graphFluctuations.setPreferredSize(graphPreferredSize);

		clearAveragesBUT=new JButton("-");
		newAverageCurvesBUT=new JButton("+");
		if (applet instanceof MCApplet)  addAveragesBUT=new JButton(Localizer.get("AddPoint"));
		else addAveragesBUT=new JButton("F(kT,E)");

		// --- layout ---
		Box graphPanel = Box.createHorizontalBox();

		JPanel buttonBox = new JPanel(new GridLayout(3,1));
		buttonBox.add(clearAveragesBUT);
		buttonBox.add(newAverageCurvesBUT);
		buttonBox.add(addAveragesBUT);
		graphPanel.add(buttonBox);
		
		
		
		graphPanel.add(graphMeanEnergyVsKT);

		graphPanel.add(graphMeanMagVsKT);

		graphPanel.add(graphFluctuations);

		// --- listeners ---
		clearAveragesBUT.addActionListener(this);
		newAverageCurvesBUT.addActionListener(this);
		addAveragesBUT.addActionListener(this);

		clearAveragesBUT.addMouseMotionListener(new StatusHelpMouseHandler(applet, clearAveragesBUT.getLabel()+": "+Localizer.get("HelpStatus3")));
		newAverageCurvesBUT.addMouseMotionListener(new StatusHelpMouseHandler(applet, clearAveragesBUT.getLabel()+": "+Localizer.get("HelpStatus4")));
		addAveragesBUT.addMouseMotionListener(new StatusHelpMouseHandler(applet, clearAveragesBUT.getLabel()+": "+Localizer.get("HelpStatus5")));

		graphMeanEnergyVsKT.addMouseListener(new GraphMouseHandler(applet, this.graphMeanEnergyVsKT));
		graphMeanEnergyVsKT.addMouseMotionListener(new StatusHelpMouseHandler(applet, graphMeanEnergyVsKT.getTitle()+": "+Localizer.get("HelpStatus1")));
		graphMeanMagVsKT.addMouseListener(new GraphMouseHandler(applet, this.graphMeanMagVsKT));
		graphMeanMagVsKT.addMouseMotionListener(new StatusHelpMouseHandler(applet, graphMeanMagVsKT.getTitle()+": "+Localizer.get("HelpStatus1")));
		graphFluctuations.addMouseListener(new GraphMouseHandler(applet, this.graphFluctuations));
		graphFluctuations.addMouseMotionListener(new StatusHelpMouseHandler(applet, graphFluctuations.getTitle()+": "+Localizer.get("HelpStatus1")));

		graphPanel.setBackground(AbstractApplet.PANELS_BACKGROUND);
		return graphPanel;
	}
	
	public void actionPerformed(ActionEvent evt){
		if (evt.getSource() == clearAveragesBUT){
			//resetAveragesGraphs();
			removeLastAverageSet();
		}
		else if (evt.getSource() == newAverageCurvesBUT){
			addNewAveragesSet();
		}
		else if (evt.getSource() == addAveragesBUT){ // manually add a new set of <E>, <M> and chi
			/*
			if (!controller.algoThread.isTemperatureSweep()) {
				controller.algoThread.kill();
				controller.startStopBUT.setLabel(Localizer.get("Start"));
			}
			*/
			if (controller instanceof MCApplet){
				double kT = ((MCApplet)controller).getAlgorithm().getTemperature();
				storeSimulationResults(kT);
				controller.showStatus(Localizer.get("NewPointAdded")+"=" + kT);
			}
			else if (controller instanceof WLApplet){
				if (freeEnergyPanel==null) {
					controller.showStatus(Localizer.get("NotReadyYet"));
					return;
				}
				freeEnergyPanel.setVisible(!freeEnergyPanel.isVisible());
			}
		}
	}

	public void updateGUI(){
		graphMeanEnergyVsKT.dataChanged();
		graphMeanMagVsKT.dataChanged();
		graphFluctuations.dataChanged();
	}
		
	
	//////////////////////////////// MuCa only ////////////////////////
	class FreeEnergyPanel extends JFrame implements ChangeListener{
		
		DefaultCurve2D freeEnergyCurve; // F(kT,E)
		Plot2D freeEnergyGraph; 
		JSlider kTSB;
		JLabel kTLBL;
		double[] binHeights;
		double[] kTarray;

		public FreeEnergyPanel(double[] kTarray){
			super("Free energy");
			this.kTarray = kTarray;
			getContentPane().setLayout(new BorderLayout(5,5));
			freeEnergyCurve = new DefaultCurve2D();
			freeEnergyGraph = new Plot2D(AbstractApplet.PANELS_BACKGROUND, AbstractApplet.PLOT2D_FRAME_COLOR2, AbstractApplet.PLOT2D_GRID_COLOR);
			freeEnergyGraph.addCurve(freeEnergyCurve, "", AbstractApplet.PLOT2D_CURVE_COLOR1, DecoratedCurve2D.DOTS);
			freeEnergyGraph.setForeground(AbstractApplet.TEXT_COLOR); // title, keys
			freeEnergyGraph.setTitle("F(kT,E)"); // no x-axis label
			freeEnergyGraph.setXaxisLabel("E");
			//graphMeanMagVsKT.yAxis().setManualAxisBounds(-0.1,1.1);
			freeEnergyGraph.setPreferredSize(new Dimension(400,400));
			getContentPane().add(freeEnergyGraph,"Center");
			
			JPanel southPane = new JPanel();
			int defIdx = kTarray.length/2;
			kTSB = new JSlider(JSlider.HORIZONTAL, 0, kTarray.length, defIdx);
			kTSB.addChangeListener(this);
			southPane.add(kTSB);
			kTLBL = new JLabel("kT="+kTarray[defIdx]);
			southPane.add(kTLBL);
			getContentPane().add(southPane,BorderLayout.SOUTH);
			setSize(400,400);
			backupEnergyHistogram();
		}
		
		public void setKTList(double[] kTarray){
			this.kTarray=kTarray;
			kTSB.setMaximum(kTarray.length);
		}
		
		public void stateChanged(ChangeEvent evt){
			if (evt.getSource() == kTSB) updateGUI();
		}
		
		public void backupEnergyHistogram(){
			WLDOSHistogram SE = ((WLSimulationThread)controller.getSimulationThread()).getWLDOSHistogram();
			if (SE != null){
				this.binHeights = new double[SE.xAxis().bins()];
				for (int bin=0; bin<SE.xAxis().bins(); bin++){
					binHeights[bin] = SE.binEntries(bin);
				}
			}
		}
		
		public void updateGUI(){
			int i = kTSB.getValue();
			if (i<0) i=0;
			if (i>kTarray.length-1) i=kTarray.length-1;
			double kT=kTarray[i];
			kTLBL.setText("kT="+kT);
			updateFreeEnergy(kT);
		}
		
		private void updateFreeEnergy(double kT){

			if (binHeights==null) backupEnergyHistogram();
			
			freeEnergyCurve.clear(); // remove all points
			WLDOSHistogram SE = ((WLSimulationThread)controller.getSimulationThread()).getWLDOSHistogram();// only used for w(E), not N(E)
			MuCaWeighter weighter = new MuCaWeighter(kT, SE);
			for (int bin=0; bin < SE.xAxis().bins(); bin++){ // for each energy level
				// Z(kT,E) = N(E)*w(E) = N(E) * exp(Emu(E)/kT0 - E/kT - K) (correct up to a multiplicative constant)
				double E = SE.binCenter(bin);
				double WE = weighter.getWeight(E);
				double NE = binHeights[bin]; // !!! bin height from backup histogram (because SE may have been reset in the meantime)
				double Z = NE * WE;
				double F = -Math.log(Z);
				int VOL = lattice.getSpinCount();
				//System.out.println("F("+(E/VOL)+")="+F);
				if (!Double.isNaN(F) && !Double.isInfinite(F)) freeEnergyCurve.addXY(E/VOL,F);
			}
			freeEnergyGraph.dataChanged();
		}
	}
}

