// Euler.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.util.Vector;
import java.io.File;
import java.util.Random;
import java.util.Arrays;
import java.lang.Math;
import java.text.NumberFormat;
import java.awt.event.*;

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

class EulerLayout implements LayoutManager {
    public EulerLayout() {}
    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 barwidth = 0;
	int i;
	for (i = 1; i < target.getComponentCount(); i++) {
	    Component m = target.getComponent(i);
	    if (m.isVisible()) {
		Dimension d = m.getPreferredSize();
		if (d.width > barwidth)
		    barwidth = d.width;
	    }
	}
	Insets insets = target.insets();
	int targetw = target.size().width - insets.left - insets.right;
	int cw = targetw-barwidth;
	int targeth = target.size().height - (insets.top+insets.bottom);
	target.getComponent(0).move(insets.left, insets.top);
	target.getComponent(0).resize(cw, targeth);
	cw += insets.left;
	int h = insets.top;
	for (i = 1; i < target.getComponentCount(); i++) {
	    Component m = target.getComponent(i);
	    if (m.isVisible()) {
		Dimension d = m.getPreferredSize();
		if (m instanceof Scrollbar)
		    d.width = barwidth;
		if (m instanceof Label) {
		    h += d.height/5;
		    d.width = barwidth;
		}
		m.move(cw, h);
		m.resize(d.width, d.height);
		h += d.height;
	    }
	}
    }
};

public class Euler extends Applet implements ComponentListener {
    EulerFrame ogf;
    void destroyFrame() {
	if (ogf != null)
	    ogf.dispose();
	ogf = null;
	repaint();
    }
    boolean started = false;
    public void init() {
	addComponentListener(this);
    }
    
    void showFrame() {
	if (ogf == null) {
	    started = true;
	    ogf = new EulerFrame(this);
	    ogf.init();
	    repaint();
	}
    }
    
    public void paint(Graphics g) {
	String s = "Applet is open in a separate window.";
	if (!started)
	    s = "Applet is starting.";
	else if (ogf == null)
	    s = "Applet is finished.";
	else
	    ogf.show();
	g.drawString(s, 10, 30);
    }
    
    public void componentHidden(ComponentEvent e){}
    public void componentMoved(ComponentEvent e){}
    public void componentShown(ComponentEvent e) { showFrame(); }
    public void componentResized(ComponentEvent e) {}
    
    public void destroy() {
	if (ogf != null)
	    ogf.dispose();
	ogf = null;
	repaint();
    }
};

class EulerFrame extends Frame
  implements ComponentListener, ActionListener, AdjustmentListener,
             MouseListener, MouseMotionListener, ItemListener {
    
    Thread engine = null;

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

    Choice modeChooser;
    Choice centerChooser;
    Checkbox animateCheck;
    Scrollbar zoomBar;
    Scrollbar termsBar;
    double zoom;
    static final double pi = 3.14159265358979323846;
    static final double pi2 = pi*2;
    int xpoints[];
    int ypoints[];
    int pause;
    Euler applet;
    static final int MODE_RESULT = 0;
    static final int MODE_ARG = 1;
    double zr = 0, zi = 3*pi/4;
    double orgx = 0, orgy = 0;
    boolean mouseDown;
    
    EulerCanvas cv;

    EulerFrame(Euler a) {
	super("Euler's Formula Applet v1.1");
	applet = a;
    }

    public void init() {
	setLayout(new EulerLayout());
	cv = new EulerCanvas(this);
	cv.addComponentListener(this);
	cv.addMouseMotionListener(this);
	cv.addMouseListener(this);
	add(cv);

	animateCheck = new Checkbox("Animate");
	animateCheck.addItemListener(this);
	animateCheck.setState(true);
	add(animateCheck);
	
	modeChooser = new Choice();
	modeChooser.add("Mouse = Select exp(z)");
	modeChooser.add("Mouse = Select z");
	modeChooser.addItemListener(this);
	add(modeChooser);
	
	centerChooser = new Choice();
	centerChooser.add("Center = Origin");
	centerChooser.add("Center = exp(z)");
	centerChooser.addItemListener(this);
	add(centerChooser);
	
	add(new Label("Zoom", Label.CENTER));
	add(zoomBar = new Scrollbar(Scrollbar.HORIZONTAL, 96, 1, 60, 200));
	zoomBar.addAdjustmentListener(this);

	add(new Label("# of Terms", Label.CENTER));
	add(termsBar = new Scrollbar(Scrollbar.HORIZONTAL, 50, 1, 1, 50));
	termsBar.addAdjustmentListener(this);

	add(new Label("http://www.falstad.com", Label.CENTER));

	try {
	    String param = applet.getParameter("PAUSE");
	    if (param != null)
		pause = Integer.parseInt(param);
	} catch (Exception e) { }
	
	xpoints = new int[4];
	ypoints = new int[4];

	reinit();
	cv.setBackground(Color.black);
	cv.setForeground(Color.white);
	resize(550,550);
	handleResize();
	show();
    }

    void handleResize() {
	reinit();
    }

    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();
    }

    long lastTime = 0;
    
    public void updateEuler(Graphics realg) {
	zoom = java.lang.Math.exp(-(zoomBar.getValue()-100)*.3);
	Graphics g = null;
	if (winSize == null || winSize.width == 0)
	    return;
	g = dbimage.getGraphics();
	g.setColor(cv.getBackground());
	g.fillRect(0, 0, winSize.width, winSize.height);
	g.setColor(cv.getForeground());

	double expv = java.lang.Math.exp(zr);
	double resultr = java.lang.Math.cos(zi)*expv;
	double resulti = java.lang.Math.sin(zi)*expv;
	if (centerChooser.getSelectedIndex() != 0) {
	    orgx = resultr;
	    orgy = resulti;
	} else {
	    orgx = orgy = 0;
	}

	g.setColor(Color.darkGray);
	int gridSpace = 1;
	while (gridSpace*10 < zoom)
	    gridSpace *= 10;
	do {
	    int i;
	    for (i = -10; i <= 10; i++) {
		map2d(i*gridSpace, -10*gridSpace, 0);
		map2d(i*gridSpace,  10*gridSpace, 1);
		drawLine(g);
		map2d(-10*gridSpace, i*gridSpace, 0);
		map2d( 10*gridSpace, i*gridSpace, 1);
		drawLine(g);
	    }
	    gridSpace /= 10;
	} while (gridSpace > 0);

	int terms = termsBar.getValue();
	
	if (terms < 20) {
	    int i;
	    for (i = -100; i <= 100; i++) {
		double xr = 1, xi = 0;
		double ix = 0, iy = 0;
		double zi2 = pi*i/50.;
		int n = 0;
		for (; n < terms; n++) {
		    ix += xr; iy += xi;
		    double nxr = xr*zr - xi*zi2;
		    double nxi = xi*zr + xr*zi2;
		    xr = nxr/(n+1); xi = nxi/(n+1);
		}
		map2d(ix, iy, 1);
		if (i > -100)
		    drawLine(g);
		xpoints[0] = xpoints[1];
		ypoints[0] = ypoints[1];
	    }
	}

	map2d(-expv, expv, 0);
	map2d(expv, -expv, 1);
	drawOval(g, xpoints[0], ypoints[0],
		   xpoints[1]-xpoints[0], ypoints[1]-ypoints[0]);

	map2d(zr, zi, 0);
	map2d(zr+1, zi, 1);
	drawLine(g);

	g.setColor(Color.gray);
	map2d(0, 0, 0);
	drawLine(g, 0, ypoints[0], winSize.width-1, ypoints[0]);
	drawLine(g, xpoints[0], 0, xpoints[0], winSize.height-1);
	map2d(-1, 1, 0);
	map2d(1, -1, 1);
	drawOval(g, xpoints[0], ypoints[0],
		 xpoints[1]-xpoints[0], ypoints[1]-ypoints[0]);

	double xr = 1, xi = 0;
	double ix = 0, iy = 0;
	int n = 0;
	map2d(0, 0, 0);
	map2d(resultr, resulti, 1);
	drawLine(g);

	g.setColor(Color.white);
	for (; n < terms; n++) {
	    g.setColor((n % 2) == 0 ? Color.blue : Color.cyan);
	    map2d(ix, iy, 0);
	    ix += xr; iy += xi;
	    map2d(ix, iy, 1);
	    drawLine(g);
	    double nxr = xr*zr - xi*zi;
	    double nxi = xi*zr + xr*zi;
	    xr = nxr/(n+1); xi = nxi/(n+1);
	}
	g.setColor(Color.red);
	drawDot(g, resultr, resulti);
	g.setColor(Color.green);
	drawDot(g, zr, zi);

	int textspace = 20;
	g.setColor(cv.getBackground());
	g.fillRect(0, winSize.height-textspace, winSize.width-1, textspace);
	NumberFormat nf = NumberFormat.getInstance();
	nf.setMaximumFractionDigits(3);
	FontMetrics fm = g.getFontMetrics();
	String st = "z = " + formatNumber(zr, zi, nf);
	String st2 = "  exp(z) = " + formatNumber(resultr, resulti, nf);
	int ty = winSize.height-textspace/4;
	g.setColor(Color.green);
	g.drawString(st, 10, ty);
	g.setColor(Color.red);
	g.drawString(st2, 10+fm.stringWidth(st), ty);
	realg.drawImage(dbimage, 0, 0, this);
	
	if (animateCheck.getState() && !mouseDown) {
	    long tm = System.currentTimeMillis();
	    if (lastTime == 0)
		lastTime = tm;
	    zi += pi/100 * (tm-lastTime)/17.;
	    if (zi > pi)
		zi = -pi;
	    lastTime = tm;
	    cv.repaint(pause);
	} else
	    lastTime = 0;
    }

    String formatNumber(double r, double i, NumberFormat nf) {
	if (r == 0) {
	    if (i == 0)
		return "0";
	    return nf.format(i) + "i";
	}
	if (i == 0)
	    return nf.format(r);
	return nf.format(r) + (i < 0 ? "" : "+") + nf.format(i) + "i";
    }

    void drawOval(Graphics g, int x, int y, int rx, int ry) {
	if (onScreen(x, y) && onScreen(x+rx, y+ry))
	    g.drawOval(x, y, rx, ry);
    }

    boolean onScreen(int x, int y) {
	return (x >= 0 && x < winSize.width && y >= 0 && y < winSize.height);
    }

    boolean maxInt(int x) {
	return (x == -214748368 || x == 2147483647);
    }

    void drawLine(Graphics g) {
	drawLine(g, xpoints[0], ypoints[0], xpoints[1], ypoints[1]);
    }

    void drawLine(Graphics g, int x1, int y1, int x2, int y2) {
	// if you attempt to draw lines that are way offscreen, you can
	// crash the java runtime.  So we prevent that by doing a bunch
	// of bounds checking and clipping here.

	if (onScreen(x1, y1) && onScreen(x2, y2)) {
	    g.drawLine(x1, y1, x2, y2);
	    return;
	}
	if (maxInt(x1) || maxInt(y1) || maxInt(x2) || maxInt(y2))
	    return;
	if ((x1 < 0 && x2 < 0) || (y1 < 0 && y2 < 0) ||
	    (x1 >= winSize.width && x2 >= winSize.width) ||
	    (y1 >= winSize.height && y2 >= winSize.height))
	    return;
	if (x1 == x2) {
	    // lines with zero slope have to be handled as a special case
	    if (y1 < 0)
		y1 = 0;
	    if (y2 < 0)
		y2 = 0;
	    if (y1 >= winSize.height)
		y1 = winSize.height-1;
	    if (y2 >= winSize.height)
		y2 = winSize.height-1;
	    g.drawLine(x1, y1, x1, y2);
	    return;
	}
	double m = (y2-y1)/(double) (x2-x1);
	if (x1 < 0) {
	    y1 -= x1*m;
	    x1 = 0;
	}
	if (y1 < 0) {
	    x1 -= y1/m;
	    y1 = 0;
	}
	if (x2 < 0) {
	    y2 -= x2*m;
	    x2 = 0;
	}
	if (y2 < 0) {
	    x2 -= y2/m;
	    y2 = 0;
	}
	if (x1 >= winSize.width) {
	    int a = winSize.width-1-x1;
	    y1 += a*m;
	    x1 += a;
	}
	if (x2 >= winSize.width) {
	    int a = winSize.width-1-x2;
	    y2 += a*m;
	    x2 += a;
	}
	if (y1 >= winSize.height) {
	    int a = winSize.height-1-y1;
	    y1 += a;
	    x1 += a/m;
	}
	if (y2 >= winSize.height) {
	    int a = winSize.height-1-y2;
	    y2 += a;
	    x2 += a/m;
	}
	if (onScreen(x1, y1) && onScreen(x2, y2))
	    g.drawLine(x1, y1, x2, y2);
    }

    void drawDot(Graphics g, double x, double y) {
	map2d(x, y, 0);
	if (onScreen(xpoints[0], ypoints[0]))
	    g.fillOval(xpoints[0]-2, ypoints[0]-2, 5, 5);
    }

    void map2d(double x, double y, int n) {
	xpoints[n] = (int) (winSize.width * (zoom+x-orgx)/(zoom*2));
	ypoints[n] = (int) (winSize.height * (zoom-y+orgy)/(zoom*2));
    }

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

    public void componentResized(ComponentEvent e) {
	handleResize();
	cv.repaint(pause);
    }
    public void actionPerformed(ActionEvent e) {
    }

    public void adjustmentValueChanged(AdjustmentEvent e) {
	cv.repaint(pause);
    }

    public void mouseDragged(MouseEvent e) {
	doMouse(e);
    }
    public void mouseMoved(MouseEvent e) {
	/*if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0)
	    return;
	    doMouse(e);*/
    }
    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
    }
    public void mouseClicked(MouseEvent e) {
    }
    public void mousePressed(MouseEvent e) {
	doMouse(e);
    }
    void doMouse(MouseEvent e) {
	mouseDown = true;
	int ex = e.getX();
	int ey = e.getY();
	double x = ex*zoom*2./winSize.width - zoom + orgx;
	double y = -(ey*zoom*2./winSize.height - zoom - orgy);
	if (modeChooser.getSelectedIndex() == MODE_RESULT) {
	    if (x == 0 && y == 0)
		x = .00001;
	    zr = .5*java.lang.Math.log(x*x+y*y);
	    zi = java.lang.Math.atan2(y, x);
	} else {
	    zr = x;
	    zi = y;
	}
	cv.repaint(pause);
    }
    public void mouseReleased(MouseEvent e) {
	mouseDown = false;
	cv.repaint(pause);
    }
    public void itemStateChanged(ItemEvent e) {
	cv.repaint(pause);
    }
    public boolean handleEvent(Event ev) {
        if (ev.id == Event.WINDOW_DESTROY) {
            applet.destroyFrame();
            return true;
        }
        return super.handleEvent(ev);
    }
}
