//******************************************************************************
// heartMesh.java:	Applet
//
//******************************************************************************
import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.URL;

//==============================================================================
// Main Class for applet heartMesh
//
//==============================================================================
public class heartMesh extends Applet implements Runnable
{

	boolean m_loadedFile = false, m_loadingError = false;

	private int m_xPrev, m_yPrev;	 // mouse position at MouseDown
	float totalScale = 1.0f;

	int m_sampleCount = 0;
	int m_pointCount = 0;
	int m_lineCount = 0;
	int[][] m_lines;			// int[m_lineCount][2]
	int m_time = 0;


	private Matrix3D m_Rot;	    
	private Matrix3D m_RotDisp;	
	private float[][] m_xt;			// float[m_sampleCount][3*m_pointCount]	 time samples
									// of points, each row is: x1 y1 z1 x2 y2 z2 ... xn yn zn 
	private float[]   m_x;			// float[3*m_pointCount]  points for one time moment
	private int  []   m_xDisp;		// float[3*m_pointCount]  rotated m_x

	int m_height, m_width;
	Image buf;						// double buffering
	Graphics gbuf;


	// THREAD SUPPORT:
	//		m_plotFromFile	is the Thread object for the applet
	//--------------------------------------------------------------------------
	Thread	 m_plotFromFile = null;

	// PARAMETER SUPPORT:
	private String m_dataFilename = "myFile.txt";
	private float m_scale = 1.0f;
	private int m_fps = 30;

    // Parameter names.  To change a name of a parameter, you need only make
	// a single change.  Simply modify the value of the parameter string below.
    //--------------------------------------------------------------------------
	private final String PARAM_dataFilename = "dataFilename";
	private final String PARAM_scale = "scale";
	private final String PARAM_fps   = "fps";

	// plotFromFile Class Constructor
	//--------------------------------------------------------------------------
	public heartMesh()
	{
		// TODO: Add constructor code here
		m_RotDisp   = new Matrix3D();
		m_RotDisp.unit();

		// Initializing Rot to eye matrix
		m_Rot		= new Matrix3D();
		m_Rot.unit();
	}
	protected void finalize() throws Throwable {
		if ( m_RotDisp != null )   m_RotDisp = null;	
		if ( m_Rot     != null )   m_Rot     = null;
		if ( m_x       != null )   m_x       = null;
		if ( m_xDisp   != null )   m_xDisp   = null;
		if ( m_xt      != null ) {
			for ( int i = 0; i < m_sampleCount ; ++i ) m_xt[i] = null;
			m_xt      = null;
		}
	}
	// APPLET INFO SUPPORT:
	//		The getAppletInfo() method returns a string describing the applet's
	// author, copyright date, or miscellaneous information.
    //--------------------------------------------------------------------------
	public String getAppletInfo()
	{
		return "Name: plotFromFile\r\n" +
		       "Author: Laza\r\n" +
		       "Created with Microsoft Visual J++ Version 1.0";
	}

	// PARAMETER SUPPORT
	//		The getParameterInfo() method returns an array of strings describing
	// the parameters understood by this applet.
	//
    // plotFromFile Parameter Information:
    //  { "Name", "Type", "Description" },
    //--------------------------------------------------------------------------
	public String[][] getParameterInfo()
	{
		String[][] info =
		{
			{ PARAM_dataFilename, "String", "Data filename (text)" },
			{ PARAM_scale, "float", "Scaling factor" },
			{ PARAM_fps, "int", "Frames per second" },
		};
		return info;		
	}

	// The init() method is called by the AWT when an applet is first loaded or
	// reloaded.  Override this method to perform whatever initialization your
	// applet needs, such as initializing data structures, loading images or
	// fonts, creating frame windows, setting the layout manager, or adding UI
	// components.
    //--------------------------------------------------------------------------
	public void init()
	{
		// PARAMETER SUPPORT
		//----------------------------------------------------------------------
		String param;

		// dataFilename: Data filename (text)
		//----------------------------------------------------------------------
		param = getParameter(PARAM_dataFilename);
		if (param != null)
			m_dataFilename = param;

		// scale: 
		//----------------------------------------------------------------------
		param = getParameter(PARAM_scale);
		if (param != null)
			m_scale = (new Float(param)).floatValue()		;

		// PARAM_fps: Frames per second
		//----------------------------------------------------------------------
		param = getParameter(PARAM_fps);
		if (param != null)
			m_fps = Integer.parseInt(param);

		//		resize(500, 500);

		// TODO: Place additional initialization code here
		m_height = size().height;
		m_width	 = size().width;
		buf = createImage(m_width, m_height );
		gbuf = buf.getGraphics();

	}

	// Place additional applet clean up code here.  destroy() is called when
	// when you applet is terminating and being unloaded.
	//-------------------------------------------------------------------------
	public void destroy()
	{
		// TODO: Place applet cleanup code here
	}


	//		The start() method is called when the page containing the applet
	// first appears on the screen. The AppletWizard's initial implementation
	// of this method starts execution of the applet's thread.
	//--------------------------------------------------------------------------
	public void start()
	{
		if (m_plotFromFile == null)
			m_plotFromFile = new Thread(this);
		m_plotFromFile.start();		// start the thread even if it existed before
	}
	
	//		The stop() method is called when the page containing the applet is
	// no longer on the screen. The AppletWizard's initial implementation of
	// this method stops execution of the applet's thread.
	//--------------------------------------------------------------------------
	public void stop()
	{
		m_plotFromFile = null;
		// TODO: Place additional applet stop code here
	}

	// THREAD SUPPORT
	//		The run() method is called when the applet's thread is started. If
	// your applet performs any ongoing activities without waiting for user
	// input, the code for implementing that behavior typically goes here. For
	// example, for an applet that performs animation, the run() method controls
	// the display of images.
	//--------------------------------------------------------------------------
	public void run()
	{
		showStatus("plotFromFile: running");
		// Netscape calls run() every time the window is resized
		if( !m_loadedFile && !m_loadingError ) {
			m_loadedFile = ReadFile(m_dataFilename);
			if ( !m_loadedFile ) {
				m_loadingError = true;
				repaint();
				stop();
			} 
		}

		while ( Thread.currentThread() == m_plotFromFile)
		{
			if ( ++m_time >= m_sampleCount )
					m_time = 0; 
			m_x = m_xt[m_time];
			repaint();		// drawing the 3D model	

			try  { Thread.currentThread().sleep(1000/m_fps);}
			catch (InterruptedException e)  {  break;	}
		}
	}


	/** synchronized void updatePoints( int t ) 
		copies points to be plotted:
	*/
/*	synchronized void UpdatePoints( int t ) 
	{
		m_x = m_xt[t];			
	}
*/
	// plotFromFile Paint Handler
	//--------------------------------------------------------------------------

	public void update(Graphics g)	{
		paint(g);
	}

	
	public void paint(Graphics g){

		if (m_loadingError){
			g.drawString("paint(): Error loading file " + m_dataFilename , 10,40);
			showStatus("paint(): Error loading file " + m_dataFilename );
		}
		else if ( m_loadedFile ) {		// drawing the model

			try {
				synchronized(m_RotDisp) {
					m_RotDisp.transform(m_x, m_xDisp, m_pointCount);
				}					// RotatePoints();

				// gbuf.setColor(Color.black);		// clear buffer image
				gbuf.setColor(getBackground());		// clear buffer image
				gbuf.fillRect(0, 0, m_width, m_height);
				PlotLines(gbuf);				// draw model to buffer
				gbuf.drawString(
					"Drag to rotate / Right button drag to zoom", 2, m_height-5);
				gbuf.drawString("Scale = " + String.valueOf(m_scale), 2, 12);

				g.drawImage(buf, 0, 0, this);	// copy buffer to screen

			}
			catch(Exception e){ 	 
				g.drawString("Exception cought " + e.toString() , 10,40);
				System.out.println("Exception cought " + e.toString());
				showStatus("Exception cought " + e.toString());
			}
		}
		else 
			g.drawString("Loading file " + m_dataFilename + " fps="+m_fps, 10,40);
	}


/*
	void  RotatePoints() 
	{
		synchronized(m_RotDisp) {
			m_RotDisp.transform(m_x, m_xDisp, m_pointCount); 
		}
	}
*/

	void PlotLines(Graphics g) 
	{
		int xOffset = (int)(size().width / 2.0);
		int yOffset = (int)(size().height/ 2.0);
		int zmin, zmax;
  
		g.setColor(Color.black);
		zmin = m_xDisp[2], zmax = m_xDisp[2];
		for( int i = 1; i < m_pointCount ; ++i ){
			 if ( m_xDisp[3*i+2] < zmin ) zmin = m_xDisp[3*i+2];
			 if ( m_xDisp[3*i+2] > zmax ) zmax = m_xDisp[3*i+2];
		}
		for( int i = 0; i < m_lineCount ; ++i ){
			float grayLevel = 0.5f*0.7f * (float)
				(m_xDisp[m_lines[i][0]*3+2]+m_xDisp[m_lines[i][1]*3+2] - 2*zmin)
			  / (zmax - zmin);
			g.setColor(new Color(grayLevel,grayLevel,grayLevel));
			g.drawLine( xOffset + (int)(m_scale * m_xDisp[m_lines[i][0]*3  ])
				,		yOffset - (int)(m_scale * m_xDisp[m_lines[i][0]*3+1])
				,		xOffset + (int)(m_scale * m_xDisp[m_lines[i][1]*3  ])
				,		yOffset - (int)(m_scale * m_xDisp[m_lines[i][1]*3+1]) );
			//  y-axis faces south => yToPlot = yOffset-y
		}
		g.setColor(Color.black);
  
	}



	public boolean mouseDown(Event evt, int x, int y)
	{	
		// save current mouse position
		m_xPrev = x; m_yPrev = y; 	  
		return true;	
	}


	public boolean mouseDrag(Event evt, int x, int y)
	{

		if (evt.metaDown()){
			m_scale *= (float) Math.exp((double)(m_yPrev-y) / (double)m_height);
			m_xPrev = x; m_yPrev = y;
		}

		else {
			m_Rot.unit();
			m_Rot.xrot((y - m_yPrev)* 360.0f*0.5f / size().width );
			m_Rot.yrot((m_xPrev - x)* 360.0f*0.5f / size().width );
			m_xPrev = x; m_yPrev = y;

			synchronized(m_RotDisp) {
				m_RotDisp.mult(m_Rot); // save some time 
			}
		}

		repaint();
		return true;
	}



	/// boolean ReadFile(String filename)
	/// reads from the file values for the variables
	///		  m_lineCount, m_pointCount, m_sampleCount
	/// and arrays
	///		m_lines[][2] with lines designated by the indices of the endpoints
	///	and m_xt[][]  points with x,y,z coordinates for each sample

	boolean ReadFile(String filename) 
	{
		showStatus("plotFromFile: reading file "+filename);
		InputStream is = null;
		System.out.println("ReadFile");
		try{
			is = new URL(getDocumentBase(), filename).openStream();
			StreamTokenizer st = new StreamTokenizer(is);
			st.eolIsSignificant(false);
			st.commentChar('#');

			// Loading connections between points.
			// Point counting starts from one => nval-1
			// format:
			//		connections N
			//		pt11, pt12
			//		pt21, pt22
			//		...
			//		ptN1, ptN2
			while(	st.nextToken()	!= StreamTokenizer.TT_EOF 
				&&	st.ttype		!= StreamTokenizer.TT_WORD
				&&  ! "connections".equals(st.sval)); 
			if( ! "connections".equals(st.sval)) {
				System.out.println(" 'connections' keyword not found ");
				throw new Exception();
			}
			if( st.nextToken() != StreamTokenizer.TT_NUMBER ) throw new Exception();
			m_lineCount = (int) st.nval;
			
			m_lines = new int[m_lineCount][2];
			System.out.println("Loading "+m_lineCount+" lines" );
			for ( int i = 0; i < m_lineCount; ++i ) {
				if( st.nextToken() != StreamTokenizer.TT_NUMBER ) throw new Exception();
				m_lines[i][0] = (int) st.nval -1;
				// allowing for the numbers to be separated by a non-number token
				if( st.nextToken() == StreamTokenizer.TT_NUMBER ) st.pushBack();
				if( st.nextToken() != StreamTokenizer.TT_NUMBER ) throw new Exception();
				m_lines[i][1] = (int) st.nval -1;	 
			}

			// Loading points.
			// format:
			//		points M   m_sampleCount
			//		x1 y1 z1  x2 y2 z2 ... xM yM zM
			//		x1 y1 z1  x2 y2 z2 ... xM yM zM
			//		...
			//		x1 y1 z1  x2 y2 z2 ... xM yM zM
			// there is total of m_sampleCount rows of point coordinates
			while(	st.nextToken() != StreamTokenizer.TT_EOF 
				&&	st.ttype != StreamTokenizer.TT_WORD
				&&  ! "points".equals(st.sval));
			if( st.nextToken() != StreamTokenizer.TT_NUMBER ) throw new Exception();
			m_pointCount = (int) st.nval;	
			if( st.nextToken() != StreamTokenizer.TT_NUMBER ) throw new Exception();
			m_sampleCount= (int) st.nval;

			m_x		= new float[3*m_pointCount];
			m_xDisp = new int  [3*m_pointCount];
			m_xt	= new float[m_sampleCount][3*m_pointCount];

			while(	st.nextToken() != StreamTokenizer.TT_EOF 
				&&	st.ttype != StreamTokenizer.TT_NUMBER );
			st.pushBack();		// push back the first number found

			st.eolIsSignificant(true);

			System.out.println("Loading "+m_pointCount+" points and "+m_sampleCount+" samples" );
			for( int t=0; t < m_sampleCount; ++t ) {
				for( int j=0; j < m_pointCount; ++j ) {	

					if( st.nextToken() != StreamTokenizer.TT_NUMBER ) throw new Exception();
					m_xt[t][3*j+0] =  (float) st.nval;	 // x-coordinate
					if( st.nextToken() != StreamTokenizer.TT_NUMBER ) throw new Exception();
					m_xt[t][3*j+1] =  (float) st.nval;	 // y-coordinate
					if( st.nextToken() != StreamTokenizer.TT_NUMBER ) throw new Exception();
					m_xt[t][3*j+2] =  (float) st.nval;	 // z-coordinate
				}
				if( st.nextToken() != StreamTokenizer.TT_EOL    ) throw new Exception();
			}
			is.close();
			System.out.println("Loaded "+m_lineCount+" lines, "
					+m_pointCount+" points, " + m_sampleCount+" samples");
		}
		catch (Exception e) {
			try{ is.close(); } 	catch (Exception f) {}
			showStatus("plotFromFile: ERROR while reading file "+filename);

			return false;
		}
		showStatus("plotFromFile: file "+filename+" read successfully");

		return true;
	}




}
