/*  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;

import java.util.*;
import java.awt.*;
import fr.ensea.Localizer;
import fr.ensea.montecarlo.*;
import fr.ensea.montecarlo.event.*;

/**
 * This class is the abstract class for simulation thread management (since the algorithm and the UI are executed 
 * in separate threads). In particular, this class allows one to pause/stop/restart the simulation, to set the "sleep" delay between
 * MC steps, arranges to repaint the UI asynchronously at appropriate times, and dispatch Thread events to
 * registered listeners. 
 */
public abstract class AbstractSimulationThread implements Runnable {

	protected AbstractApplet controller;
	protected boolean alive, paused;
	protected long sleepDelay=100;
	protected boolean isSuspendRedrawViewer;
	
	protected int nbMCSteps;
	
	/**
	 * a list of listeners to be notified signals during algorithm run-time
	 */
	protected Vector listenerList = new Vector();

	public AbstractSimulationThread(AbstractApplet controller){
		this.controller=controller;
		isSuspendRedrawViewer = false;
		nbMCSteps=Integer.MAX_VALUE;
	}

	public abstract boolean setAlgorithm(String algo);
	
	/**
	 * Set the number of MC steps to be simulated
	 * @param n set to a negative number to simulated an infinite number of steps
		 */
	public void setNumberMCSteps(int n){
		if (n<1) this.nbMCSteps = Integer.MAX_VALUE;
		else this.nbMCSteps = n;
	}

	public int getNumberMCSSteps(){
		return this.nbMCSteps;
	}
	
	/**
	 * Returns the counter rate (e.g. acceptance rate, cluster size) in human-readable form
	 */
	public abstract String getCountersRateLabel();

	public abstract void reinitCounters();

	/**
	 * If "s" is true, stops triggering repaint event for the lattice viewer.
	 */
	public void suspendRedrawViewer(boolean s){
		this.isSuspendRedrawViewer = s;
	}

	public boolean isSuspendRedrawViewer(){
		return this.isSuspendRedrawViewer;
	}

	/**
	 * Implements the run() method of interface Runnable. We iterate over the temperature list (which is reduced
	 * to a single element if the simulation is a single-temperature one), testing for the "alive" and "pause" flags
	 * at each iteration.
	 */
	public void run(){
		alive=true;
		paused=false;
		fireSignal(new SignalEvent(this, SignalEvent.STARTED));
		while (configureNextRun()){
			//while(alive && controller.getSamplesBag().size() < nbMCSteps){
			while(true){
				sweep();
				controller.updateGUI(); // update graphs + labels
				if (!this.isSuspendRedrawViewer){
					controller.updateLatticeViewer();
					try {
						Thread.sleep(sleepDelay);
					}
					catch (InterruptedException ex){}
				}
				while(paused){
					try {
						Thread.sleep(1000);
					}
					catch (InterruptedException ex){}
				}
				if (finalizeRun()) break;
				if (!alive) break;
			}
			//finalizeRun();
			if (!alive) break; // break for() loop 'cause we killed it inside the while() loop !
		}
		alive =false;
		fireSignal(new SignalEvent(this, SignalEvent.COMPLETED));
		System.out.println("Runnable COMPLETED");
	}

	protected abstract boolean configureNextRun();
	
	protected abstract void sweep();

	/**
	 * Invoked at the end of the run() loop. Return true if we can switch to the next run (either the next 
	 * temperature, or the next WL iteration).
	 */
	protected abstract boolean finalizeRun();
	
	
	public void kill(){
		System.out.println("Algorithm killed");
		alive = false;
		paused=false;
	}

	/** N/A if this thread is dead */
	public void pause(){
		if (alive) {
			paused=true;
			fireSignal(new SignalEvent(this, SignalEvent.SUSPENDED));
		}
	}

	/** N/A if this thread is dead */
	public void resume(){
		if (alive) {
			paused=false;
			fireSignal(new SignalEvent(this, SignalEvent.RESUMED));
		}
	}

	public void start(){
		kill();
		new Thread(this).start();
	}

	public void setSleepDelay(long millis){
		this.sleepDelay = millis;
	}

	public long getSleepDelay(){
		return this.sleepDelay;
	}

	public boolean isAlive(){
		return alive;
	}

	public boolean isPaused(){
		return alive & paused; // a dead thread can't be paused !
	}

	public boolean isRunning(){
		return alive & !paused;
	}

	public boolean isDead(){
		return !alive;
	}

	// -------------- listeners methods ------------------------------
	/**
	 * registers a SignalListener to receive SignalEvent's during algorithm run-time
	 * note : this method tries to mimic javax.swing.EventListener as much as possible,
	 * seeing that swing classes ain't accessible easily from an applet.
	 */
	public void addSignalListener(SignalListener listener){
		if (listener!=null) listenerList.addElement(listener);
	}

	public void removeSignalListener(SignalListener listener){
		if (listener!=null) listenerList.removeElement(listener);
	}

	/**
	 * fires the given DrawingEvent to all registered DrawingListener's
	 * this usually takes place at the very beginnig of the algorithm, then at the end of each step processed, finally when
	 * the algorithm has finished.
	 */
	public void fireSignal(SignalEvent event){
		for(Enumeration e = listenerList.elements(); e.hasMoreElements();){
			((SignalListener)(e.nextElement())).signalBroadcast(event);

		}
	}
	// --------------- UI -----------------
	public abstract Choice createAlgoChoice();

}

