// CO307 Project: TheShip

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

class ShipsLog {
    int Time, Xpos, Ypos, Dir;
    ShipsLog next;

    // Ship's log constructor
    public ShipsLog(int t, int x, int y, int d) {
        this.Time = t;
        this.Xpos = x;
        this.Ypos = y;
        this.Dir = d;
        this.next = null;
    }
}

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
    private int Time;
    ShipsLog Log;

    // 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;
        Time = 0;
        Log = new ShipsLog(Time, ShipXpos, ShipYpos, ShipDir);
    }

    // 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)

        Time++;
	return true;
    }

    // Write state data to ship's log
    public void writeLog() {
        ShipsLog entry = Log;
        while (entry.next!=null) entry = entry.next;
        entry.next = new ShipsLog(Time, ShipXpos, ShipYpos, ShipDir);
    }

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

    // Generate a random integer between 0 and n-1
    private int randint(int n) { return (int)(Math.random()*n); }

    // Randomly generate values to modify wind state
    public void randomWind(int dir, int speed) {
        switch (randint(dir)) {		// Change wind direction
          case 0:	dir = -1;	break;	// Left one degree
          case 1:	dir = 1;	break;	// Right one degree
          default:	dir = 0;		// No change
        }
        switch (randint(speed)) {	// Change wind speed
          case 0:	speed = 1;	break;	// Up one knot
          case 1:	speed = -1;	break;	// Down one knot
          default:	speed = 0;		// No change
        }
        if ((dir!=0)||(speed!=0)) ChangeWind(dir, speed);
    }

}

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

    // Initialize the applet
    public void init() {
        sleft = new Button("Left");		add(sleft);	// Ship left
        sright = new Button("Right");		add(sright);	// Ship right
        sup = new Button("Sail Up");		add(sup);	// Sail up
        sdown = new Button("Sail Down");	add(sdown);	// Sail 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
        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;
                myShip.writeLog();		// Write to ship's log
            }
            if (ssail!=0) {			// Change sail area
                myShip.ChangeSailArea(ssail);	ssail = 0;
            }
            myShip.randomWind(4, 4);		// Change wind speed/direction
            afloat = myShip.moveShip();		// Move the ship
            if (!afloat) myShip.writeLog();	// Final log entry (sunk)
            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){;}
        }
    }
}

