import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;

import javax.swing.JFrame;
import javax.swing.JOptionPane;

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.DefaultXYItemRenderer;
import org.jfree.data.time.Second;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.ui.RefineryUtilities;

import processing.app.Preferences;

/***
 * This class displays a window containing a graph of all the data on my
 * Ardunio based data logger.
 * 
 * For more information visit: 
 * http://pskillenrules.blogspot.com/2009/06/arduino-based-data-logger.html
 * 
 * License:
 * You are free to edit and/or redistribute this work or works derived from 
 * this, provided that any redistribution/derived work remains free and open 
 * source, and also contains reference to my work.
 * 
 * @author Patrick Skillen
 * @version 1.0 : 24 June 2009
 *
 */
public class GraphFrame extends JFrame {

	private static final long serialVersionUID = 1735181004793315055L;
	private static final int READ_LDR = 1;
	private static final int READ_RTD = 2;
	private static final long GRAPH_INTERVAL = 15000; // ms

	public static void main(String args[]) {
		GraphFrame frm = new GraphFrame();
		RefineryUtilities.centerFrameOnScreen(frm);
		frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frm.setVisible(true);
	}

	private ArrayList<Float> rtdValues;
	private ArrayList<Float> ldrValues;
	private int readMode;
	private JFreeChart chart;
	private XYPlot plot;
	private long start;

	public GraphFrame() {
		this.setSize(600, 400);
		this.setTitle("Paddy's Data Logger Grapher App");

		rtdValues = new ArrayList<Float>();
		ldrValues = new ArrayList<Float>();
		
		final DefaultXYItemRenderer renderer = new DefaultXYItemRenderer();
		renderer.setBaseShapesVisible(false);


		// create a chart and plot object
		chart = new JFreeChart("RTD and LDR values over time",
				JFreeChart.DEFAULT_TITLE_FONT, new XYPlot(), true);
		plot = chart.getXYPlot();
		plot.setRenderer(renderer);
		plot.setRangeAxis(new NumberAxis("'C / %"));
		plot.setDomainAxis(new DateAxis("Time"));

		
		getContentPane().add(new ChartPanel(chart));
		this.pack();

		// remember the exact time we started the download
		start = new Date().getTime();
		// download the data
		readData();
		// graph the data
		graphData();
	}

	private void readData() {
		// clear any previous data
		rtdValues.clear();
		ldrValues.clear();
		
		final CommPortIdentifier portId;
		final SerialPort port;
		final InputStream input;
		final OutputStream output;

		String s;
		int val;
		int foundData;
		float f;

		readMode = -1;
		foundData = 0;

		Preferences.init();
		System.out.println("Using port: " + Preferences.get("serial.port"));

		try {
			// get a handle on the USB COM port
			portId = CommPortIdentifier.getPortIdentifier(Preferences
					.get("serial.port"));

			port = (SerialPort) portId.open("serial talk", 4000);
			input = port.getInputStream();
			output = port.getOutputStream();
			port.setSerialPortParams(Preferences
					.getInteger("serial.debug_rate"), SerialPort.DATABITS_8,
					SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);

			// On linux and Mac OS systems, connecting to the Arduino over USB
			// will cause a reset. The first 1500ms are taken up by the bootloader
			// waiting for a new program being uploaded. If it doesn't find one,
			// it continues with the boot. I've added an extra second so that
			// The setup() routine can finish and the device boots into a running
			// state.
			Thread.sleep(2500);

			// Send the "download" command (just the character 'd')
			System.out.println("Sending data download command 'd'");
			output.write('d');
			//output.flush();

			System.out.println("Waiting for data");
			//Thread.sleep(500);

			// keep looking for data as long as we haven't found any before,
			// or there's still some in the buffer
			while (foundData == 0 || input.available() > 0) {
				
				val = -1;
				s = "";

				// read until there's a comma
				while (input.available() > 0) {
					val = input.read();

					// if we see a comma we're at the end of the value
					if (val == ',')
						break;

					// add the character to the string buffer
					s += (char) val;
					
					// remember how much data we've found
					foundData ++;
					
					// if we haven't found a comma yet, there's probably more 
					// data on the way, so wait for it
					if (input.available() == 0)
						Thread.sleep(50);
				}

				// remove whitespace from the string
				s = s.trim();
				
				// it's possible to get some 0 length strings. I don't know why,
				// but let's just skip them anyway
				if (s.length() == 0) continue;
				
//				System.out.println("Found: " + s);

				// decide what kind of token this is
				// Possibilities are: LDR, RTD, xx/xx (progress), xx.xx (value)
				if (s.toUpperCase().equals("LDR")) {
					readMode = READ_LDR;
					System.out.println("Reading LDR values");
				} else if (s.toUpperCase().equals("RTD")) {
					readMode = READ_RTD;
					System.out.println("Reading RTD values");
				} else if (s.contains("/")) {
					// this token is a progress value
					if (readMode == READ_LDR) {
						System.out.println("LDR: " + s);
					} else if (readMode == READ_RTD) {
						System.out.println("RTD: " + s);
					}
					
				} else {
					// the token must be a value
					try {
						f = Float.parseFloat(s);
						
						if (readMode == READ_LDR) {
							ldrValues.add(new Float(f));
						} else if (readMode == READ_RTD) {
							rtdValues.add(new Float(f));
						}
					} catch (NumberFormatException e) {
						System.out.println("Didn't recognise string: " + s);
					}

				}

				// again, wait for more data
				if (foundData == 0 || input.available() == 0)
					Thread.sleep(100);
			}
			System.out.println("End of data");
			System.out.println(foundData + " records");

			// tidy up afterwards
			input.close();
			output.close();
			port.close();

		} catch (NoSuchPortException e) {
			JOptionPane.showMessageDialog(this, e.getMessage(), e.getClass()
					.getName(), JOptionPane.ERROR_MESSAGE);
			e.printStackTrace();
		} catch (PortInUseException e) {
			JOptionPane.showMessageDialog(this, e.getMessage(), e.getClass()
					.getName(), JOptionPane.ERROR_MESSAGE);
			e.printStackTrace();
		} catch (IOException e) {
			JOptionPane.showMessageDialog(this, e.getMessage(), e.getClass()
					.getName(), JOptionPane.ERROR_MESSAGE);
			e.printStackTrace();
		} catch (UnsupportedCommOperationException e) {
			JOptionPane.showMessageDialog(this, e.getMessage(), e.getClass()
					.getName(), JOptionPane.ERROR_MESSAGE);
			e.printStackTrace();

		} catch (InterruptedException e) {
			JOptionPane.showMessageDialog(this, e.getMessage(), e.getClass()
					.getName(), JOptionPane.ERROR_MESSAGE);
			e.printStackTrace();
		}
	}

	private void graphData() {
		// couple of time-based series for the graph 
		final TimeSeriesCollection dataset = new TimeSeriesCollection();
		final TimeSeries series = new TimeSeries("RTD");
		final TimeSeries series2 = new TimeSeries("LDR");
		long l;

		// Loop thru each of the data arrays and add it to the graph
		// The timestamp for each value is calculated as the current time, minus 
		// the number of values from the end of the queue, times the sampling interval
		// Basically, the most recent sample is now,
		// previous would be: Now - 15s,
		// 2 samples ago would be: Now - (2 * 15s),
		// 100 samples ago would be: Now - (100 * 15s), etc
		for (int i = 0; i < rtdValues.size(); i++) {
			l = start - ((rtdValues.size() - i) * GRAPH_INTERVAL);
			series.addOrUpdate(new Second(new Date(l)), rtdValues.get(i));
		}
		for (int i = 0; i < ldrValues.size(); i++) {
			l = start - ((ldrValues.size() - i) * GRAPH_INTERVAL);
			series2.addOrUpdate(new Second(new Date(l)), ldrValues.get(i));
		}

		// add the datasets to the graph
		dataset.addSeries(series);
		dataset.addSeries(series2);
		
		System.out.println("Series 1: " + series.getItemCount());
		System.out.println("Series 2: " + series2.getItemCount());

		plot.setDataset(dataset);
		

	}
}
