// file:///C:/Documents%20and%20Settings/Andi/Desktop/HandGrapher/HandGrapher.html
// javac -target 1.2 -source 1.2 Equator.java; jar cvfe equator.jar Equator *.class *.png
// javac -target 1.2 -source 1.2 Equator.java; jar cvfe equator.jar Equator *.class *.png; java -jar equator.jar
// Combine Xsquared/Xsquared2 behaviour using achange/bchange?

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.datatransfer.*;
import java.awt.geom.*;

import java.lang.*;
import java.io.*;
import java.text.*;
import java.util.*;

public class Equator extends JFrame implements AWTEventListener{
	static final int startSize = 500;
	static final int minSize = 350;
	static Equator E;
	public static void main(String[] a) {
		E = new Equator();
		Toolkit t = Toolkit.getDefaultToolkit();
		Dimension d = t.getScreenSize();
		E.setSize(startSize, startSize);
		E.setMinimumSize(new Dimension(minSize, minSize));
		E.setLocation((int)d.getWidth()/2-250, (int)d.getHeight()/2-250);
		E.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		E.setTitle("Equator");
		E.setVisible(true);
		/*E.addComponentListener(new ComponentAdapter() {
			public void componentResized(ComponentEvent e) {
				Dimension d = E.getSize();
				double h = d.getHeight(), w = d.getWidth();
				if (h < minSize) h = minSize;
				if (w < minSize) w = minSize;
				d.setSize(w, h);
				E.setSize(d);
			}
		});*/
	/* =code debug
		JFrame jf = new JFrame("Debug");
		JScrollPane sp = new JScrollPane(ta);
		jf.add(sp);
		jf.setSize(1280, 700);
		jf.setVisible(true);
		t.addAWTEventListener(E, 0xFFFFFFFFFFFFFFFFl ^ (AWTEvent.COMPONENT_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK) );
		// -code */
	}
	static JTextArea ta = new JTextArea("");
	public void eventDispatched(AWTEvent event) {
		ta.append(event + "\n");
	// -code */
	}

	// mv (MoVe equ button), bb (Bend equ Button), mg (Move Graph button), zg (Zoom Graph button)
	JToggleButton mv, bb, mg, zg;
	JGraph g;

	static JToggleButton selected;
	static void changeSelected(JToggleButton jb) {
		if (selected != null) selected.setSelected(true);
		selected = jb;
		selected.setSelected(false);
	}

	static void setSize(JComponent c, int width, int height) {
		c.setMinimumSize(new Dimension(width, height));
		c.setMaximumSize(new Dimension(width, height));
		c.setPreferredSize(new Dimension(width, height));
	}

	Equator() {
		ClassLoader rl = ClassLoader.getSystemClassLoader();
		Container c = getContentPane();
		c.setLayout(new GridBagLayout());
		GridBagConstraints gc = new GridBagConstraints();
		int buttonsSize = 50;

		JPanel p = new JPanel();
		p.setLayout(new BoxLayout(p, BoxLayout.LINE_AXIS));
		p.setBorder(new TitledBorder(null, "Equation", TitledBorder.LEFT, TitledBorder.TOP));
		setSize(p, 500, 60);
		gc.gridx = 0;
		gc.gridy = 0;
		gc.gridwidth = GridBagConstraints.REMAINDER;
		gc.fill = GridBagConstraints.HORIZONTAL;
		c.add(p, gc);

		mv = new JToggleButton(new ImageIcon(rl.getResource("move.png")), true);
		setSize(mv, buttonsSize, buttonsSize );
		gc.gridx = 0;
		gc.gridy = 1;
		gc.gridwidth = 1;
		gc.fill = GridBagConstraints.NONE;
		c.add(mv, gc);
		mv.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {g.action = JGraph.MOVE; changeSelected(mv); }
		});
		changeSelected(mv);

		bb = new JToggleButton(new ImageIcon(rl.getResource("bend.png")), true);
		setSize(bb, buttonsSize, buttonsSize );
		gc.gridx = 0;
		gc.gridy = 2;
		gc.gridwidth = 1;
		gc.fill = GridBagConstraints.NONE;
		c.add(bb, gc);
		bb.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {g.action = JGraph.BEND; changeSelected(bb); }
		});

		mg = new JToggleButton(new ImageIcon(rl.getResource("moveg.png")), true);
		setSize(mg, buttonsSize, buttonsSize );
		gc.gridx = 0;
		gc.gridy = 3;
		gc.gridwidth = 1;
		gc.fill = GridBagConstraints.NONE;
		c.add(mg, gc);
		mg.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {g.action = JGraph.CENTER; changeSelected(mg); }
		});

		zg = new JToggleButton(new ImageIcon(rl.getResource("zoom.png")), true);
		setSize(zg, buttonsSize, buttonsSize );
		gc.gridx = 0;
		gc.gridy = 4;
		gc.gridwidth = 1;
		gc.anchor = GridBagConstraints.FIRST_LINE_START;
		gc.gridheight = GridBagConstraints.REMAINDER;
		gc.fill = GridBagConstraints.NONE;
		c.add(zg, gc);
		zg.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {g.action = JGraph.ZOOM; changeSelected(zg); }
		});

		g = new JGraph(p);
		g.setOpaque(true);
		g.addMouseListener(g);
		g.addMouseMotionListener(g);
		g.addComponentListener(g);

		JData d = new JData(g);
		d.setBorder(new TitledBorder(null, "", TitledBorder.RIGHT, TitledBorder.BOTTOM));
		d.setOpaque(false);

		g.setLayout(new BorderLayout());
		g.add(d, BorderLayout.CENTER);
		gc.gridx = 1;
		gc.gridy = 1;
		gc.gridwidth = GridBagConstraints.REMAINDER;
		gc.gridheight = GridBagConstraints.REMAINDER;
		gc.weightx = 1.0;
		gc.weighty = 1.0;
		gc.insets = new Insets(0,2,2,2);
		gc.fill = GridBagConstraints.BOTH;
		c.add(g, gc);
		
		JMenuBar m = new JMenuBar();

		JMenu m0 = new JMenu("Enter Data");
		m0.setMnemonic(KeyEvent.VK_D);
		m.add(m0);
		m0.addMenuListener(d);

		JMenu m1 = new JMenu("Equations");
		m1.setMnemonic(KeyEvent.VK_E);
		m.add(m1);
		
		JMenuItem m11 = new JMenuItem("Exponential");
		JMenuItem m12 = new JMenuItem("Quadratic");
		JMenuItem m13 = new JMenuItem("Reciprocal");
		JMenuItem m14 = new JMenuItem("Linear");
		m1.add(m14); m1.add(m12); m1.add(m13); m1.add(m11);
		m11.addActionListener(g);
		m12.addActionListener(g);
		m13.addActionListener(g);
		m14.addActionListener(g);

		JMenu m2 = new JMenu("Line Colour");
		m2.setMnemonic(KeyEvent.VK_C);
		m.add(m2);

		JMenuItem m21 = new JMenuItem("Red");
		JMenuItem m22 = new JMenuItem("Green");
		JMenuItem m23 = new JMenuItem("Blue");
		m2.add(m21); m2.add(m22); m2.add(m23);
		m21.addActionListener(g);
		m22.addActionListener(g);
		m23.addActionListener(g);

		JMenu m3 = new JMenu("Line Size");
		m3.setMnemonic(KeyEvent.VK_S);
		m.add(m3);

		JMenuItem m31 = new JMenuItem("1");
		JMenuItem m32 = new JMenuItem("2");
		JMenuItem m33 = new JMenuItem("3");
		JMenuItem m34 = new JMenuItem("4");
		m3.add(m31); m3.add(m32); m3.add(m33); m3.add(m34);
		m31.addActionListener(g);
		m32.addActionListener(g);
		m33.addActionListener(g);
		m34.addActionListener(g);

		JMenu m5 = new JMenu("Reset Graph");
		m5.setMnemonic(KeyEvent.VK_R);
		m.add(m5);
		m5.addMenuListener(new MenuListener() {
			public void menuDeselected(MenuEvent e) {}
			public void menuCanceled(MenuEvent e) {}
			public void menuSelected(MenuEvent e) { g.defaultValues(); }
		});

		m.add(new JSeparator());

		// DO AN 'ARE YOU SURE?' POP-UP BOX WHEN EXITING...
		JMenu m4 = new JMenu("Exit");
		m4.setMnemonic(KeyEvent.VK_X);
		m.add(m4);
		m4.addMenuListener(new MenuListener() {
			public void menuDeselected(MenuEvent e) {}
			public void menuCanceled(MenuEvent e) {}
			public void menuSelected(MenuEvent e) { System.exit(0); }
		});

		setJMenuBar(m);
	}


/******************************
* JGraph component
******************************/

	public class JGraph extends JComponent implements MouseInputListener, ActionListener, ComponentListener {
		NumberFormat nf = NumberFormat.getInstance();		// Number Formatter used to format numbers(!)
		Formula f;						// Current Formula/Equation being plotted
		Color c = Color.blue;					// Current Color of the formula's lines
		int lt;							// Current Line Thickness for the formula's line
		int apph, appw;						// JGraph's APPlication Height and Width
		JPanel eqp;							// A pointer the the EQuation Panel
		int action;							// Current mouse ACTION state
		static final int MOVE = 0, BEND = 1, CENTER = 2, ZOOM = 3;	// Available mouse ACTIONS
		Font bf;							// Base Font of graphics
		Font af;							// Axis Font
		FontRenderContext fr;						// Font Rendering context of grpahics
		

		JGraph(JPanel p) { eqp = p; setFormula(new X()); defaultValues(); }

		public void defaultValues() { cx = 0; cy = 0; dimx = 20; dimy = 20; lt = 2; repaint(); }

		protected void paintComponent(Graphics gg) {
			Graphics2D g = (Graphics2D)gg;
			Color cc = g.getColor();
			if (bf == null) {
				bf = g.getFont();
				af = bf.deriveFont(10f);
				fr = g.getFontMetrics(bf).getFontRenderContext();
			}
			g.setColor(getBackground());
			g.fillRect(0, 0, appw, apph);
			g.setStroke(new BasicStroke(lt));
			g.setColor(c);
			plotFormula(g, f);
			g.setStroke(new BasicStroke(1));
			g.setColor(Color.darkGray);
			drawAxes(g);
			g.setColor(Color.green.darker().darker());
			P v = cartesianToViewpoint(cx,cy);
			g.fillOval((int)v.x-2, (int)v.y-2, 5, 5);
			g.setColor(Color.black);

			String s = "(" + nf.format(xCart(atX)) + ", " + nf.format(yCart(atY)) + ")";
			Rectangle2D.Double r = new Rectangle2D.Double();
			TextLayout t = new TextLayout(s, bf, fr);
			r.setRect(t.getBounds());
			g.drawString(s, 1, (int)r.getHeight() + 1);

			if (action == ZOOM && zooming) {
				g.setStroke(new BasicStroke(2));
				g.setColor(Color.orange);
				int x = Math.min(zX, atX);
				int y = Math.min(zY, atY);
				int w = Math.abs(atX - zX);
				int h = Math.abs(atY - zY);
				g.drawRect(x, y, w, h);
			}
			g.setColor(cc);
			f.display();
		}

		private double dimx, dimy, cx, cy;
		void plotFormula(Graphics2D g, Formula f) {
			double hdimx = dimx/2;
			double addon = dimx/appw;
			P ip = new P(cx + -hdimx, f.y(cx + -hdimx));
			for (double x = cx + -hdimx + addon; x < cx + hdimx; x += addon) {
				P fp = new P(x, f.y(x));
				if (f.connect(ip, fp)) {
					g.drawLine(xView(ip.x), yView(ip.y), xView(fp.x), yView(fp.y));
				}
				ip = fp;
			}
		}
		void drawAxes(Graphics2D g) {
			int l = 3;
			double hdimx = dimx/2;
			double hdimy = dimy/2;
			P ip, fp;
			g.setFont(af);
			// Draw X-Axis
			ip = cartesianToViewpoint(cx - hdimx,0);
			fp = cartesianToViewpoint(cx + hdimx,0);
			g.drawLine((int)ip.x, (int)ip.y, (int)fp.x, (int)fp.y);
			double addon = dimx/10;
			double s = Math.floor(cx-hdimx);
			s = s - s%(dimx/10);
			for (double x = s; x <= cx + hdimx; x += addon) {
					ip = cartesianToViewpoint(x, 0);
					g.drawLine((int)ip.x, (int)ip.y - l, (int)ip.x, (int)ip.y + l);
					g.drawString(nf.format(x), (int)ip.x, (int)ip.y-3);
			}
			// Draw Y-Axis
			ip = cartesianToViewpoint(0, cy - hdimy);
			fp = cartesianToViewpoint(0, cy + hdimy);
			g.drawLine((int)ip.x, (int)ip.y, (int)fp.x, (int)fp.y);
			addon = dimy/10;
			s = Math.floor(cy-hdimy);
			s = s - s%(dimy/10);
			for (double y = s; y <= cy + hdimy; y += addon) {
				if (y > 1E-10 || y < -1E-10) {  // Don't display another 0
					ip = cartesianToViewpoint(0, y);
					g.drawLine((int)ip.x - l, (int)ip.y, (int)ip.x + l, (int)ip.y);
					g.drawString(nf.format(y), (int)ip.x+5, (int)ip.y);
				}
			}
			g.setFont(bf);
		}
		P cartesianToViewpoint(double x, double y) {
			return new P(xView(x), yView(y));
		}
		P viewpointToCartesian(int x, int y) {
			return new P(xCart(x), yCart(y));
		}
		int xView(double x) {
			double dx = ((x - cx)/dimx + 0.5) * appw;
			if (dx < Integer.MIN_VALUE) dx = Integer.MIN_VALUE/100;
			else if (dx > Integer.MAX_VALUE) dx = Integer.MAX_VALUE/100;
			return (int)dx;
		}
		int yView(double y) {
			double dy = (-(y - cy)/dimy + 0.5) * apph;
			if (dy < Integer.MIN_VALUE) dy = Integer.MIN_VALUE/100;
			else if (dy > Integer.MAX_VALUE) dy = Integer.MAX_VALUE/100;
			return (int)dy;
		}
		double xCart(int x) { return (((double)x)/appw - 0.5)*dimx + cx; }
		double yCart(int y) { return (((double)y)/apph - 0.5)*(-dimy) + cy; }

		void setFormula(Formula newf) {
			eqp.removeAll();
			f = newf;
			f.setup(eqp);
		}	

		//***  EVENTS  ********************************************
		public void actionPerformed(ActionEvent e) {
			String ss = e.getActionCommand();
			switch(ss.charAt(0)) {
				case 'E': setFormula(new Expx()); break;
				case 'Q': setFormula(new Xsquared2()); break;
				case 'R': switch(ss.charAt(2)) {
						case 'c': setFormula(new Invdx()); break;
						case 'd': c = Color.red; break;
					    } break;
				case 'L': setFormula(new X()); break;
				case 'G': c = Color.green; break;
				case 'B': c = Color.blue; break;
				case '1': lt = 1; break;
				case '2': lt = 2; break;
				case '3': lt = 3; break;
				case '4': lt = 4; break;
			}
			repaint();
		}

		int atX, atY, nwX, nwY, zX, zY;
		boolean zooming = false;
		public void mousePressed(MouseEvent e) {
			zX = e.getX(); zY = e.getY();
			switch(action) {
				case ZOOM: zooming = true; break;
			}
		}
		public void mouseMoved(MouseEvent e) { atX = e.getX(); atY = e.getY(); repaint(); }
		public void mouseReleased(MouseEvent e) {
			int nwX = e.getX(), nwY = e.getY();
			switch(action) {
				case ZOOM:
					if (nwX == zX || nwY == zY) {
						dimy *= 10;
						dimx *= 10;
						if (dimx > 1) dimx = Math.ceil(dimx);
						if (dimy > 1) dimy = Math.ceil(dimy);
					}
					else {
						double x1 = xCart(zX);
						double x2 = xCart(atX);
						double y1 = yCart(zY);
						double y2 = yCart(atY);
						dimx = Math.abs(x1 - x2);
						dimy = Math.abs(y1 - y2);
						if (dimx > 1) dimx = Math.ceil(dimx);
						if (dimy > 1) dimy = Math.ceil(dimy);
						cx = Math.min(x1, x2) + dimx/2;
						cy = Math.min(y1, y2) + dimy/2;
					}
					if (dimy > 20000) dimy = 20000;
					if (dimy < 0.02) dimy = 0.02;
					if (dimx > 20000) dimx = 20000;
					if (dimx < 0.02) dimx = 0.02;
					zooming = false;
					repaint();
					break;
			}
		}

		public void mouseDragged(MouseEvent e) {
			nwY = e.getY(); nwX = e.getX();
			switch(action) {
				case MOVE:
				f.vShift(dimy/apph*(atY - nwY));
				f.hShift(dimx/appw*(atX - nwX));
				break;
				case BEND:
				double x = xCart(zX);
				f.vChange(new P(x, f.y(x)), viewpointToCartesian(atX, atY));
				/*
				double sensitivity = 0.001;
				double s = sensitivity*Math.min(dimx, dimy);
				if (atY < nwY) f.aChange(-s);
				if (atY > nwY) f.aChange(s);
				if (atX < nwX) f.bChange(-s);
				if (atX > nwX) f.bChange(s);
				*/
				break;
				case CENTER:
				cy = cy - dimy/apph*(atY - nwY);
				cx = cx + dimx/appw*(atX - nwX);
				break;
			}
			repaint();
			mouseMoved(e);
		}

		public void mouseClicked(MouseEvent e) {}
		public void mouseEntered(MouseEvent e) {}
		public void mouseExited(MouseEvent e) {}

		public void componentHidden(ComponentEvent e) {}
		public void componentMoved(ComponentEvent e) {}
		public void componentResized(ComponentEvent e) {
			apph = getHeight();
			appw = getWidth();
		}
		public void componentShown(ComponentEvent e) {}
		//***  End EVENTS  ****************************************
	}
/******************************
* End JGraph component
******************************/

/******************************
* JData component
******************************/
	public class JData extends JComponent implements MenuListener {
		int NDV = 1000;						// Number of Data Values
		Color c = Color.black;					// Colour of data points
		int ps = 5;							// Points Size
		int data = 0;						// number of Data points
		double[] datax = new double[NDV];			// Data points' x-position
		double[] datay = new double[NDV];			// Data points' y-position
		NumberFormat nf = NumberFormat.getInstance();	// For formatting numbers
		JGraph jg;							// JGraph pointer to know where to draw points
		JFrame w = new JFrame();				// Window frame to collect data points
		JDataField[] tfx = new JDataField[NDV];		// Text field holding x-position
		JDataField[] tfy = new JDataField[NDV];			// Text field holding y-position

		JData(JGraph g) {
			jg = g;

			//**** Setup Data Collection Window...
			w.setLayout(new BorderLayout());
			w.setTitle("Enter Data...");			
			w.setSize(200, startSize);

			//**** XY Labels
			JPanel xyp = new JPanel();
			xyp.setLayout(new GridLayout(1, 2));
			JLabel jlx = new JLabel("X"); jlx.setHorizontalAlignment(SwingConstants.CENTER);
			JLabel jly = new JLabel("Y"); jly.setHorizontalAlignment(SwingConstants.CENTER);
			xyp.add(jlx);
			xyp.add(jly);

			//**** Data Points
			JPanel dpp = new JPanel();
			dpp.setLayout(new GridLayout(NDV, 2));
			for (int n = 0; n < NDV; n++) {
				tfx[n] = new JDataField(3);
				tfy[n] = new JDataField(3);
				if (n < data) {
					tfx[n].setText("" + nf.format(datax[n]));
					tfy[n].setText("" + nf.format(datay[n]));
				}
				dpp.add(tfx[n]);
				dpp.add(tfy[n]);
			}
			for (int n = 0; n < NDV; n++) {
				tfx[n].setNext(tfy[n]);
				if (n < NDV-1) tfy[n].setNext(tfx[n+1]);
			}

			//**** Buttons
			JPanel bup = new JPanel();
			bup.setLayout(new GridLayout(1, 2));
			JButton db = new JButton("Update");
			db.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					datax = new double[NDV];
					datay = new double[NDV];
					int pos = 0;
					Double xv, yv;
					for (int n = 0; n < NDV; n++) {
						try {
							xv = Double.valueOf(tfx[n].getText());
							yv = Double.valueOf(tfy[n].getText());
						} catch (Exception err) {
							continue;
						}
						datax[pos] = xv.doubleValue();
						datay[pos] = yv.doubleValue();
						pos++;
					}
					data = pos;
					repaint();
				}
			});
			bup.add(db);
			JButton cb = new JButton("Clear All");
			cb.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					for (int n = 0; n < NDV; n++) {
						tfx[n].setText("");
						tfy[n].setText("");
					}
					data = 0;
				}
			});
			bup.add(cb);

			//**** Piece it together...
			JPanel bl = new JPanel();
			bl.setLayout(new BorderLayout());
			bl.add(xyp, BorderLayout.NORTH);
			bl.add(new JScrollPane(dpp), BorderLayout.CENTER);
			bl.add(bup, BorderLayout.SOUTH);

			w.add(bl, BorderLayout.CENTER);

			//Point sp = E.getLocation();
			//w.setLocation((int)sp.getX()-210, (int)sp.getY());
			// ADD A 'COPY TO CLIPBOARD' BUTTON
			// ADD A 'PASTE FROM CLIPBOARD' BUTTON
		}

		// Clears the drawing area with an 100% alpha black, then draws data[] points.
		protected void paintComponent(Graphics gg) {
			Color cc = gg.getColor();
			Graphics2D g = (Graphics2D)gg;
			g.setColor(new Color(0,0,0,1));
			g.fillRect(0, 0, jg.appw, jg.apph);
			g.setColor(c);
			for (int n = 0; n < data; n++) {
				g.fillOval(jg.xView(datax[n])-ps/2, jg.yView(datay[n])-ps/2, ps, ps);
			}
			g.setColor(cc);
		}

		// Special JTextField Class that allows textfields to be linked together for a neat
		// self-contained pasting operation...
		class JDataField extends JTextField {
			JDataField next = null;

			JDataField(int n) { super(n); }

			public void setNext(JDataField n) { next = n; }

			public void paste() {
				try {
					Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
					getToken(new StringTokenizer((String)c.getData(DataFlavor.stringFlavor)));
				} catch (Exception e) { }
			}

			public void getToken(StringTokenizer s) {
				setText("" + s.nextToken());
				if (next != null) next.getToken(s);
			}
		}

		//***  EVENTS  ********************************************
		public void menuCanceled(MenuEvent e) { }
		public void menuDeselected(MenuEvent e) { }
		public void menuSelected(MenuEvent e) { w.setVisible(true); }
		//***  End EVENTS  ****************************************
	}
/******************************
* End JData component
******************************/

}
