// CO307 Project: TheShip

import java.awt.*;
import java.applet.*;

class Ship {
    private static final int
        MAX_SPEED = 10, MAX_ANGLE = 45, MAX_WIND = 40, MAX_SAIL = 20,
        MAX_FORCE = (MAX_WIND/2)*(MAX_SAIL/2),
        SHIP_LEN = 20, WIND_LEN = 30, WIND_XPOS = 580, WIND_YPOS = 380;

    private int ShipXpos, ShipYpos, ShipDir, ShipSail;	// Ship state
    private int WindDir, WindSpeed;             	// Wind state

    // Ship constructor - Initialize ship/wind state
    public Ship(int x, int y, int d, int wd, int ws) {
	ShipXpos  = x;
	ShipYpos  = y;
	ShipDir   = d;
	ShipSail  = 0;		// No sail at start
	WindDir   = wd;
	WindSpeed = ws;
    }

    // Degree -> Radians Conversion
    private static final double PI_DIV_180 = 0.0174532925199432958;
    private double torad(double deg) { return deg*PI_DIV_180; }

    // Return x/y displacements for line of length at angle
    private double xdisp(int length, int angle) {
        return length * Math.cos(torad(angle));
    }
    private double ydisp(int length, int angle) {
        return length * Math.sin(torad(angle));
    }

    // Add offset to angle (in degrees) - ensures 0 <= result < 360
    private int addangle(int angle, int offset) {
        return (angle+360+offset)%360;
    }

    // Return the acute difference between two angles (in degrees)
    private int diffacute(int d1, int d2) {
        if ((d1 = addangle(d1, -d2)) > 180) d1 = 360-d1;
        return d1;
    }

    // Draw an arrow of length len at angle centred on point x,y
    private void drawArrow(int xpos, int ypos, int angle, int len, Graphics g) {
	int x, y;

	len /= 2;				// Halve length (mid-point)
	x = (int)xdisp(len, angle);
	y = (int)ydisp(len, angle);
	g.drawLine(xpos-x, ypos-y, xpos+x, ypos+y);  // Arrow stem
	xpos += x;	ypos += y;		// Move to arrow point
	x = (int)xdisp(len, angle+135);
	y = (int)ydisp(len, angle+135);
	g.drawLine(xpos, ypos, xpos+x, ypos+y);	// Left arrow-part
	x = (int)xdisp(len, angle+225);
	y = (int)ydisp(len, angle+225);
	g.drawLine(xpos, ypos, xpos+x, ypos+y);	// Right arrow-part
    }

    // Draw the ship
    public void drawShip(Graphics g) {
        drawArrow(ShipXpos, ShipYpos, ShipDir, SHIP_LEN, g);
    }

    // Draw the wind-indicator
    public void drawWind(Graphics g) {
        drawArrow(WIND_XPOS, WIND_YPOS, WindDir, WIND_LEN, g);
    }

    // Calculate movement offsets and move ship - returns false if sunk
    public boolean moveShip() {
	double s;

	s = WindSpeed * ShipSail;
	if (s>MAX_FORCE) return false;	// Sink the ship?
	s /= (MAX_FORCE/MAX_SPEED);	// Convert force to speed

	// Reduce speed in proportion to angle between ship & wind direction
	// Multiplies speed by a number between 0 and 1, proportional to angle
	s *= (1 - (diffacute(WindDir, ShipDir) / (MAX_ANGLE+1)));

	ShipXpos += xdisp((int)s, ShipDir);
	ShipYpos += ydisp((int)s, ShipDir);

	// Collision-testing code can go here (islands etc)

	return true;
    }

    public void ChangeHeading(int x) {
        ShipDir = addangle(ShipDir, x);
    }

    public void ChangeSailArea(int s) {
        if ((ShipSail+s>=0) && (ShipSail+s<=MAX_SAIL))
	    ShipSail += s;
    }

    public void ChangeWind(int x, int s) {
	WindDir = addangle(WindDir, x);
	if ((WindSpeed+s>=0) && (WindSpeed+s<=MAX_WIND))
	    WindSpeed += s;
    }

}

public class TheShip extends Applet implements Animation {
    Button sleft, sright, sup, sdown, wleft, wright, wup, wdown;
    AnimationTimer timer = new AnimationTimer(this, 200);
    Ship myShip = new Ship(300, 200, 270, 270, 10);
    int sdir = 0, ssail = 0, wdir = 0, wspeed = 0;
    boolean afloat = true;

    // Initialize the applet
    public void init() {
        sleft = new Button("Sleft");	add(sleft);	// Ship left
        sright = new Button("Sright");	add(sright);	// Ship right
        sup = new Button("Sup");	add(sup);	// Sail up
        sdown = new Button("Sdown");	add(sdown);	// Sail down
        wleft = new Button("Wleft");	add(wleft);	// Wind left
        wright = new Button("Wright");	add(wright);	// Wind right
        wup = new Button("Wup");	add(wup);	// Wind up
        wdown = new Button("Wdown");	add(wdown);	// Wind down
    }

    // Process pressed buttons
    public boolean action(Event event, Object arg) {
        if (event.target == sleft) sdir = -1;	// Left one degree
        if (event.target == sright) sdir = 1;	// Right one degree
        if (event.target == sup) ssail = 5;	// Up 5 square metres
        if (event.target == sdown) ssail = -5;	// Down 5 square metres
        if (event.target == wleft) wdir = -1;	// Left one degree
        if (event.target == wright) wdir = 1;	// Right one degree
        if (event.target == wup) wspeed = 1;	// Up one knot
        if (event.target == wdown) wspeed = -1;	// Down one knot
        return true;
    }

    // Redraw the display
    public void paint(Graphics g) {
        g.setColor(Color.red);
        myShip.drawShip(g);
        g.setColor(Color.blue);
        myShip.drawWind(g);
    }

    // Start and stop the AnimationTimer thread
    public void start() { timer.start_animation();}
    public void stop() { timer.pause_animation();}

    // Animation Interface Implementation
    // Check for input and move the ship (if still afloat)
    public void animate() {
        if (afloat) {
            if (sdir!=0) {                 // Change ship heading
                myShip.ChangeHeading(sdir); sdir = 0;
            }
            if (ssail!=0) {                // Change sail area
                myShip.ChangeSailArea(ssail); ssail = 0;
            }
            if ((wdir!=0)||(wspeed!=0)) {  // Change wind speed/direction
                myShip.ChangeWind(wdir, wspeed);
                wdir = 0; wspeed = 0;
            }
            afloat = myShip.moveShip();
            repaint();
        }
    }
}

// Animation Interface
interface Animation { public void animate(); }

// Animation Thread Control
class AnimationTimer extends Thread {
    Animation animation;	// Object which implements Animation interface
    int delay;			// Animation timer delay

    // Constructor - set Animation implementation object and delay
    public AnimationTimer(Animation animation, int delay) {
        this.animation = animation;
        this.delay = delay;
    }

    // Start or restart the animation thread
    public void start_animation() {
        if (isAlive()) resume();
        else start();
    }

    // Suspend the animation thread
    public void pause_animation() { suspend(); }

    // Animation timer loop
    public void run() {
        for (;;) {
            animation.animate();
            try { Thread.sleep(delay); } catch (InterruptedException e){;}
        }
    }
}

