/*
  This file ships under the GNU-COPYLEFT.
  See: http://www.gnu.org/

  Last Changes:
  2002-11-9
  The Thread functions stop and resume are deprecated.
  Have to stop the wave via some new variable `stopWave'.
  See
  http://java.sun.com/j2se/1.4.1/docs/guide/misc/threadPrimitiveDeprecation.html
  for further details.

  2002-12-1
  Hack from Niels Neumann:
  Fix image size
 */

import java.applet.Applet;
import java.lang.*;
import java.util.Vector;
import java.awt.Graphics;
import java.awt.FontMetrics;
import java.awt.Image;
import java.awt.Color;
import java.awt.Dimension;

public class Plot extends Applet implements Runnable
{
    // wave_action == NOT_RUNNING or wave_action == NOT_SET stops the
    // wave. (time freezes)
    public static final int NOT_SET = 0;
    public static final int NOT_RUNNING = 1;
    public static final int RUNNING = 2;

    /*
      Niels Neumann
      Hack for the MS java engine:
     */
    public static final int default_size_width = 320;
    public static final int default_size_height = 200;
    

    public int wave_action = NOT_SET;

    public boolean border = true;
    public boolean xlabels = true;
    public boolean xzeroaxis = true;
    public boolean ylabels = true;
    public boolean yzeroaxis = true;
    public boolean tlabel = true;

    // Private variables:
    private boolean _Im_running=false;
    private Image _image_buffer, _coordinate_system_buffer;
    private Dimension _size;
    private Dimension _graph_size,_graph_offset;
    private Dimension _plot_size,_plot_offset;
    protected Graphics _graphics;

    private double _xmin=0;
    private double _xmax=0;
    private double _delta_x=0;
    private double _dix_by_dx=0;

    private double _x_tic_min=0;
    private double _delta_x_tic=0;

    private double _ymin=0;
    private double _ymax=0;
    private double _delta_y=0;
    private double _diy_by_dy=0;

    private double _y_tic_min=0;
    private double _delta_y_tic=0;

    private int _tic_length=2;

    private Dimension _t_label_pos;

    private double _p_start=0;
    private double _p_end=0;
    private double _delta_p=0;

    private double _t=0;
    public double t_min=0;
    public double t_max=0;
    public double delta_t=0;
    private int _sleep_time=50; // in milisecs

    public Thread _thread=null;
    // Public stuff:

    // Put Your functions to plot into graphs:
    public Vector graphs;
    // Put additional lines into lines:
    public Vector lines;

    Vector _y_tics=null;
    Vector _x_tics=null;


    // The default function:
    public class PlotFunction
    {
	public Color color=Color.red;
	
	public double fx(double par, double time)
	{
	    return par;
	}
	public double fy(double par, double time)
	{
	    return 0.0;
	}
	
    }

    // lines:
    public class Line
    {
	public double x1,x2,y1,y2;
	public Color color;
	public Line(double _x1,double  _y1,double  _x2,double  _y2, Color _color )
	{
	    x1=_x1;
	    y1=_y1;
	    x2=_x2;
	    y2=_y2;
	    color=_color;
	    
	}
	public Line(double _x1,double  _y1,double  _x2,double  _y2)
	{
	    x1=_x1;
	    y1=_y1;
	    x2=_x2;
	    y2=_y2;
	    color=Color.black;
	}
	public Line(Line l)
	{
	    x1=l.x1;
	    x2=l.x2;
	    y1=l.y1;
	    y2=l.y2;
	    color=l.color;
	}
    };

    private class AxisLabel
    {
	public double val;
	public String str;
	public int index;
	public AxisLabel(double _val, String _str, int _index)
	{ val=_val; str=_str; index=_index; }
    }

    public double p_to_x(double p)
    {
	return _xmin + (_xmax-_xmin)/(_p_end-_p_start)*(p-_p_start);
    }

    public void set_x_range(double xmin,double xmax)
    {
	_xmin=xmin;
	_xmax=xmax;
    }

    public void set_x_tics(double xmin,double deltax)
    {
	_x_tic_min=xmin;
	_delta_x_tic=deltax;
    }

    public double get_x_min()
    {
	return _xmin;
    }
 
    public double get_x_max()
    {
	return _xmin;
    }
    
    public int ix_coord(double x)
    {
	return _plot_offset.width + (int) (((double) _plot_size.width) * (x-_xmin)/(_xmax-_xmin));
    }
    
    public double[] get_x_range()
    {
	double[] range = new double[2];
	range[0]=_xmin;
	range[1]=_xmax;
	return range;
    }

    public void set_y_range(double ymin,double ymax)
    {
	_ymin=ymin;
	_ymax=ymax;
	_delta_y=ymax-ymin;
    }

    public void set_y_tics(double ymin,double deltay)
    {
	_y_tic_min=ymin;
	_delta_y_tic=deltay;
    }

    public Dimension graph_offset() {return _graph_offset; }
    public Dimension graph_size()   {return _graph_size;   }

    public void set_graph_dimensions( Dimension graph_offset, Dimension graph_size )
    {
	_graph_offset=graph_offset;
	_graph_size = graph_size;
	_create_coordinate_system();
    }

    private void _create_coordinate_system()
    {
	int i;
	FontMetrics fm = _graphics.getFontMetrics();
	int fontHeight= fm.getHeight();
	double val;
	String str;
	double half_string_length;
	int _ix_offset;

	_plot_size.height = _graph_size.height - fontHeight - _tic_length;
	_plot_offset.height = _graph_offset.height;
	
	_y_tics.removeAllElements();
	// y-Axis: Generate labels:
	for(val=_y_tic_min;
	    val < _ymax - (_ymax-_ymin)*0.5*((double)fontHeight)/((double)_plot_size.height);
	    val += _delta_y_tic )
	    if( val >= _ymin )
		_y_tics.addElement( new AxisLabel( val,String.valueOf(val),iy_coord(val)+fontHeight/2) );
	
	int width1,width2;
	// Find maximum widht of labels:
	for(_ix_offset=0,i=0;i<_y_tics.size();i++)
	    {
		width2=fm.stringWidth(((AxisLabel)_y_tics.elementAt(i)).str);
		    if( width2 > _ix_offset )
			_ix_offset = width2;
	    }
	_ix_offset += _tic_length;

	_plot_size.width = _graph_size.width - _ix_offset;
	_plot_offset.width = _graph_offset.width + _ix_offset;

	// Now we know _plot_offset.width. Therefore we are ready to compute the
	// locations of the x labels and the tlabel:
	_x_tics.removeAllElements();
	val=_x_tic_min;
	do{
	    str=String.valueOf(val);
	    half_string_length=0.5*(_xmax-_xmin)*((double) fm.stringWidth(str))
		/
		((double) _plot_size.width);
	    if( val > _xmax-half_string_length ) break;
	    _x_tics.addElement(
			       new AxisLabel(val,str,
					     ix_coord(val-half_string_length)
					     )
				   );
	    val += _delta_x_tic;
	}while(true);

	_t_label_pos=new Dimension(_plot_offset.width+2,(3*fontHeight)/2);

	repaint();
    } // end of method _create_coordinate_system

    public int iy_coord(double y)
    {
	return _plot_offset.height + (int) (((double) _plot_size.height)*(_ymax-y)/(_ymax-_ymin));
    }

    public double[] get_y_range()
    {
	double[] range = new double[2];
	range[0]=_ymin;
	range[1]=_ymax;
	return range;
    }

    public void set_p_range(double p_start,double p_end,double delta_p)
    {
	_p_start=p_start;
	_p_end=p_end;
	_delta_p=delta_p;
	repaint();
    }
    
    public void set_t_range(double _tmin,double _tmax,double _deltat)
    {
	t_min=_tmin;
	t_max=_tmax;
	delta_t=_deltat;
    }

    public void drawFunction(PlotFunction fg)
    {
	double p;
	int ix_previous = ix_coord( fg.fx(_p_start,_t) );
	int iy_previous = iy_coord( fg.fy(_p_start,_t) );
	int ix;
	int iy;

	Color save_color = _graphics.getColor();
	_graphics.setColor(fg.color);

	
	for(p=_p_start;
	    ( (_delta_p > 0.0) && (p < _p_end) )
		||
		( (_delta_p < 0.0) && (p < _p_end) );
	    p += _delta_p)
	    {
		ix = ix_coord( fg.fx( p, _t ) );
		iy = iy_coord( fg.fy( p, _t ) );
		_graphics.drawLine(ix_previous,iy_previous,ix,iy);
		ix_previous = ix;
		iy_previous = iy;
	    }
	_graphics.setColor(save_color);
    }


    /**************************************************
		  Methods of Applet
    **************************************************/

    public Plot()
    {
	// System.out.println("Plot()");
	_plot_size = new Dimension();
	_plot_offset = new Dimension();
	graphs = new Vector();
	lines = new Vector();
	_x_tics = new Vector();
	_y_tics = new Vector();
    }

    public void init()
    {
	int i;
	_size=this.getSize(); // This will never change...
	/*
	  Hack of Niels Neumann.
	  The java engine of MS returns (0,0) as this.getSize()...
	  Therefore:
	 */

	if( _size.width == 0 || _size.height == 0 )
	    {
		_size.width = default_size_width;
		_size.height = default_size_height;
	    }

	//////////////////////////////////////////////////////////////////////
	// Double buffering:
	_image_buffer = this.createImage( _size.width,
					    _size.height);
	_graphics = _image_buffer.getGraphics();


	//////////////////////////////////////////////////////////////////////
	// Coordinate system
	/* The graph window takes the whole applet size:
	   You can change this by set_graph_size().
	   Setting graph size and
	   creating the coordinate system:
	*/
	set_graph_dimensions(
			      new Dimension(0,0), // _graph_offset
			      _size // _graph_size 
				  );
	//////////////////////////////////////////////////////////////////////

	_Im_running = true;

	// Default parameter range if the parameter range is not set yet.
	// (For non-parametric plots.)
	if( _delta_p == 0.0 )
	    {
		_p_start = _xmin;
		_p_end = _xmax;
		_delta_p = (_xmax-_xmin)/((double)_size.width);
	    }

	_t = t_min;

    }

    public void start()
    {
	if( _thread == null ) {
	    _thread = new Thread(this, "Plot");
	    _thread.start();
	}

	if( wave_action == NOT_SET ) wave_action = RUNNING;
    }

    public void time_step()
    {
	_t += delta_t;
	repaint();
    }

    public void run()
    {
	// System.out.println("run()");
	Thread mythread = Thread.currentThread();
	while( mythread == _thread )
	    {
		try{
		    _thread.sleep(_sleep_time);
		} catch(InterruptedException _interrupted_exception){}
		
		// toDo:
		// Instead of wave_action
		// The button-events "Weiter" and "Schritt"
		// should be cough via wait(...) and notify
		// That would save cpu-time.
		if( wave_action == RUNNING )
		    {
			// advancing in time:
			_t += delta_t;
			if(_t > t_max ) _t = t_min;
		    }
		
		repaint();
	    }
    }

    public void stop()
    {
	// System.out.println("stop()");
	// deprecated: _thread.suspend();
	_thread = null;
    }

    public void destroy()
    {
	// System.out.println("destroy()");
	_thread = null;
	_Im_running = false;
    }
    // update() of class Applet is overwritten for double buffering...
    public void update(Graphics gr)
    {
	// clear image:
	_graphics.setColor( getBackground() );

	_graphics.fillRect(
			   0,0,
			   _size.width,
			   _size.height
			   );

	_graphics.setColor( getForeground() );

	// run plot() on the buffer...
	super.update(_graphics);
	
	// finally show the image:
	// System.out.println("Show the image.");
	gr.drawImage( _image_buffer, 0 , 0, this );
    }

    public void paint(Graphics gr)
    {
	// System.out.println("Running paint()");
	int i,pos1;
	double val;
	// System.out.println("paint()");
	// draw the coordinate system:
	if( border )
	    {
		//x-axis:
		pos1=_plot_offset.height + _plot_size.height;
		gr.drawLine(_plot_offset.width-1,pos1,
			    _plot_offset.width+_plot_size.width-1,pos1);
		//y-axis:
		gr.drawLine(_plot_offset.width-1,_plot_offset.height,
			    _plot_offset.width-1,pos1);
	    }
	if( xlabels ) {
	    pos1=_graph_offset.height+_graph_size.height-1;
		for(i=0;i<_x_tics.size();i++)
		    gr.drawString(
				  ((AxisLabel)_x_tics.elementAt(i)).str,
				  ((AxisLabel)_x_tics.elementAt(i)).index,
				  pos1
				  );
	}
	if( ylabels ) {
	    pos1=_graph_offset.width+1;
	    for(i=0;i<_y_tics.size();i++)
		    gr.drawString(
				  ((AxisLabel)_y_tics.elementAt(i)).str,
				  pos1,
				  ((AxisLabel)_y_tics.elementAt(i)).index
				  );
	}
	if( tlabel ) {
	    gr.drawString(
			  "t=" + String.valueOf(_t),
			  _t_label_pos.width, _t_label_pos.height
			  );
	}
	if( xzeroaxis )
	    {
		pos1=ix_coord(0.0);
		if( pos1 >= _plot_offset.width &&
		    pos1 < _plot_offset.width+_plot_size.width
		    )
		    gr.drawLine(pos1,_plot_offset.height,
				pos1,_plot_offset.height+_plot_size.height-1);
	    }
	if( yzeroaxis )
	    {
		pos1=iy_coord(0.0);
		if( pos1 < _plot_offset.height+_plot_size.height &&
		    pos1 > _plot_offset.height
		    )
		    gr.drawLine(_plot_offset.width,pos1,
				_plot_offset.width+_plot_size.width-1,pos1);
	    }
	
	// Lines:
	for(i=0;i<lines.size(); i++)
	    {
		Line l=(Line)lines.elementAt(i);
		gr.drawLine(ix_coord(l.x1),
				iy_coord(l.y1),
			    ix_coord(l.x2),
			    iy_coord(l.y2)
			    );
	    }
	
	// draw the graphs:
	for(i=0;i<graphs.size();i++)
	    drawFunction((PlotFunction) graphs.elementAt(i));
	
	//toDo: Images fuer Koordinatensystem in gr uebertragen:
    }
};



