package seng302.Model;

import seng302.Constants;
import seng302.Networking.Messages.Enums.BoatStatusEnum;


/**
 * This class represents a boat during a race.
 */
public class Boat {
    /**
     * The name of the boat/team.
     */
    private String name;

    /**
     * The current speed of the boat, in knots.
     * TODO knots
     */
    private double currentSpeed;

    /**
     * The current bearing/heading of the boat.
     */
    private Bearing bearing;

    /**
     * The current position of the boat.
     */
    private GPSCoordinate currentPosition;

    /**
     * The country or team abbreviation of the boat.
     */
    private String country;

    /**
     * The source ID of the boat.
     * This uniquely identifies an entity during a race.
     */
    private int sourceID;

    /**
     * The leg of the race that the boat is currently on.
     */
    private Leg currentLeg;

    /**
     * The distance, in meters, that the boat has travelled in the current leg.
     * TODO meters
     */
    private double distanceTravelledInLeg;

    /**
     * The time, in milliseconds, that has elapsed during the current leg.
     * TODO milliseconds
     */
    private long timeElapsedInCurrentLeg;

    /**
     * The timestamp, in milliseconds, of when the boat finished the race.
     * Is -1 if it hasn't finished.
     * TODO milliseconds
     */
    private long timeFinished = -1;

    /**
     * The current status of the boat.
     */
    private BoatStatusEnum status;

    /**
    * This stores a boat's polars table.
    * Can be used to calculate VMG.
    */
    private Polars polars;

    /**
    * This stores the milliseconds since the boat has changed its tack, to allow for only updating the tack every X milliseconds.
    * TODO milliseconds
    */
    private long timeSinceTackChange = 0;


    /**
     * Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table.
     *
     * @param sourceID The id of the boat
     * @param name The name of the Boat.
     * @param country The abbreviation or country code for the boat.
     * @param polars The polars table to use for this boat.
     */
    public Boat(int sourceID, String name, String country, Polars polars) {
        this.country = country;
        this.name = name;
        this.sourceID = sourceID;
        this.polars = polars;

        this.bearing = Bearing.fromDegrees(0d);

        this.status = BoatStatusEnum.UNDEFINED;
    }




    /**
     * Calculate the bearing of the boat to its next marker.
     * @return The bearing to the next marker.
     */
    public Bearing calculateBearingToNextMarker() {

        //Get the start and end points.
        GPSCoordinate currentPosition = this.getCurrentPosition();
        GPSCoordinate nextMarkerPosition = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate();

        //Calculate bearing.
        Bearing bearing = GPSCoordinate.calculateBearing(currentPosition, nextMarkerPosition);

        return bearing;
    }



    /**
     * Calculates the distance between the boat and its target marker in nautical miles.
     * @return The distance (in nautical miles) between the boat and its target marker.
     */
    public double calculateDistanceToNextMarker() {

        //Get start and end markers.
        GPSCoordinate startPosition = this.getCurrentPosition();

        //When boats finish, their "current leg" doesn't have an end marker.
        if (this.getCurrentLeg().getEndCompoundMark() == null) {
            return 0d;
        }

        GPSCoordinate endMarker = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate();


        //Calculate distance.
        double distanceNauticalMiles = GPSCoordinate.calculateDistanceNauticalMiles(startPosition, endMarker);

        return distanceNauticalMiles;
    }


    /**
     * Returns the name of the boat/team.
     * @return Name of the boat/team.
     */
    public String getName() {
        return name;
    }

    /**
     * Sets the name of the boat/team.
     * @param name Name of the boat/team.
     */
    public void setName(String name) {
        this.name = name;
    }


    /**
     * Returns the current speed of the boat, in knots.
     * @return The current speed of the boat, in knots.
     */
    public double getCurrentSpeed() {
        return currentSpeed;
    }

    /**
     * Sets the speed of the boat, in knots.
     * @param currentSpeed The new speed of the boat, in knots.
     */
    public void setCurrentSpeed(double currentSpeed) {
        this.currentSpeed = currentSpeed;
    }


    /**
     * Gets the country/team abbreviation of the boat.
     * @return The country/team abbreviation of the boat.
     */
    public String getCountry() {
        return country;
    }

    /**
     * Sets the country/team abbreviation of the boat.
     * @param country  The new country/team abbreviation for the boat.
     */
    public void setCountry(String country) {
        this.country = country;
    }


    /**
     * Returns the source ID of the boat.
     * @return The source ID of the boat.
     */
    public int getSourceID() {
        return sourceID;
    }

    /**
     * Sets the source ID of the boat.
     * @param sourceID The new source ID for the boat.
     */
    public void setSourceID(int sourceID) {
        this.sourceID = sourceID;
    }

    /**
     * Returns the current leg of the race the boat is in.
     * @return The current leg of the race the boat is in.
     */
    public Leg getCurrentLeg() {
        return currentLeg;
    }

    /**
     * Sets the current leg of the race the boat is in.
     * Clears time elapsed in current leg and distance travelled in current leg.
     * @param currentLeg The new leg of the race the boat is in.
     */
    public void setCurrentLeg(Leg currentLeg) {
        this.currentLeg = currentLeg;
        this.setTimeElapsedInCurrentLeg(0);
        this.setDistanceTravelledInLeg(0);
    }


    /**
     * Returns the distance, in meters, the boat has travelled in the current leg.
     * @return The distance, in meters, the boat has travelled in the current leg.
     */
    public double getDistanceTravelledInLeg() {
        return distanceTravelledInLeg;
    }

    /**
     * Sets the distance, in meters, the boat has travelled in the current leg.
     * @param distanceTravelledInLeg The distance, in meters, the boat has travelled in the current leg.
     */
    public void setDistanceTravelledInLeg(double distanceTravelledInLeg) {
        this.distanceTravelledInLeg = distanceTravelledInLeg;
    }


    /**
     * Returns the current position of the boat.
     * @return The current position of the boat.
     */
    public GPSCoordinate getCurrentPosition() {
        return currentPosition;
    }

    /**
     * Sets the current position of the boat.
     * @param currentPosition The new position for the boat.
     */
    public void setCurrentPosition(GPSCoordinate currentPosition) {
        this.currentPosition = currentPosition;
    }


    /**
     * Gets the timestamp, in milliseconds, at which the boat finished the race.
     * @return The timestamp, in milliseconds, at which the boat finished the race.
     */
    public long getTimeFinished() {
        return timeFinished;
    }

    /**
     * Sets the timestamp, in milliseconds, at which the boat finished the race.
     * @param timeFinished The timestamp, in milliseconds, at which the boat finished the race.
     */
    public void setTimeFinished(long timeFinished) {
        this.timeFinished = timeFinished;
    }




    /**
     * Returns the current bearing of the boat.
     * @return The current bearing of the boat.
     */
    public Bearing getBearing() {
        return bearing;
    }

    /**
     * Sets the current bearing of the boat.
     * @param bearing The new bearing of the boat.
     */
    public void setBearing(Bearing bearing) {
        this.bearing = bearing;
    }


    /**
     * Returns the polars table for this boat.
     * @return The polars table for this boat.
     */
    public Polars getPolars() {
        return polars;
    }

    /**
     * Sets the polars table for this boat.
     * @param polars The new polars table for this boat.
     */
    public void setPolars(Polars polars) {
        this.polars = polars;
    }


    /**
     * Returns the time since the boat changed its tack, in milliseconds.
     * @return Time since the boat changed its tack, in milliseconds.
     */
    public long getTimeSinceTackChange() {
        return timeSinceTackChange;
    }

    /**
     * Sets the time since the boat changed it's tack, in milliseconds.
     * @param timeSinceTackChange Time since the boat changed its tack, in milliseconds.
     */
    public void setTimeSinceTackChange(long timeSinceTackChange) {
        this.timeSinceTackChange = timeSinceTackChange;
    }


    /**
     * Returns the time, in milliseconds, that has elapsed since the boat started the current leg.
     * @return The time, in milliseconds, that has elapsed since the boat started the current leg.
     */
    public long getTimeElapsedInCurrentLeg() {
        return timeElapsedInCurrentLeg;
    }

    /**
     * Sets the time, in milliseconds, that has elapsed since the boat started the current leg.
     * @param timeElapsedInCurrentLeg The new time, in milliseconds, that has elapsed since the boat started the current leg.
     */
    public void setTimeElapsedInCurrentLeg(long timeElapsedInCurrentLeg) {
        this.timeElapsedInCurrentLeg = timeElapsedInCurrentLeg;
    }


    /**
     * Returns the status of the boat.
     * @return The sttus of the boat.
     */
    public BoatStatusEnum getStatus() {
        return status;
    }

    /**
     * Sets the status of the boat.
     * @param status The new status of the boat.
     */
    public void setStatus(BoatStatusEnum status) {
        this.status = status;
    }


    /**
     * Moves the boat meters forward in the direction that it is facing
     * @param meters The number of meters to move forward.
     * @param milliseconds The number of milliseconds to advance the boat's timers by.
     */
    public void moveForwards(double meters, long milliseconds) {


        //Update the boat's time since last tack.
        this.setTimeSinceTackChange(this.getTimeSinceTackChange() + milliseconds);

        //Update the time into the current leg.
        this.setTimeElapsedInCurrentLeg(this.getTimeElapsedInCurrentLeg() + milliseconds);

        //Update the distance into the current leg.
        this.setDistanceTravelledInLeg(this.getDistanceTravelledInLeg() + meters);

        //Updates the current position of the boat.
        GPSCoordinate newPosition = GPSCoordinate.calculateNewPosition(this.getCurrentPosition(), meters, Azimuth.fromBearing(this.getBearing()));
        this.setCurrentPosition(newPosition);

    }


    /**
     * Sets the boats speed and bearing to those in the given VMG.
     * @param newVMG The new VMG to use for the boat - contains speed and bearing.
     */
    public void setVMG(VMG newVMG) {
        this.setBearing(newVMG.getBearing());
        this.setCurrentSpeed(newVMG.getSpeed());
        this.setTimeSinceTackChange(0);
    }


    /**
     * Calculates the number of nautical miles the boat will travel in a given time slice.
     * E.g., in 53 milliseconds a boat may travel 0.0002 nautical miles.
     * @param timeSlice The timeslice to use.
     * @return The distance travelled, in nautical miles, over the given timeslice.
     */
    public double calculateNauticalMilesTravelled(long timeSlice) {

        //The proportion of one hour the current timeslice is.
        //This will be a low fractional number, so we need to go from long -> double.
        double hourProportion = ((double) timeSlice) / Constants.OneHourMilliseconds;

        //Calculates the distance travelled, in nautical miles, in the current timeslice.
        //distanceTravelledNM = speed (nm p hr) * time taken to update loop
        double distanceTravelledNM = this.getCurrentSpeed() * hourProportion;

        return distanceTravelledNM;
    }

    /**
     * Calculates the number of meters the boat will travel in a given time slice.
     * E.g., in 53 milliseconds a boat may travel 0.02 meters.
     * @param timeSlice The timeslice to use.
     * @return The distance travelled, in meters, over the given timeslice.
     */
    public double calculateMetersTravelled(long timeSlice) {

        //Calculate the distance travelled, in nautical miles.
        double distanceTravelledNM = this.calculateNauticalMilesTravelled(timeSlice);

        //Convert to meters.
        double distanceTravelledMeters = distanceTravelledNM * Constants.NMToMetersConversion;

        return distanceTravelledMeters;
    }

}
