/*
 * (c) 2015 CS448J Lecture, Stanford University.
 */

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;

/**
 * Simulation of a Foucault pendulum with simple forward Euler and classical Runge-Kutta integration.
 * 
* @author Dominik L. Michels <michels@cs.stanford.edu>
 */
public class FoucaultPendulum extends Applet implements ActionListener, Runnable {

    double phi = Math.PI / 2; //latitude in radian
    double T = 30; //length of a day in s
    double L = 5; //length of the pendulum in m
    double g = 9.81;
    double omega = 2 * Math.PI * Math.sin(phi) / T;
    double t = 0;
    double x = 0, dx = 2, ddx;
    double y = 0, dy = 0, ddy;
    double xa, ya, dxa, dya;
    int xint, yint, xinta, yinta;
    double dt = 0.05;
    double k1, k2, k3, k4, l1, l2, l3, l4, m1, m2, m3, m4, n1, n2, n3, n4, k, l, m, n;
    double x1 = x, x2 = dx, y1 = y, y2 = dy;
    double x1a, x2a, y1a, y2a;
    private Image trajectoryImage;
    private Graphics trajectoryGraphics;
    private Image bufferingImage;
    private Graphics bufferingGraphics;
    Thread runner;
    boolean started = false;
    Button btnSP, btnST, btnSW;
    boolean euler = false;
    boolean initial = true;

    public double f1(double x, double dy) {
        return 2 * omega * dy + (Math.pow(omega, 2) - g / L) * x;
    }

    public double f2(double dx, double y) {
        return -2 * omega * dx + (Math.pow(omega, 2) - g / L) * y;
    }

    public void euler() {
        ddx = f1(x, dy);
        ddy = f2(dx, y);

        dxa = dx;
        dx = dxa + ddx * dt;
        dya = dy;
        dy = dya + ddy * dt;

        xa = x;
        x = xa + dx * dt;
        ya = y;
        y = ya + dy * dt;

        t = t + dt;

        xinta = xint;
        yinta = yint;
        xint = (int) (75 * x + 150);
        yint = (int) (75 * y + 150);

        if (t > dt) {
            trajectoryGraphics.drawLine(xinta, yinta, xint, yint);
        }
    }

    public void rungeKutta() {
        k1 = dt * x2;
        l1 = dt * f1(x1, y2);
        m1 = dt * y2;
        n1 = dt * f2(x2, y1);

        k2 = dt * (x2 + l1 / 2);
        l2 = dt * f1(x1 + k1 / 2, y2 + n1 / 2);
        m2 = dt * (y2 + n1 / 2);
        n2 = dt * f2(x2 + l1 / 2, y1 + m1 / 2);

        k3 = dt * (x2 + l2 / 2);
        l3 = dt * f1(x1 + k2 / 2, y2 + n2 / 2);
        m3 = dt * (y2 + n2 / 2);
        n3 = dt * f2(x2 + l2 / 2, y1 + m2 / 2);

        k4 = dt * (x2 + l3);
        l4 = dt * f1(x1 + k3, y2 + n3);
        m4 = dt * (y2 + n3);
        n4 = dt * f2(x2 + l3, y1 + m3);

        k = (k1 + 2 * (k2 + k3) + k4) / 6;
        l = (l1 + 2 * (l2 + l3) + l4) / 6;
        m = (m1 + 2 * (m2 + m3) + m4) / 6;
        n = (n1 + 2 * (n2 + n3) + n4) / 6;

        x1a = x1;
        x1 = x1a + k;
        x2a = x2;
        x2 = x2a + l;
        y1a = y1;
        y1 = y1a + m;
        y2a = y2;
        y2 = y2a + n;

        xa = x1a;
        x = x1;
        ya = y1a;
        y = y1;

        t = t + dt;

        xinta = xint;
        yinta = yint;
        xint = (int) (75 * x + 150);
        yint = (int) (75 * y + 150);

        if (t > dt) {
            trajectoryGraphics.drawLine(xinta, yinta, xint, yint);
        }
    }

    void text(String txt, int x, int y, int s, Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        Font f = new Font("Sans Serif", Font.LAYOUT_LEFT_TO_RIGHT, s);
        FontMetrics fm = getFontMetrics(f);
        LineMetrics lm = fm.getLineMetrics(txt, g);
        g2d.setFont(f);
        g2d.drawString(txt, x, y);
    }

    public void paint(Graphics g) {
        if (initial) {
            g.setColor(Color.black);
            g.fillOval(145, 145, 10, 10);
            g.drawOval(0, 0, 300, 300);
            g.setColor(new Color(155, 155, 240));
        }

        g.setColor(Color.red);
        if (euler) {
            text("EULER", 0, 12, 12, g);
        } else {
            text("RUNGE-KUTTA", 0, 12, 12, g);
        }

        g.setColor(Color.black);

        text("t = " + String.valueOf(Math.round(t * 10.) / 10.) + "s", 0, 24, 12, g);

        g.drawLine(150, 150, xint, yint);

        g.setColor(Color.red);
        g.fillOval(xint - 10, yint - 10, 20, 20);

        g.setColor(Color.black);
        g.drawOval(xint - 10, yint - 10, 20, 20);
    }

    public void update(Graphics g) {
        int width = 400, height = 400;
        if (bufferingImage == null) {
            bufferingImage = createImage(width, height);
            bufferingGraphics = bufferingImage.getGraphics();
        }
        bufferingGraphics.setColor(getBackground());
        bufferingGraphics.fillRect(0, 0, width, height);
        bufferingGraphics.drawImage(trajectoryImage, 0, 0, this);
        paint(bufferingGraphics);
        g.drawImage(bufferingImage, 0, 0, this);
    }

    public void init() {
        setLayout(new BorderLayout());
        Panel mainPanel = new Panel();
        mainPanel.setLayout(new GridLayout(1, 2));
        btnSP = new Button("start/pause");
        btnST = new Button("stop");
        btnSW = new Button("switch");
        mainPanel.add(btnSP);
        mainPanel.add(btnST);
        mainPanel.add(btnSW);
        add(mainPanel, BorderLayout.SOUTH);
        btnSP.addActionListener(this);
        btnST.addActionListener(this);
        btnSW.addActionListener(this);

        t = -dt;
        x = 0;
        y = 0;

        trajectoryImage = createImage(800, 800);
        trajectoryGraphics = trajectoryImage.getGraphics();
        trajectoryGraphics.setColor(Color.black);
        trajectoryGraphics.fillOval(145, 145, 10, 10);
        trajectoryGraphics.drawOval(0, 0, 300, 300);
        trajectoryGraphics.setColor(new Color(155, 155, 240));

        euler();
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == btnSP) {
            if (started) {
                runner.stop();
            } else {
                runner = new Thread(this);
                runner.start();
            }
            started = !started;
        } else if ((e.getSource() == btnST) || (e.getSource() == btnSW)) {

            try {
                runner.stop();
            } catch (Exception oops) {
            }
            t = -dt;
            x = 1;
            dx = 0;
            y = 1;
            dy = 0;
            x1 = x;
            x2 = dx;
            y1 = y;
            y2 = dy;

            trajectoryImage = createImage(800, 800);
            trajectoryGraphics = trajectoryImage.getGraphics();
            trajectoryGraphics.setColor(Color.black);
            trajectoryGraphics.fillOval(145, 145, 10, 10);
            trajectoryGraphics.drawOval(0, 0, 300, 300);
            trajectoryGraphics.setColor(new Color(155, 155, 240));

            if (e.getSource() == btnSW) {
                euler = !euler;
            }
            if (euler) {
                euler();
            } else {
                rungeKutta();
            }

            if (started) {
                started = !started;
            }
            repaint();
        }
    }

    public void run() {
        initial = false;
        while (true) {
            repaint();
            if (euler) {
                euler();
            } else {
                rungeKutta();
            }
            try {
                Thread.sleep((int) (50));
            } catch (InterruptedException e) {
            }
        }
    }
}