package adaptive.agents.tutor;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
/*****************************************************************************
 *
 * Copyright 2000, Institute for Complex Engineered Systems,
 *                 Carnegie Mellon University
 * PROJECT: Self-adaptive Software
 * DESCRIPTION: agent to display a waveform in a small gui frame
 *
 * @author Jonathan R Jackson <A HREF="mailto:jackson4@andrew.cmu.edu">jackson4@andrew.cmu.edu>/A>
 *
 * REVISION HISTORY:
 *
 * $Log: WaveFrame.java,v $
 * 
 ****************************************************************************/
public class WaveFrame extends Frame{
    protected static final boolean DEBUG=false;
    protected static final int HEIGHT=100;
    protected static final int WIDTH=400;
    protected WaveForm wave;
    protected WaveThread thread;
    protected WaveTimeScale time;
    protected WaveValueScale value;

    public WaveFrame(){
	this("Wave Display");
    }
    public WaveFrame(String title){
	this(title,0,2000,-0.1f,1.1f,20);
    }
    public WaveFrame(String title,int minTime,int maxTime,float minValue, float maxValue,int timeScale){
	super(title);
	BorderLayout b=new BorderLayout();
	setLayout(b);
	wave=new WaveForm(minTime,maxTime,minValue,maxValue,timeScale);
	wave.setSize(WIDTH,HEIGHT);
	add("Center",wave);
	thread=new WaveThread();

	time=new WaveTimeScale();
	add("South",time);
	value=new WaveValueScale();
	add("West",value);

	addWindowListener( new WindowAdapter() {
	    public void windowClosing( WindowEvent ev ) {
		if (DEBUG) System.out.println("I think the window should close now");
		stop();
		dispose();
		//	System.exit(0);
	    }
	}
			   );
	//this.pack();
	this.setSize(420,120);

    }
    public void start(){
	thread.start();
    }
    public void stop(){
	thread.stop();
    } 
    public void update(float v){
	wave.update(v);
	wave.timeStep();
    }
    protected class WaveThread extends Thread{
	public WaveThread(){
	    super();
	}
	public void run(){
	    while (true){
		wave.timeStep();		
		wave.repaint();
		time.repaint();
		value.repaint();
		try{
		    sleep(wave.getTimeScale());
		}catch(InterruptedException ie){}
	    }
	}
    }/*end WaveThread*/

    protected class WaveTimeScale extends Panel{
	protected Graphics offGraphics; 
	protected Image offImage;
	protected Dimension offDimension;
	public WaveTimeScale(){
	    setSize(WaveFrame.WIDTH,20);
	}
	public void update(Graphics g){
	    Dimension d = getSize();
	    Point temp=new Point(0,10);
	    if ( (offGraphics == null)
		 || (d.width != offDimension.width)
		 || (d.height != offDimension.height) ) {
		if (DEBUG) System.out.println("resetting time offGraphics");
		offDimension = d;
		offImage = createImage(d.width, d.height);
		offGraphics = offImage.getGraphics();
	    
		//Erase the previous image.
		offGraphics.setColor(getBackground());
		offGraphics.fillRect(0, 0, d.width, d.height);
		offGraphics.setColor(Color.black);
		
		Dimension other=value.getSize();
		offGraphics.drawLine(other.width-1,0,d.width-1,0);
		for (int i=other.width-1;i<d.width;i+=10*wave.timeScaleFactor){
		    offGraphics.drawLine(i,0,i,5);
		}
		g.drawImage(offImage, 0, 0, this);
	    }
	}
    }
    protected class WaveValueScale extends Panel{
	protected Graphics offGraphics; //used to prepare graphics in background to reduce flicker
	protected Image offImage;
	protected Dimension offDimension;
	public WaveValueScale(){
	    setSize(20,WaveFrame.HEIGHT);
	}
	public void update(Graphics g){
	    Dimension d = getSize();
	    Point temp=new Point(0,0);
	    if ( (offGraphics == null)
		 || (d.width != offDimension.width)
		 || (d.height != offDimension.height) ) {
		if (DEBUG) System.out.println("resetting value offGraphics");
		offDimension = d;
		offImage = createImage(d.width, d.height);
		offGraphics = offImage.getGraphics();
	    
		//Erase the previous image.
		offGraphics.setColor(getBackground());
		offGraphics.fillRect(0, 0, d.width, d.height);
		offGraphics.setColor(Color.black);
		
		offGraphics.drawLine(d.width-1,0,d.width-1,d.height-1);
		
		for (int i=d.height-1;i>0;i-=0.2*wave.valueScaleFactor){
		    offGraphics.drawLine(d.width,i,d.width-5,i);
		}
		/*for (float i=wave.minValue;i<wave.maxValue;i+=0.1f){
		  temp=wave.calcPlotPoint(new Long(wave.minTime),new Float(i));
		  offGraphics.drawLine(d.width,temp.y,d.width-5,temp.y);
		  }*/
		
		g.drawImage(offImage, 0, 0, this);
	    }
	}
    }
    protected class WaveForm extends Panel{
	public long minTime;  //in milliseconds
	public long maxTime;  //in milliseconds
	public float minValue;
	public float maxValue;

	protected long currentTime; //number of milliseconds since we started
	protected float currentValue;


	public int timeScale;   //number of milliseconds=1 data point

	protected float valueScaleFactor; //number of pixels=value change of 1
	protected float timeScaleFactor;  //number of pixels=time step of timeScale

	protected Graphics offGraphics; //used to prepare graphics in background to reduce flicker
	protected Image offImage;
	protected Dimension offDimension;

	protected Vector storedTimes;
	protected Vector storedValues;

	protected long startTime;
	public WaveForm(long minTime,long maxTime,float minValue, float maxValue,int timeScale){
	    super();
	    this.minValue=minValue;
	    this.maxValue=maxValue;
	    
	    this.valueScaleFactor=(int)(100/(maxValue-minValue));
	    this.timeScaleFactor=400.0f*timeScale/(float)(maxTime-minTime);
	    this.timeScale=timeScale;
	    this.minTime=minTime;
	    this.maxTime=maxTime;
	    this.currentTime=minTime;
	    this.currentValue=0;

	    if (DEBUG) System.out.println("time:"+timeScaleFactor+" value:"+valueScaleFactor);
	    if (DEBUG) System.out.println("mintime:"+this.minTime+"  maxtime:"+this.maxTime);
	    if (DEBUG) System.out.println("currentTime:"+this.currentTime);

	    storedTimes=new Vector();
	    storedValues=new Vector();
	    this.startTime=System.currentTimeMillis();
	}
	public WaveForm(){
	    this(0,2000,-.1f,1.1f,20);
	}

	public void update(float newValue){
	    this.currentValue=newValue;
	}

	public Point calcPlotPoint(Long time,Float value){
	    long t=time.longValue();
	    float val=value.floatValue();
	    Point newPoint;

	    int v;//=(int)(valueScaleFactor*(val/(maxValue-minValue)));
	    v=(int)(valueScaleFactor*(maxValue-val/(maxValue-minValue)));
	    newPoint= new Point((int)(timeScaleFactor*(t-minTime)/(float)timeScale),v);
	    return newPoint;
	}
	public void timeStep(){	    
	    float lastValue0=0,lastValue1;
	    long lastTime0,lastTime1;

	    currentTime=System.currentTimeMillis()-startTime;

	    if (storedValues.size()>1){
		lastValue0=((Float)storedValues.elementAt(storedValues.size()-1)).floatValue();
		lastValue1=((Float)storedValues.elementAt(storedValues.size()-2)).floatValue();
		lastTime0=((Long)storedTimes.elementAt(storedTimes.size()-1)).longValue();
		lastTime1=((Long)storedTimes.elementAt(storedTimes.size()-2)).longValue();
      
	
		if (Math.abs(slope(lastTime1,lastValue1,currentTime,currentValue)-slope(lastTime0,lastValue0,currentTime,currentValue))<0.01){
		    storedTimes.setElementAt(new Long(currentTime),storedTimes.size()-1);
		    storedValues.setElementAt(new Float(currentValue),storedValues.size()-1);
		}
		else{
		    storedTimes.addElement(new Long(currentTime));
		    storedValues.addElement(new Float(currentValue));
		}

		if (currentTime>maxTime){
		    maxTime+=timeScale;
		    minTime+=timeScale;
		    long time0,time1;
		    float val0,val1,newVal;
		    time0=((Long)storedTimes.elementAt(0)).longValue();
		    time1=((Long)storedTimes.elementAt(1)).longValue();
		    val0=((Float)storedValues.elementAt(0)).floatValue();
		    val1=((Float)storedValues.elementAt(1)).floatValue();

		    if (time0<minTime){
			newVal=slope(time0,val0,time1,val1)*(minTime-time0)+val0;
			time0=minTime;
			storedTimes.setElementAt(new Long(time0),0);
			storedValues.setElementAt(new Float(newVal),0);
			if (time1<time0){
			    storedTimes.removeElementAt(0);
			    storedValues.removeElementAt(0);
			}
		    }
		}
	    }
	    else{
		if (storedTimes.size()==0){
		    this.startTime=System.currentTimeMillis();
		    if (DEBUG) System.out.println("Setting startTime");
		    currentTime=minTime;
		}
		storedTimes.addElement(new Long(currentTime));
		storedValues.addElement(new Float(currentValue));
	    }
	}
	public long getTimeScale(){
	    return this.timeScale;
	}

	public void update(Graphics g){
	    Dimension d = getSize();
	    Point lastPoint,currentPoint;
	    if ( (offGraphics == null)
		 || (d.width != offDimension.width)
		 || (d.height != offDimension.height) ) {
		if (DEBUG) System.out.println("resetting offGraphics");
		offDimension = d;
		offImage = createImage(d.width, d.height);
		offGraphics = offImage.getGraphics();

		offGraphics.setColor(getBackground());
		offGraphics.fillRect(0, 0, d.width, d.height);
		offGraphics.setColor(Color.black);

		this.valueScaleFactor=(d.height/(maxValue-minValue));
		this.timeScaleFactor=(d.width*(float)timeScale/(float)(maxTime-minTime));
		if (DEBUG) System.out.println("TSF="+timeScaleFactor+" VSF="+this.valueScaleFactor);
		if (DEBUG) System.out.println("width="+d.width+" check:"+calcPlotPoint(new Long(maxTime),new Float(0.0f)));
	    }
	    //Erase the previous image.
	    offGraphics.setColor(getBackground());
	    offGraphics.fillRect(0, 0, d.width, d.height);
	    offGraphics.setColor(Color.black);

	    //draw every point
	    int i;
	    lastPoint=new Point(0,0);
	    if (storedTimes.size()>0){
		lastPoint=calcPlotPoint((Long)storedTimes.elementAt(0),(Float)storedValues.elementAt(0));
	    }
	    for (i=1;i<storedTimes.size();i++){
		currentPoint=calcPlotPoint((Long)storedTimes.elementAt(i),(Float)storedValues.elementAt(i));
		offGraphics.drawLine(lastPoint.x,lastPoint.y,currentPoint.x,currentPoint.y);
		lastPoint=currentPoint;
	    }
	    g.drawImage(offImage, 0, 0, this);

	}
	
    }/*end WaveForm*/
    protected float slope(float x0,float y0,float x1,float y1){
	return (y1-y0)/(x1-x0);
    }

    public static void main(String[] args){
	WaveFrame wf=new WaveFrame();
	wf.show();
	wf.start();
	for (int i=1;i<1000;i++){
	    wf.update((float)i/1000);
	    try{
		Thread.sleep(10);
	    }catch(InterruptedException ie){}
	}
	System.out.println("Done");
	wf.stop();
    }

}/*end WaveFrame */



