package simulate;
import java.io.*;
import java.awt.event.*;
import java.awt.*;
import java.util.*;
import java.beans.*;
import simulate.units.*;

public abstract class Integrator extends Container implements Simulation.Element, Observer, Serializable, Runnable {

  public Thread runner;
  public boolean running = false;
  private boolean haltRequested = false;
  protected int maxSteps = Integer.MAX_VALUE;
  protected int stepCount = 0;
 
  Phase firstPhase;
  Phase[] phase;
  int phaseCount = 0;
  int phaseCountMax = 1;
  protected int sleepPeriod = 10;
  private Vector intervalListeners;
  int interval;  // number of drawTimeSteps between IntervalEvent firing
  IntervalEvent intervalEvent;
  int integrationCount;
  boolean doSleep = true;
  boolean paused = false;
  public Controller parentController;

  protected double temperature;
  protected Unit.Temperature temperatureUnit;
  protected boolean isothermal = false;

  public Integrator() {
    intervalListeners = new Vector();
    interval = 10;
    intervalEvent = new IntervalEvent();
    integrationCount = 0;
    phase = new Phase[phaseCountMax];
    
    setTemperature(Default.TEMPERATURE, Default.TEMPERATURE_UNIT);
    Simulation.register(this);
    Simulation.phaseManager.addObserver(makePhaseObserver());
  }
  
    // abstract methods
    public abstract void doStep();
    public abstract void initialize();  //put here a call to deployAgents, among other things
    public abstract Agent makeAgent(Atom a);
  
      
    //Override this to have respond in a different way when component is added
    //not good if multiple integrators are involved
    protected Observer makePhaseObserver() {    
        return new Observer() {
            public void update(Observable mgr, Object obj) {
                Phase phase = (Phase)obj;
                if(phase.integrator() == null && Integrator.this.wantsPhase()) {  //phase doesn't have an integrator and integrator wants a phase
                    registerPhase(phase);
                    phase.gravity.addObserver(Integrator.this);
                }
            }
        };
    }

  public void setController(Controller c) {parentController = c;}
  
  protected void deployAgents() {  //puts an Agent of this integrator in each atom of all phases
    for(int i=0; i<phaseCount; i++) {
        Phase p = phase[i];
        for(Atom a=p.firstAtom(); a!=null; a=a.nextAtom()) {
            a.setIntegratorAgent(makeAgent(a));
        }
    }
  }
  
  // Temperature and its units
  public final void setTemperature(double t, Unit.Temperature u) {
    temperatureUnit = u;
    setTemperature(t);
  }
  public final void setTemperature(double t) {temperature = temperatureUnit.toSim(t);}
  public final double getTemperature() {return temperatureUnit.fromSim(temperature);}
  public final double temperature() {return temperature;}
  public final void setTemperatureUnit(Unit.Temperature u) {temperatureUnit = u;}
  public Unit.Temperature getTemperatureUnit() {return temperatureUnit;}

  //Other introspected properties
  public void setIsothermal(boolean b) {isothermal = b;}
  public boolean isIsothermal() {return isothermal;}
    
  public final int getInterval() {return interval;}
  public final void setInterval(int interval) {interval = interval;}
  
  public final int getSleepPeriod() {return sleepPeriod;}
  public final void setSleepPeriod(int s) {sleepPeriod = s;}

  /**
   * @return true if integrator can perform integration of another phase, 
   *         false if the integrator has all the phases it was built to handle
   */
  public boolean wantsPhase() {return phaseCount < phaseCountMax;}
  
  public void registerPhase(Phase p) {
    for(int i=0; i<phaseCount; i++) {if(phase[i]==p) return;}  //check that phase is not already registered
    if(!this.wantsPhase()) {phaseCount--;}  //if another phase not wanted, use given one to replace last phase
    phase[phaseCount] = p;
    phaseCount++;
    firstPhase = phase[0];
    p.setIntegrator(this);
    p.meterManager().addObserver(new Observer() {
        public void update(Observable mgr, Object obj) {
            Integrator.this.addIntervalListener((MeterAbstract)obj);  
        }
    });
  }
  
  public synchronized void addIntervalListener(IntervalListener iil) {
    intervalListeners.addElement(iil);
  }

  public synchronized void removeIntervalListener(IntervalListener iil) {
    intervalListeners.removeElement(iil);
  }

  public void fireIntervalEvent(IntervalEvent iie) {
    Vector currentListeners = null;
    synchronized(this){
        currentListeners = (Vector)intervalListeners.clone();
    }
    for(int i = 0; i < currentListeners.size(); i++) {
        IntervalListener listener = (IntervalListener)currentListeners.elementAt(i);
        listener.intervalAction(iie);
    }
  }
  
  /**
   Update method for Observer interface
   */
  public void update(Observable o, Object arg) {}
 
    public int getMaxSteps() {return maxSteps;}
    public void setMaxSteps(int m) {maxSteps = m;}
    
    public void start() {
        haltRequested = false;
        this.initialize();
        runner = new Thread(this);
        runner.start();
    }

    public void run() {
        int ic = 0;
        int stepCount = 0;
        int iieCount = interval;
        while(stepCount < maxSteps) {
            while(paused) doWait();
            if(haltRequested) break;
            this.doStep();
            if(--iieCount == 0) {
                fireIntervalEvent(intervalEvent);
                iieCount = interval;
            }
            if(doSleep) {
                try { Thread.sleep(sleepPeriod); }
                catch (InterruptedException e) { }
            }
            stepCount++;
        }
        System.exit(0);
    }
    
    protected synchronized void doWait() {
        try {
            wait();
        } catch(InterruptedException e) {}
    }
    
    //suspend and resume functions
    public void pause() {paused = true;}
    public synchronized void unPause() {paused = false; notify();}
    
    //stop function
    public void halt() {
        haltRequested = true;
        if(paused) unPause();
    }

    public void setDoSleep(boolean b) {doSleep = b;}
    public boolean isDoSleep() {return doSleep;}

// Class generated by integrator as one of the properties of each atom

    interface Agent {}
    
    public class IntervalEvent extends EventObject{
        
        public IntervalEvent() {
            super(Integrator.this);
        }
    }
    
    public interface IntervalListener extends java.util.EventListener {
        public void intervalAction(IntervalEvent evt);
    }

}

