
import java.awt.*;
import java.applet.*;
import java.io.*;
import java.net.*;

/**
 * Heart.java
 *
 *
 * Created: Wed May 26 16:23:31 1999
 *
 * @author Jan Newmarch
 * @version 2.8
 *    removed socket reads, to use a URL from a file
 */

public class Heart extends Applet {

    static final String VERSION = "2.8";
    static final int DEFAULT_SLEEP_TIME = 25; // milliseconds
    static int sleepTime; // for use by painter

    boolean doConsole = false;
    int[] points = null;
    Painter painter = null;

    String heartRate = "--";

    public Heart() {
    }

    private boolean getBool(String paramName) {
	String value = getParameter(paramName);
	return (value != null) && Boolean.valueOf(value).booleanValue();
    }

    private int getInt(String paramName, int defaultVal) {
	String value = getParameter(paramName);
	if (value != null) {
	    return Integer.valueOf(value).intValue();
	} else {
	    return defaultVal;
	}
    }

    public void init() {
	doConsole = getBool("doConsole");
    }

    public void setHeartRate(int rate) {
	if (rate > 20 && rate <= 250) {
	    heartRate = "Heart Rate: " + rate;
	} else {
	    heartRate = "Heart Rate: --";
	}
	// ? ask for repaint? 
    }

    void indicateStatus(String s) {
	try {
	    showStatus(s);
	    if (doConsole) {
		System.out.println(s);
	    }
	} catch (RuntimeException ex) {
	    // don't propagate it
	}
    }

    void quit(Exception ex, String comment) {
	showStatus("Applet quit -- got " + ex + " while " + comment);
	// Really ought to display this in the applet window as well
    }

    public void start() {
	indicateStatus("ECG Applet v" + VERSION);
	int SAMPLE_SIZE = 300 / Toolkit.getDefaultToolkit().
	                                            getScreenResolution();
	int width = size().width;
	// capture points in an array, for redrawing in app if needed
	points = new int[width * SAMPLE_SIZE];
	for (int n = 0; n < width; n++) {
	    points[n] = -1;
	}

	URL url = getCodeBase();

	String hostName = null;
	int port = 0;
	try {
	    indicateStatus("Extracting host for ECG server...");
	    hostName = url.getHost();
	    port = url.getPort();
	} catch (RuntimeException ex) {
	    quit(ex, "extracting host for ECG server");
	    return;
	}

	InetAddress ipAddress = null;
	try {
	    indicateStatus("Looking up host for ECG server...");
	    ipAddress = InetAddress.getByName(hostName);
	} catch (UnknownHostException ex) {
	    quit(ex, "looking up host for ECG server (!?)");
	    return;
	} catch (RuntimeException ex) {
	    quit(ex, "looking up host for ECG server");
	    return;
	}

	URL dataUrl = null;
	InputStream in = null;
	String dataFile = getParameter("dataFile");
	try {
	    indicateStatus("Connecting to ECG server...");
	    // sock = new Socket(ipAddress, DATA_PORT);
	    dataUrl = new URL("http", hostName, port, dataFile);
	    in = dataUrl.openStream();
	} catch (Exception ex) {
	    ex.printStackTrace();
	    quit(ex, "connecting to ECG server");
	    return;
	}

	try {
	    indicateStatus("Connected to ECG server, reading data...");
	    sleepTime = getInt("sleepTime", DEFAULT_SLEEP_TIME);
	    painter = new Painter(this, in);
	    painter.start();
	} catch (Exception ex) {
	    quit(ex, "fetching data from ECG server");
	    try {
		// sock.close();
	    } catch (Exception dontCare) {
		// quietly swallow it
	    }
	    return;
	}
    }

    public void stop() {
	indicateStatus("Stopping ECG applet...");
	if (painter != null) {
	    painter.stopWork();
	    painter = null;
	}
    }
    
    public void paint(Graphics g) {

	/*
	g.setColor(Color.black);
	g.drawString("Hi Mom", 0, 400);
	if (heartRate != null) {
	    g.setColor(Color.black);
	    g.drawString(heartRate, 0, 200);
	}
	*/

	if (points == null) 
	    return;
	int SAMPLE_SIZE = 300 / Toolkit.getDefaultToolkit().
	                                            getScreenResolution();
	g.setColor(Color.red);
	int min = 127;
	int max = -128;

	for (int n = 0; n < points.length; n++) {
	    int x = n + 1; 
	    int magnitude = points[n];
	    if (magnitude == -1) {
		return;
	    }
	    
	    if (x % SAMPLE_SIZE == 0) {
		// draw only on multiples of sample size
		// System.out.println("reading");
		// System.out.println("Drawing at " + x + "," + magnitude);
		// repaint(x, 0, 5, height);
		// g.clearRect(x, 0, CLEAR_AHEAD, height);
		// g.drawLine(x, magnitude, x+1, magnitude+1);
		// Data is in the range -128 .. 127. Need to
		// draw so -128 is at the bottom, 127 at the top
		int x0 = x / SAMPLE_SIZE;
		g.drawLine(x0, min, x0, max);
		min = 127;
		max = -128;
	    } else {
		if (magnitude > max) max = magnitude;
		if (magnitude < min) min = magnitude;
	    }
	    //g.fillRect(n, points[n], 1, 5);
	}
    }    
} // Heart

class Painter extends Thread {

    static final int CLEAR_AHEAD = 15;
    static final int MAX = 255;
    static final int MIN = 0;
    final int READ_SIZE = 10;

    protected Heart app;
    protected Socket sock;
    protected InputStream in;
    protected final int RESOLUTION = Toolkit.getDefaultToolkit().
	                                            getScreenResolution();
    protected final int UNITS_PER_INCH = 125;
    protected final int SAMPLE_SIZE = 300 / RESOLUTION;
    protected boolean keepRunning = true;
    protected int sleepTime = Heart.sleepTime;
    
    public Painter(Heart app, InputStream in) throws Exception {
	this.app = app;
	this.in = in;
	/*
	this.sock = sock;
	try {
	    this.in = sock.getInputStream();
	} catch (Exception ex) {
	    try {
		sock.close();
	    } catch (Exception dontCare) {
		// toss it
	    }
	    throw ex;
	}
	*/
    }

    public void stopWork() {
	keepRunning = false;

	app.indicateStatus("Closing connection to ECG server...");
	try {
	    if (sock != null) {
		sock.close();
		sock = null;
	    }
	} catch (Exception ex) {
	    // hide it
	}
    }

    public void run() {
System.err.println("resolution " + RESOLUTION + " units " + UNITS_PER_INCH);
	int height = app.size().height;
	int width = app.size().width;
	int x = 1; // start at 1 rather than 0 to avoid drawing initial line
	           // from -128 .. 127
	int magnitude;
	int nread;
	int max = MIN; // top bound of magnitude
	int min = MAX;  // bottom bound of magnitude
	int oldMax = MAX + 1;
	byte[] data = new byte[READ_SIZE];
	Graphics g = app.getGraphics();
	g.setColor(Color.red);
	try {
	    Font f = new Font("Serif", Font.BOLD, 20);
	    g.setFont(f);
	} catch (Exception ex) {
	    // ....
	}



	try {
            boolean expectHR = false;   // true ==> next byte is heartrate

	    while (keepRunning && (nread = in.read(data)) != -1) {
		for (int n = 0; n < nread; n++) {
		    int thisByte = data[n] & 0xFF;
		    if (expectHR) {
			expectHR = false;
			app.setHeartRate(thisByte);
			if (thisByte <= 250) {
			    if (thisByte < 40) {
				app.indicateStatus("(No heart rate at present)");
			    } else {
				app.indicateStatus("Heartrate: " + thisByte);
			    }
			} else {
			    // ===> magic values, e.g. server disconnected
			    app.indicateStatus("(May have lost signal from mobile phone)");
			}
			continue;
		    } else if (thisByte == 255) {
			expectHR = true;
			continue;
		    }
		    
		    // convert to y value, with y increasing downwards
		    // convert to signed

		    // we are reading bytes, from -127..128
		    // conver to unsigned
		    magnitude = thisByte;

		    // then convert to correct scale
		    magnitude -= 128;
		    // scale and convert to window coord from the top downwards
		    int y = ((128 - magnitude) * RESOLUTION) / UNITS_PER_INCH;
		    app.points[x] = y;
		    // System.err.println("" + y);		
		    if (x % SAMPLE_SIZE == 0) {
			Thread.sleep(sleepTime);
			// draw only on multiples of sample size
			// System.out.println("reading");
			// System.out.println("Drawing at " + x + "," + magnitude);
			// repaint(x, 0, 5, height);
			// g.drawLine(x, magnitude, x+1, magnitude+1);
			// Data is in the range -128 .. 127. Need to
			// draw so -128 is at the bottom, 127 at the top
			int x0 = x / SAMPLE_SIZE;
			g.clearRect(x0, 0, CLEAR_AHEAD, height);
			if (oldMax != MAX + 1) {
			    g.drawLine(x0-1, oldMax, x0, min); 
			}
			g.drawLine(x0, min, x0, max);
			oldMax = max;
			min = 1000;
			max = -1000;
			if (app.heartRate != null) {
			    g.setColor(Color.black);
			    g.clearRect(0, 180, 200, 100);
			    g.drawString(app.heartRate, 0, 220);
			    g.setColor(Color.red);
			}
		    } else {
			if (y > max) max = y;
			if (y < min) min = y;
		    }
		    if (++x >= width * SAMPLE_SIZE) {
			x = 0;
		    }
		}
	    }
	    // ===> System.out.println("Stopped");
	} catch(Exception ex) {
	    // ===> should do something better...
	    if (! (ex instanceof SocketException)) {
		System.out.println("Applet quit -- got " + ex);
	    }
	} finally {
	    app.indicateStatus("Applet cleaning up...");
	    try {
		if (in != null) {
		    in.close();
		    in = null;
		}
	    } catch (Exception ex) {
		// hide it
	    }
	    try {
		if (sock != null) {
		    sock.close();
		    sock = null;
		}
	    } catch (Exception ex) {
		// hide it
	    }
	    app.indicateStatus("ECG applet stopped successfully");
	}
    }
}
