// DotProduct.java (C) 2001 by Paul Falstad, www.falstad.com

import java.io.InputStream;
import java.awt.*;
import java.awt.image.ImageProducer;
import java.applet.Applet;
import java.applet.AudioClip;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.File;
import java.net.URL;
import java.util.Random;
import java.awt.image.MemoryImageSource;
import java.lang.Math;
import java.text.NumberFormat;
import java.awt.event.*;

class DotProductCanvas extends Canvas {
    DotProduct pg;
    DotProductCanvas(DotProduct p) {
	pg = p;
    }
    public Dimension getPreferredSize() {
	return new Dimension(300,400);
    }
    public void update(Graphics g) {
	pg.updateDotProduct(g);
    }
    public void paint(Graphics g) {
	pg.updateDotProduct(g);
    }
};

class DotProductLayout implements LayoutManager {
    public DotProductLayout() {}
    public void addLayoutComponent(String name, Component c) {}
    public void removeLayoutComponent(Component c) {}
    public Dimension preferredLayoutSize(Container target) {
	return new Dimension(500, 500);
    }
    public Dimension minimumLayoutSize(Container target) {
	return new Dimension(100,100);
    }
    public void layoutContainer(Container target) {
	int cw = target.size().width;
	int ct = target.getComponentCount();
	target.getComponent(ct-1).move(0, 0);
	target.getComponent(ct-1).resize(cw, target.size().height);
	int i;
	int h = 0;
	for (i = 0; i < ct-1; i++) {
	    Component m = target.getComponent(i);
	    if (m.isVisible()) {
		Dimension d = m.getPreferredSize();
		m.move(cw-d.width, h);
		m.resize(d.width, d.height);
		h += d.height;
	    }
	}
    }
};


public class DotProduct extends Applet
  implements ComponentListener, ActionListener, AdjustmentListener,
             MouseMotionListener, MouseListener {
    
    Thread engine = null;

    Dimension winSize;
    Image dbimage;
    
    Random random;
    
    public String getAppletInfo() {
	return "DotProduct by Paul Falstad";
    }

    Button swapButton;
    double vecs[][];
    int selection = -1;

    int getrand(int x) {
	int q = random.nextInt();
	if (q < 0) q = -q;
	return q % x;
    }
    DotProductCanvas cv;

    public void init() {
	setLayout(new DotProductLayout());
	cv = new DotProductCanvas(this);
	cv.addComponentListener(this);
	cv.addMouseMotionListener(this);
	cv.addMouseListener(this);
	add(swapButton = new Button("Swap"));
	swapButton.addActionListener(this);
	add(cv);
	setBackground(Color.black);
	setForeground(Color.lightGray);
	random = new Random();
	vecs = new double[2][2];
	vecs[0][0] = 0; vecs[0][1] = 1;
	vecs[1][0] = 1; vecs[1][1] = 1;
	reinit();
	repaint();
    }

    void reinit() {
        Dimension d = winSize = cv.getSize();
	if (winSize.width == 0)
	    return;
	dbimage = createImage(d.width, d.height);
    }
    
    public void paint(Graphics g) {
	cv.repaint();
    }

    void findVecCoords(double x, double y, int result[]) {
	int cy = winSize.height/4;
	int cx = cy;
	result[0] = (int) (cx*(x+2));
	result[1] = (int) (cy*(2-y));
    }

    void findVecCoords(int num, int result[]) {
	findVecCoords(vecs[num][0], vecs[num][1], result);
    }

    void drawArrow(Graphics g, int x1, int y1, int x2, int y2, double len) {
	g.drawLine(x1, y1, x2, y2);
	if (len > .05) {
	    double l = java.lang.Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
	    double hatx = (x2-x1)/l;
	    double haty = (y2-y1)/l;
	    int as = 10;
	    g.drawLine(x2, y2,
		       (int) (haty*as-hatx*as+x2),
		       (int) (-hatx*as-haty*as+y2));
	    g.drawLine(x2, y2,
		       (int) (-haty*as-hatx*as+x2),
		       (int) (hatx*as-haty*as+y2));
	}
    }

    void drawBar(Graphics g, int offset, double val) {
	int x = (int) (winSize.width * val/6);
	int cx = winSize.width/2;
	int h = 5;
	int y = winSize.height + h*offset;
	int y2 = y+h-1;
	if (val < 0)
	    g.fillRect(cx+x, y, -x, h);
	else
	    g.fillRect(cx, y, x, h);
    }

    public void updateDotProduct(Graphics realg) {
	double alen = java.lang.Math.sqrt(
	    vecs[0][0] * vecs[0][0] + vecs[0][1] * vecs[0][1]);
	double blen = java.lang.Math.sqrt(
	    vecs[1][0] * vecs[1][0] + vecs[1][1] * vecs[1][1]);
	double piadj = 180/3.14159265;
	double dot = vecs[0][0] * vecs[1][0] + vecs[0][1] * vecs[1][1];
	double acosth = (blen > 0) ? dot/blen : 0;
	double costh = (alen > 0) ? acosth/alen : 0;
	double theta = java.lang.Math.acos(costh)*piadj;

	Graphics g = dbimage.getGraphics();
	if (winSize == null || winSize.width == 0)
	    return;
	g.setColor(cv.getBackground());
	g.fillRect(0, 0, winSize.width, winSize.height);
	g.setColor(Color.gray);
	int i, j;
	for (i = -2; i <= 2; i++) {
	    int x = winSize.height*(i+2)/4;
	    g.drawLine(x, 0, x, winSize.height);
	    g.drawLine(0, x, winSize.height, x);
	}
	int cy = winSize.height/2;
	int cx = cy;
	int vc[] = new int[2];
	if (blen > 0) {
	    int vc2[] = new int[2];
	    findVecCoords(vecs[1][0] * acosth/blen,
			  vecs[1][1] * acosth/blen, vc);
	    findVecCoords(0, vc2);
	    g.setColor(Color.gray);
	    g.drawLine(vc[0], vc[1], vc2[0], vc2[1]);
	}
	if (alen > .1 && blen > .1) {
	    int c1x = cx/10;
	    int c1y = cy/10;
	    int a1 = (int)(piadj*java.lang.Math.atan2(vecs[0][1], vecs[0][0]));
	    int a2 = (int)(piadj*java.lang.Math.atan2(vecs[1][1], vecs[1][0]));
	    if (a1 > a2 && a1 < a2+180) {
		int a3 = a1;
		a1 = a2; 
		a2 = a3;
	    }
	    if (a2 < a1)
		a2 += 360;
	    g.setColor(Color.orange);
	    g.drawArc(cx-c1x, cy-c1y, c1x*2, c1y*2, a1, a2-a1);
	}
	findVecCoords(0, vc);
	g.setColor(Color.red);
	drawArrow(g, cx, cy, vc[0], vc[1], alen);
	findVecCoords(1, vc);
	g.setColor(Color.cyan);
	drawArrow(g, cx, cy, vc[0], vc[1], blen);
	int yl = g.getFontMetrics().getHeight();
	int y = yl;
	NumberFormat nf = NumberFormat.getInstance();
	nf.setMaximumFractionDigits(3);
	g.setColor(Color.red);
	displayString(g,
		     "A = (" + nf.format(vecs[0][0]) + ", " +
		     nf.format(vecs[0][1]) + ")", y += yl);
	displayString(g, "|A| = " + nf.format(alen), y += yl);
	drawBar(g, -4, alen);
	g.setColor(Color.cyan);
	displayString(g,
		     "B = (" + nf.format(vecs[1][0]) + ", " +
		     nf.format(vecs[1][1]) + ")", y += yl);
	displayString(g, "|B| = " + nf.format(blen), y += yl);
	drawBar(g, -3, blen);
	g.setColor(Color.yellow);
	displayString(g, "|A| cos theta = " + nf.format(acosth), y += yl);
	drawBar(g, -2, acosth);
	if (blen > 0) {
	    findVecCoords(vecs[1][0] * acosth/blen,
			  vecs[1][1] * acosth/blen, vc);
	    g.setColor(Color.yellow);
	    g.drawLine(cx, cy, vc[0], vc[1]);
	}
	g.setColor(Color.white);
	displayString(g, "cos theta = " + nf.format(costh), y += yl);
	g.setColor(Color.orange);
	displayString(g, "theta = " + nf.format(theta) + "\u00b0", y += yl);
	g.setColor(Color.green);
	displayString(g, "A dot B = " + nf.format(dot), y += yl);
	drawBar(g, -1, dot);
	realg.drawImage(dbimage, 0, 0, this);
    }

    void displayString(Graphics g, String s, int y) {
	int lx = winSize.height;
	int lw = winSize.width - lx;
	FontMetrics fm = g.getFontMetrics();
        g.drawString(s, lx+(lw-fm.stringWidth(s))/2, y);
    }

    void edit(MouseEvent e) {
	if (selection == -1)
	    return;
	int x = e.getX();
	int y = e.getY();
	double cy = winSize.height/4;
	double cx = cy;
	double xf = x/cx-2;
	double yf = 2-y/cy;
	if (xf < -2) xf = -2;
	if (yf < -2) yf = -2;
	if (xf >  2) xf =  2;
	if (yf >  2) yf =  2;
	vecs[selection][0] = xf;
	vecs[selection][1] = yf;
	cv.repaint();
    }

    public void componentHidden(ComponentEvent e){}
    public void componentMoved(ComponentEvent e){}
    public void componentShown(ComponentEvent e) {
	cv.repaint();
    }

    public void componentResized(ComponentEvent e) {
	reinit();
	cv.repaint(100);
    }
    public void actionPerformed(ActionEvent e) {
	if (e.getSource() == swapButton) {
	    int x;
	    for (x = 0; x < 2; x++) {
		double y = vecs[0][x];
		vecs[0][x] = vecs[1][x];
		vecs[1][x] = y;
	    }
	    cv.repaint();
	}
    }
    public void adjustmentValueChanged(AdjustmentEvent e) {
    }
    public void mouseDragged(MouseEvent e) {
	edit(e);
    }
    public void mouseMoved(MouseEvent e) {
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0)
	    return;
	edit(e);
    }
    public void mouseClicked(MouseEvent e) {
    }
    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
    }
    public void mousePressed(MouseEvent e) {
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
	    return;
	int x = e.getX();
	int y = e.getY();
	int vc[] = new int[2];
	int i;
	selection = -1;
	for (i = 0; i != 2; i++) {
	    findVecCoords(i, vc);
	    int space = 10;
	    if (vc[0] >= x-space && vc[0] <= x+space &&
		vc[1] >= y-space && vc[1] <= y+space) {
		selection = i;
		break;
	    }
	}
	if (selection != -1)
	    edit(e);
    }
    public void mouseReleased(MouseEvent e) {
	if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0)
	    return;
	selection = -1;
    }
}

