import java.text.NumberFormat;
import java.util.Observable;
import java.io.*;

        /** Definition of a Ship class with position,
         *  speed, course and name information.
         *  @author David J. Barnes (d.j.barnes@ukc.ac.uk)
         *  @version Version 2.0 (14th Apr 1999)<br>
         */
public class Ship extends Observable implements Runnable {
        /** Default X position. */
    public static final double DEFAULT_X = 25.0;
        /** Default Y position. */
    public static final double DEFAULT_Y = 25.0;
        /** Default course. */
    public static final double DEFAULT_COURSE = 0.0;
        /** Default speed. */
    public static final double DEFAULT_SPEED = 5.0;

        /** Construct with explicit position, course, speed and name.
         */
    public Ship(double x, double y, double course, double speed,String name){
        setSpeed(speed);
        setPosition(x,y);
        setCourse(course);
        this.name = name;

        // Am I the first?
        if(getFormatter() == null){
            formatter = NumberFormat.getNumberInstance();
            // Print to two decimal places.
            formatter.setMaximumFractionDigits(2);
            setFormatter(formatter);
        }

        // Add myself to the GUI.
        sea.add(this);
    }

        /** Construct with explicit position, course and speed.
         *  The ship has no name.
         */
    public Ship(double x, double y, double course, double speed){
        this(x,y,course,speed,"");
    }

        /** Construct with explicit position and course.
         *  The ship has no name.
         */
    public Ship(double x, double y, double course){
        this(x,y,course,DEFAULT_SPEED);
    }

        /** Construct with explicit position.
         *  The ship has no name.
         */
    public Ship(double x, double y){
        this(x,y,DEFAULT_COURSE);
    }

        /** Construct with default position, course and speed.
         *  The ship has no name.
         */
    public Ship(){
        // Set default position, course and speed for this ship.
        this(DEFAULT_X,DEFAULT_Y);
    }

        /** Construct with a name.
         */
    public Ship(String name){
        this(DEFAULT_X,DEFAULT_Y,DEFAULT_COURSE,DEFAULT_SPEED,name);
    }

        /** Set the ship's position.
         */
    public void setPosition(double x, double y){
        setPosition(new Position(x,y));
        radarBlip();
    }

        /** Set the ship's position.
         */
    public void setPosition(Position newPosition){
        position = newPosition;
    }

        /** Set the ship's speed.
         *  @param newSpeed A positive or negative value.
         */
    public void setSpeed(double newSpeed){
        speed = newSpeed;
    }

        /** Set the ship's course.
         *  @param newCourse The course in degrees. Values are
         *  adjusted to the range [0..360)
         */
    public void setCourse(double newCourse){
        // Make sure it is in the range [0..360)
        final double numDegrees = 360.0;
        if(newCourse < 0){
            newCourse = numDegrees+(newCourse%numDegrees);
        }
        course = newCourse % numDegrees;
        radarBlip();
    }

        /** Report the ship's position, course and speed.
         *  Reporting is done via the ship's current.
         *  @see #getWriter()
         *  @see #setWriter(PrintWriter)
         */
    public void report(){
        Position p = getPosition();
        NumberFormat formatter = getFormatter();
        PrintWriter writer = getWriter();

        String name = getName();
        if((name != null) && !name.equals("")){
            writer.print(name+": ");
        }
        writer.print("Position is ("+formatter.format(p.getX())+","+
                                   formatter.format(p.getY())+"), ");
        writer.print("Course is "+formatter.format(getCourse())+", ");
        writer.print("Speed is "+formatter.format(getSpeed()));
        writer.println();
        writer.flush();
    }

        /** Move the ship according to its current speed and position.
         */
    public void move(){
        double radians = Math.toRadians(getCourse());
        Position p = getPosition();
        setPosition(p.getX()+speed*Math.cos(radians),
                    p.getY()+speed*Math.sin(radians));
    }

        /** An implementation of the Runnable interface, to enable a Ship
         *  to be run in an independent thread, if required.
         *  @see java.lang.Runnable
         */
    public void run(){
        while(true){
            move();
            report();
            // Be prepared to yield, in case we are not sleeping for
            // some reason.
            Thread.yield();
        }
    }

        /** Return the current position.
         *  @see Position
         */
    public Position getPosition(){
        return new Position(position.getX(),position.getY());
    }

        /** Return the current course.
         */
    public double getCourse(){
        return course;
    }

        /** Return the current speed.
         */
    public double getSpeed(){
        return speed;
    }

        /** Return the current name.
         */
    public String getName(){
        return name;
    }

        /** Return the current course and position.
         *  @see CourseAndPosition
         */
    public CourseAndPosition getCourseAndPosition(){
        return new CourseAndPosition(getCourse(),getPosition());
    }

        /** Get the writer to be used for reporting.
         *  @see #report()
         */
    public PrintWriter getWriter(){
        return writer;
    }

        /** Set the writer to be used for reporting.
         *  @see #report()
         */
    public void setWriter(PrintWriter w){
        writer = w;
    }

        /** Notify any observers of a significant change.
         */
    protected void radarBlip(){
        setChanged();
        notifyObservers(getCourseAndPosition());
        // Give the UI a chance to display us.
        delay();
    }

        /** Make the animation a little easier to follow.
         *  Sleep for a time related to the ship's speed.
         *  @see #getDelayTime()
         *  @see #setDelayTime(int)
         */
    protected void delay(){
        try{
            final int maxSleepTime = getDelayTime();
            int sleepTime;
            final double speed = getSpeed();
            if(speed == 0){
                sleepTime = maxSleepTime;
            }
            else{
                sleepTime = (int) Math.abs(maxSleepTime/speed);
            }
            Thread.sleep(sleepTime);
        }
        catch(InterruptedException e){
        }
    }

    protected NumberFormat getFormatter(){
        return formatter;
    }

    protected void setFormatter(NumberFormat f){
        formatter = f;
    }

        /** Get the delay time (in milliseconds) used by the animation.
         */
    protected int getDelayTime(){
        return delayTime;
    }

        /** Set the delay time used by the animation.
         *  @param delay The delay time in milliseconds.
         */
    protected void setDelayTime(int delay){
        if(delay >= 0){
            delayTime = delay;
        }
    }

    // (x,y) co-ordinates in the ocean.
    private Position position;
    // Speed and course
    private double speed;
    // Keep degrees in the range [0..360)
    private double course;
    // A name for the ship.
    private final String name;

    // The GUI used to display ships. Shared by them all.
    private static final SeaGUI sea = new SeaGUI();
    // A formatter used in reporting. Shared by all ships.
    private static NumberFormat formatter;
    // How long to delay between moves, to make the animation easier
    // to follow. A value in milliseconds.
    private int delayTime = 4000;
    // Where reports are printed to. Standard output by default.
    private PrintWriter writer = new PrintWriter(System.out);
}
