// ================================================================
// John Kerl
// kerl.john.r@gmail.com
// 2005-05-02
// ================================================================

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;

public class Plot extends Frame
{
	PlotPanel    plotPanel;
	PData        data;
	PData.Bounds dataBounds;
	boolean doField;
	boolean doDots;
	boolean doLines;
	boolean doWhiteBg;
	int     perPointDelay;

	// ----------------------------------------------------------------
	// Plot constructor

	public Plot(
		PData arg_data,
		boolean argDoField,
		boolean argDoDots,
		boolean argDoLines,
		boolean argDoWhiteBg,
		int     argPerPointDelay)
	{
		super("Plotter");
		this.add(this.plotPanel = new PlotPanel(this));

		this.doField    = argDoField;
		this.doDots     = argDoDots;
		this.doLines    = argDoLines;
		this.doWhiteBg  = argDoWhiteBg;
		this.perPointDelay = argPerPointDelay;

		this.data = arg_data;
		this.dataBounds = this.data.get_bounds();
		System.out.println(
			"X: "  + this.dataBounds.minx +
			" to " + this.dataBounds.maxx);
		System.out.println(
			"Y: "  + this.dataBounds.miny +
			" to " + this.dataBounds.maxy);

		// Instantiate a new frame.
		this.addWindowListener(new WindowAdapter() {
			public void windowClosing    (WindowEvent e) {
				System.out.println("Got closing.");
				System.exit(0);
			}
			public void windowDeiconified(WindowEvent e) { start(); }
			public void windowIconified  (WindowEvent e) { stop(); }
		});

		this.pack();
		this.setSize(new Dimension(600, 600));
		this.show();
	}

	// ----------------------------------------------------------------
	// Plot start
	// Simply starts the Plot.PlotPanel animation.

	public void start()
	{
		plotPanel.start();
	}

	// ----------------------------------------------------------------
	// Plot stop
	//
	// Simply stops the Plot.PlotPanel animation.

	public void stop()
	{
		plotPanel.stop();
	}

	// ================================================================
	// The Plot.PlotPanel class performs the animation and the painting of
	// the data.

	private static class PlotPanel extends JPanel implements Runnable
	{
		private Thread thread;
		private static final int NUMPTS = 4;
		private Plot plot; // Reference to containing object

		private static Color ppColors[] = {
			Color.blue,
			Color.red,
			Color.green,
			Color.orange,
			Color.yellow,
			Color.cyan,
			Color.magenta,
			Color.pink};

		// ----------------------------------------------------------------
		// Plot.PlotPanel constructor

		public PlotPanel(Plot arg_plot)
		{
			this.setBackground(Color.black);
			// this.setBackground(Color.white);
			this.plot      = arg_plot;
		}

		// ----------------------------------------------------------------
		// Plot.PlotPanel.paint:

		public void paint(
			Graphics g)
		{
			if (this.plot.doField)
				drawFieldPlotPanel(g);
			else
				drawPlotPanel(g);
		}

		// ----------------------------------------------------------------
		// Plot.PlotPanel.drawPlotPanel:

		public void drawPlotPanel(
			Graphics g)
		{
			Dimension d = getSize();

			int     serno;
			int     ptno;
			double  x1,  y1,  x2,  y2;
			int    ix1, iy1, ix2, iy2; 

			// Background
			if (this.plot.doWhiteBg)
				g.setColor(Color.white);
			else
				g.setColor(Color.black);
			g.fillRect(0, 0, d.width, d.height);

			// Axes
			g.setColor(Color.gray);
			g.drawLine(
				x_to_ix(plot.dataBounds.minx, d.width),
				y_to_iy(0.0,                  d.height),
				x_to_ix(plot.dataBounds.maxx, d.width),
				y_to_iy(0.0,                  d.height));

			g.drawLine(
				x_to_ix(0.0,                  d.width),
				y_to_iy(plot.dataBounds.miny, d.height),
				x_to_ix(0.0,                  d.width),
				y_to_iy(plot.dataBounds.maxy, d.height));


			for (ptno = 1; ptno < plot.data.get_num_points(); ptno++) {

				if (this.plot.perPointDelay > 0) {

					for (serno = 0; serno < plot.data.get_num_series(); serno++) {
						x1  = plot.data.xdata[ptno];
						y1  = plot.data.ydata[serno][ptno];
						ix1 = x_to_ix(x1, d.width);
						iy1 = y_to_iy(y1, d.height);
						if (this.plot.doWhiteBg) {
							g.setColor(Color.white);
							g.setXORMode(Color.black);
						}
						else {
							g.setColor(Color.black);
							g.setXORMode(Color.white);
						}
						g.drawRect(ix1-1, iy1-1, 2, 2);
					}

					try { thread.sleep(this.plot.perPointDelay); }
					catch (Exception e) { break; }

					for (serno = 0; serno < plot.data.get_num_series(); serno++) {
						x1  = plot.data.xdata[ptno];
						y1  = plot.data.ydata[serno][ptno];
						ix1 = x_to_ix(x1, d.width);
						iy1 = y_to_iy(y1, d.height);
						g.drawRect(ix1-1, iy1-1, 2, 2);
						g.setPaintMode();
					}

				}

				for (serno = 0; serno < plot.data.get_num_series(); serno++) {
					g.setColor(ppColors[ serno % ppColors.length ]);


					x1  = plot.data.xdata[ptno-1];
					y1  = plot.data.ydata[serno][ptno-1];
					ix1 = x_to_ix(x1, d.width);
					iy1 = y_to_iy(y1, d.height);

					x2 = plot.data.xdata[ptno];
					y2 = plot.data.ydata[serno][ptno];
					ix2 = x_to_ix(x2, d.width);
					iy2 = y_to_iy(y2, d.height);

					if (this.plot.doLines)
						g.drawLine(ix1, iy1, ix2, iy2);
					if (this.plot.doDots)
						g.drawRect(ix2-1, iy2-1, 2, 2);
				}
			}
		}

		// ----------------------------------------------------------------
		// Plot.PlotPanel.drawFieldPlotPanel:

		public void drawFieldPlotPanel(
			Graphics g)
		{
			Dimension d = getSize();

			int     serno;
			int     ptno;
			double  x1,  y1,  x2,  y2;
			int    ix1, iy1, ix2, iy2; 
			int     grad_tick_pix_length = 10;
			double  zpt = 1e-5;

			if (plot.data.get_num_series() != 3) {
				System.out.println("Field plot:  Must have 3 series.");
				System.exit(1);
			}

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

			// axes
			g.setColor(Color.gray);
			g.drawLine(
				x_to_ix(plot.dataBounds.minx, d.width),
				y_to_iy(0.0,                  d.height),
				x_to_ix(plot.dataBounds.maxx, d.width),
				y_to_iy(0.0,                  d.height));

			g.drawLine(
				x_to_ix(0.0,                  d.width),
				y_to_iy(plot.dataBounds.miny, d.height),
				x_to_ix(0.0,                  d.width),
				y_to_iy(plot.dataBounds.maxy, d.height));

			g.setColor(Color.blue);

			for (ptno = 0; ptno < plot.data.get_num_points(); ptno++) {
				x1 = plot.data.xdata[ptno];
				y1 = plot.data.ydata[0][ptno];
				ix1 = x_to_ix(x1, d.width);
				iy1 = y_to_iy(y1, d.height);

				g.setColor(Color.gray);
				g.drawRect(ix1-1, iy1-1, 2, 2);

				x2 = plot.data.ydata[1][ptno];
				y2 = plot.data.ydata[2][ptno];

				double angle = Math.atan2(y2, x2);
				double mag   = Math.sqrt(x2*x2 + y2*y2);


				if (mag < zpt) {
					ix2 = ix1;
					iy2 = iy1;
				}
				else {
					double dx = grad_tick_pix_length * Math.cos(angle);
					double dy = grad_tick_pix_length * Math.sin(angle);
					ix2 = ix1 + (int)dx;
					iy2 = iy1 - (int)dy;
				}

				g.setColor(Color.blue);
				g.drawLine(ix1, iy1, ix2, iy2);

			}
		}

		// ----------------------------------------------------------------
		private int x_to_ix(
			double x,
			int    iWinWidth)
		{
			double minx = plot.dataBounds.minx;
			double maxx = plot.dataBounds.maxx;
			return (int)((x - minx) / (maxx - minx) * iWinWidth);
		}

		// ----------------------------------------------------------------
		private int y_to_iy(
			double y,
			int    iWinHeight)
		{
			double miny = plot.dataBounds.miny;
			double maxy = plot.dataBounds.maxy;
			return (int)((y - maxy) / (miny - maxy) * iWinHeight);
		}

		// ----------------------------------------------------------------
		// Plot.PlotPanel methods implementing Runnable interface.

		// ----------------------------------------------------------------
		// Plot.PlotPanel.start:

		public void start()
		{
			thread = new Thread(this);
			thread.setPriority(Thread.MIN_PRIORITY);
			thread.start();
		}

		// ----------------------------------------------------------------
		// Plot.PlotPanel.stop:

		public synchronized void stop()
		{
			thread = null;
		}

		// ----------------------------------------------------------------
		// Plot.PlotPanel.run:

		public void run()
		{
			repaint();
			thread = null;
		}

	} // End Plot.PlotPanel class

	// ----------------------------------------------------------------
	// Plot.main

	public static void main(
		String[] argv)
	{
		int argi;

		// Default settings.
		boolean doField    = false;
		boolean doDots     = false;
		boolean doLines    = true;
		boolean doWhiteBg  = false;
		int     perPointDelay = 0;

		for (argi = 0; argi < argv.length; argi++) {
			if (argv[argi].equals("-p")) {
				doDots  = true;
				doLines = false;
			}
			else if (argv[argi].equals("-l")) {
				doDots  = false;
				doLines = true;
			}
			else if (argv[argi].equals("-lp")) {
				doDots  = true;
				doLines = true;
			}
			else if (argv[argi].equals("-pl")) {
				doDots  = true;
				doLines = true;
			}
			else if (argv[argi].equals("-w")) {
				doWhiteBg  = true;
			}
			else if (argv[argi].equals("-f")) {
				doField = true;
			}
			else if (argv[argi].equals("-d")) {
				if (argi >= (argv.length - 1)) {
					System.out.println("Missing argument for -d.\n");
					System.exit(1);
				}
				argi++;
				perPointDelay = Integer.valueOf(argv[argi]).intValue();
				if (perPointDelay <= 0) {
					System.out.println("Argument for -d must be positive.\n");
					System.exit(1);
				}
			}
			else {
				System.err.println("Unrecognized command-line option \""
					+ argv[argi] + "\".\n");
				System.exit(1);
			}
		}

		// Convert standard input to a vector of strings.
		// Convert that to a matrix of numbers.
		// Pass the matrix to the Plot constructor.

		Vector stringList = TextFileReader.readInputStream(System.in);
		TextFileReader.dump(stringList);

		// xxx temp PData pdata       = new PData(stringList);
		// xxx temp final Plot plotter = new Plot(pdata,
		// xxx temp 	doField, doDots, doLines, doWhiteBg, perPointDelay);
		// xxx temp
		// xxx temp // Start the animation.
		// xxx temp plotter.start();
	}
} // End Plot class
