package seng302.Model;

import javafx.animation.AnimationTimer;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import seng302.Constants;
import seng302.DataInput.RaceDataSource;
import seng302.MockOutput;
import seng302.Networking.Messages.BoatLocation;
import seng302.Networking.Messages.BoatStatus;
import seng302.Networking.Messages.Enums.BoatStatusEnum;
import seng302.Networking.Messages.Enums.RaceStatusEnum;
import seng302.Networking.Messages.Enums.RaceTypeEnum;
import seng302.Networking.Messages.RaceStatus;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;


/**
 * Represents a yacht race.
 * Has a course, boats, boundaries, etc...
 * Is responsible for simulating the race, and sending messages to a MockOutput instance.
 */
public class Race implements Runnable {

    /**
     * An observable list of boats in the race.
     */
    private ObservableList<Boat> boats;

    /**
     * An observable list of compound marks in the race.
     */
    private ObservableList<CompoundMark> compoundMarks;

    /**
     * A list of legs in the race.
     */
    private List<Leg> legs;

    /**
     * A list of coordinates describing the boundary of the course.
     */
    private List<GPSCoordinate> boundary;

    /**
     * The elapsed time, in milliseconds, of the race.
     */
    private long totalTimeElapsed;

    /**
     * The starting timestamp, in milliseconds, of the race.
     */
    private long startTime;

    /**
     * The scale factor of the race.
     * Frame periods are multiplied by this to get the amount of time a single frame represents.
     * E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred.
     */
    private int scaleFactor = 5;

    /**
     * The race ID of the course.
     */
    private int raceId;

    /**
     * The current status of the race.
     */
    private RaceStatusEnum raceStatusEnum;

    /**
     * The type of race this is.
     */
    private RaceTypeEnum raceType;

    /**
     * The percent chance that a boat fails the race, and enters a DNF state, at each checkpoint.
     * 0 = 0%, 100 = 100%.
     */
    private int dnfChance = 0;


    /**
     * The mockOutput to send messages to.
     */
    private MockOutput mockOutput;


    /**
     * Wind direction bearing.
     */
    private Bearing windDirection;

    /**
     * Wind speed (knots).
     * Convert this to millimeters per second before passing to RaceStatus.
     */
    private double windSpeed;

    private int windDir;
    private int changeWind = 4;



    /**
     * Constructs a race object with a given RaceDataSource and sends events to the given mockOutput.
     * @param raceData Data source for race related data (boats, legs, etc...).
     * @param mockOutput The mockOutput to send events to.
     */
    public Race(RaceDataSource raceData, MockOutput mockOutput) {

        this.mockOutput = mockOutput;

        this.boats = FXCollections.observableArrayList(raceData.getBoats());
        this.compoundMarks = FXCollections.observableArrayList(raceData.getCompoundMarks());
        this.boundary = raceData.getBoundary();
        this.legs = raceData.getLegs();
        this.legs.add(new Leg("Finish", this.legs.size()));

        this.raceId = raceData.getRaceId();

        //The start time is current time + 4 minutes, scaled. prestart is 3 minutes, and we add another.
        this.startTime = System.currentTimeMillis() + ((Constants.RacePreStartTime + (1 * 1000)) / this.scaleFactor);

        this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE);
        this.raceType = RaceTypeEnum.FLEET_RACE;

        this.windSpeed = 12;
        this.windDirection = Bearing.fromDegrees(180);


    }

    /**
     * Runnable for the thread.
     */
    public void run() {
        initialiseBoats();
        initialiseWindDir();
        countdownTimer.start();
    }

    /**
     * Parse the compound marker boats through mock output.
     */
    private void parseMarks() {
        for (CompoundMark compoundMark : this.compoundMarks) {

            //Get the individual marks from the compound mark.
            Mark mark1 = compoundMark.getMark1();
            Mark mark2 = compoundMark.getMark2();

            //If they aren't null, parse them (some compound marks only have one mark).
            if (mark1 != null) {
                this.parseIndividualMark(mark1);
            }

            if (mark2 != null) {
                this.parseIndividualMark(mark2);
            }

        }
    }

    /**
     * Parses an individual marker boat, and sends it to mockOutput.
     * @param mark The marker boat to parse.
     */
    private void parseIndividualMark(Mark mark) {

        this.mockOutput.parseBoatLocation(mark.getSourceID(), mark.getPosition().getLatitude(), mark.getPosition().getLongitude(),0,0);

    }

    /**
     * Parse the boats in the race, and send it to mockOutput.
     */
    private void parseBoatLocations() {

        //Parse each boat.
        for (Boat boat : this.boats) {

            this.parseIndividualBoatLocation(boat);

        }

    }

    /**
     * Parses an individual boat, and sends it to mockOutput.
     * @param boat The boat to parse.
     */
    private void parseIndividualBoatLocation(Boat boat) {

        this.mockOutput.parseBoatLocation(
                boat.getSourceID(),
                boat.getCurrentPosition().getLatitude(),
                boat.getCurrentPosition().getLongitude(),
                boat.getBearing().degrees(),
                boat.getCurrentSpeed()
        );

    }


    /**
     * Updates the race status enumeration based on the current time, in milliseconds.
     * @param currentTime The current time, in milliseconds.
     */
    private void updateRaceStatusEnum(long currentTime) {

        //The amount of milliseconds until the race starts.
        long timeToStart = this.startTime - currentTime;

        //Scale the time to start based on the scale factor.
        long timeToStartScaled = timeToStart / this.scaleFactor;


        if (timeToStartScaled > Constants.RacePreStartTime) {
            //Time > 3 minutes is the prestart period.
            this.setRaceStatusEnum(RaceStatusEnum.PRESTART);

        } else if ((timeToStartScaled <= Constants.RacePreStartTime) && (timeToStartScaled >= Constants.RacePreparatoryTime)) {
            //Time between [1, 3] minutes is the warning period.
            this.setRaceStatusEnum(RaceStatusEnum.WARNING);

        } else if ((timeToStartScaled <= Constants.RacePreparatoryTime) && (timeToStartScaled > 0)) {
            //Time between (0, 1] minutes is the preparatory period.
            this.setRaceStatusEnum(RaceStatusEnum.PREPARATORY);

        } else {
            //Otherwise, the race has started!
            this.setRaceStatusEnum(RaceStatusEnum.STARTED);

        }


    }

    /**
     * Parses the race status, and sends it to mockOutput.
     */
    private void parseRaceStatus() {

        //A race status message contains a list of boat statuses.
        List<BoatStatus> boatStatuses = new ArrayList<>();

        //Add each boat status to the status list.
        for (Boat boat : boats) {

            BoatStatus boatStatus = new BoatStatus(boat.getSourceID(), boat.getStatus(), boat.getCurrentLeg().getLegNumber());

            boatStatuses.add(boatStatus);
        }

        //TODO REFACTOR for consistency, could send parameters to mockOutput instead of the whole racestatus. This will also fix the sequence number issue.

        //Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class.
        int windDirectionInt = BoatLocation.convertHeadingDoubleToInt(this.windDirection.degrees());
        int windSpeedInt = (int) (windSpeed * Constants.KnotsToMMPerSecond);

        //Create race status object, and send it.
        RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), this.raceId, this.getRaceStatusEnum().getValue(), this.startTime, windDirectionInt, windSpeedInt, this.getRaceType().getValue(), boatStatuses);

        mockOutput.parseRaceStatus(raceStatus);


    }


    /**
     * Sets the status of all boats in the race to RACING.
     */
    private void setBoatsStatusToRacing() {

        for (Boat boat : this.boats) {
            boat.setStatus(BoatStatusEnum.RACING);
        }
    }


    /**
     * Countdown timer until race starts.
     */
    protected AnimationTimer countdownTimer = new AnimationTimer() {


        long currentTime = System.currentTimeMillis();

        @Override
        public void handle(long arg0) {

            //Update the race status based on the current time.
            updateRaceStatusEnum(this.currentTime);

            //Parse the boat locations.
            parseBoatLocations();

            //Parse the marks.
            parseMarks();

            // Change wind direction
            changeWindDir();

            //Parse the race status.
            parseRaceStatus();


            if (getRaceStatusEnum() == RaceStatusEnum.STARTED) {
                System.setProperty("javafx.animation.fullspeed", "true");
                setBoatsStatusToRacing();
                raceTimer.start();
                this.stop();
            }

            //Update the animations timer's time.
            currentTime = System.currentTimeMillis();
        }
    };


    /**
     * Timer that runs for the duration of the race, until all boats finish.
     */
    private AnimationTimer raceTimer = new AnimationTimer() {

        /**
         * Start time of loop, in milliseconds.
         */
        long timeRaceStarted = System.currentTimeMillis();

        /**
         * The time of the previous frame, in milliseconds.
         */
        long lastFrameTime = timeRaceStarted;

        @Override
        public void handle(long arg0) {

            //Get the current time.
            long currentTime = System.currentTimeMillis();

            //Update the total elapsed time.
            totalTimeElapsed = currentTime - this.timeRaceStarted;

            //As long as there is at least one boat racing, we still simulate the race.
            if (getNumberOfActiveBoats() != 0) {

                //Get the time period of this frame.
                long framePeriod = currentTime - lastFrameTime;
                //We actually simulate 20ms istead of the amount of time that has occurred, as that ensure that we don't end up with large frame periods on slow computers, causing position issues.
                framePeriod = 20;


                //For each boat, we update its position, and generate a BoatLocationMessage.
                for (Boat boat : boats) {

                    //If it is still racing, update its position.
                    if (boat.getStatus() == BoatStatusEnum.RACING) {

                        updatePosition(boat, framePeriod, totalTimeElapsed);

                    }

                }

            } else {
                //Otherwise, the race is over!

                setRaceStatusEnum(RaceStatusEnum.FINISHED);
                this.stop();
            }

            // Change wind direction
            changeWindDir();

            //Parse the boat locations.
            parseBoatLocations();

            //Parse the marks.
            parseMarks();

            //Parse the race status.
            parseRaceStatus();


            //Update the last frame time.
            this.lastFrameTime = currentTime;
        }
    };


    /**
     * Initialise the boats in the race.
     * This sets their starting positions and current legs.
     */
    public void initialiseBoats() {

        //Gets the starting positions of the boats.
        List<GPSCoordinate> startingPositions = getSpreadStartingPositions();

        //Get iterators for our boat and position lists.
        Iterator<Boat> boatIt = this.boats.iterator();
        Iterator<GPSCoordinate> startPositionIt = startingPositions.iterator();

        //Iterate over the pair of lists.
        while (boatIt.hasNext() && startPositionIt.hasNext()) {

            //Get the next boat and position.
            Boat boat = boatIt.next();
            GPSCoordinate startPosition = startPositionIt.next();


            //The boat starts on the first leg of the race.
            boat.setCurrentLeg(this.legs.get(0));

            //Boats start with 0 knots speed.
            boat.setCurrentSpeed(0d);

            //Place the boat at its starting position.
            boat.setCurrentPosition(startPosition);

            //Boats start facing their next marker.
            boat.setBearing(boat.calculateBearingToNextMarker());

            //Sets the boats status to prestart - it changes to racing when the race starts.
            boat.setStatus(BoatStatusEnum.PRESTART);

            //We set a large time since tack change so that it calculates a new VMG when the simulation starts.
            boat.setTimeSinceTackChange(999999);

        }

    }


    /**
     * Creates a list of starting positions for the different boats, so they do not appear cramped at the start line.
     *
     * @return A list of starting positions.
     */
    public List<GPSCoordinate> getSpreadStartingPositions() {

        //The first compound marker of the race - the starting gate.
        CompoundMark compoundMark = this.legs.get(0).getStartCompoundMark();

        //The position of the two markers from the compound marker.
        GPSCoordinate mark1Position = compoundMark.getMark1Position();
        GPSCoordinate mark2Position = compoundMark.getMark2Position();


        //Calculates the azimuth between the two points.
        Azimuth azimuth = GPSCoordinate.calculateAzimuth(mark1Position, mark2Position);

        //Calculates the distance between the two points.
        double distanceMeters = GPSCoordinate.calculateDistanceMeters(mark1Position, mark2Position);

        //The number of boats in the race.
        int numberOfBoats = this.boats.size();

        //Calculates the distance between each boat. We divide by numberOfBoats + 1 to ensure that no boat is placed on one of the starting gate's marks.
        double distanceBetweenBoatsMeters = distanceMeters / (numberOfBoats + 1);


        //List to store coordinates in.
        List<GPSCoordinate> positions = new ArrayList<>();

        //We start spacing boats out from mark 1.
        GPSCoordinate position = mark1Position;

        //For each boat, displace position, and store it.
        for (int i = 0; i < numberOfBoats; i++) {

            position = GPSCoordinate.calculateNewPosition(position, distanceBetweenBoatsMeters, azimuth);

            positions.add(position);

        }

        return positions;
    }


    /**
     * Calculates a boat's VMG.
     * @param boat The boat to calculate VMG for.
     * @param bearingBounds An array containing the lower and upper acceptable bearing bounds to keep the boat in the course.
     * @return VMG for the specified boat.
     */
    private VMG calculateVMG(Boat boat, Bearing[] bearingBounds) {

        //How fast a boat can turn, in degrees per millisecond.
        double turnRate = 0.03;

        //How much the boat is allowed to turn, considering how long since it last turned.
        double turnAngle = turnRate * boat.getTimeSinceTackChange();
        //This ensures that the boat bounds don't flip around.
        turnAngle = Math.min(turnAngle, 179.999);

        //Find the bounds on what angle the boat is allowed to travel at. The bounds cap out at [0, 360).
        double bound1Degrees = boat.getBearing().degrees() - turnAngle;
        double bound2Degrees = boat.getBearing().degrees() + turnAngle;

        Bearing bound1 = Bearing.fromDegrees(bound1Degrees);
        Bearing bound2 = Bearing.fromDegrees(bound2Degrees);


        //Get the lower and upper acceptable bounds.
        Bearing lowerAcceptableBound = bearingBounds[0];
        Bearing upperAcceptableBound = bearingBounds[1];


        //Find the bounds on what angle the boat can travel on, taking into account both turning rate and the acceptable bounds.
        bound1Degrees = Math.max(bound1.degrees(), lowerAcceptableBound.degrees());
        bound2Degrees = Math.min(bound2.degrees(), upperAcceptableBound.degrees());

        bound1 = Bearing.fromDegrees(bound1Degrees);
        bound2 = Bearing.fromDegrees(bound2Degrees);


        //Calculate VMG using these bounds.
        return boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), bound1, bound2);

    }


    /**
     * Determines whether or not a given VMG improves the velocity of a boat.
     * @param boat The boat to test.
     * @param vmg The new VMG to test.
     * @return True if the new VMG is improves velocity, false otherwise.
     */
    private boolean improvesVelocity(Boat boat, VMG vmg) {

        //Calculates the angle between the boat and its destination.
        Angle angleBetweenDestAndHeading = Angle.fromDegrees(boat.getBearing().degrees() - boat.calculateBearingToNextMarker().degrees());

        //Calculates the angle between the new VMG and the boat's destination.
        Angle angleBetweenDestAndNewVMG = Angle.fromDegrees(vmg.getBearing().degrees() - boat.calculateBearingToNextMarker().degrees());


        //Calculate the boat's current velocity.
        double currentVelocity = Math.cos(angleBetweenDestAndHeading.radians()) * boat.getCurrentSpeed();

        //Calculate the potential velocity with the new VMG.
        double vmgVelocity =  Math.cos(angleBetweenDestAndNewVMG.radians()) * vmg.getSpeed();

        //Return whether or not the new VMG gives better velocity.
        return vmgVelocity > currentVelocity;

    }


    /**
     * Calculates the distance a boat has travelled and updates its current position according to this value.
     *
     * @param boat The boat to be updated.
     * @param updatePeriodMilliseconds The time, in milliseconds, since the last update.
     * @param totalElapsedMilliseconds The total number of milliseconds that have elapsed since the start of the race.
     */
    protected void updatePosition(Boat boat, long updatePeriodMilliseconds, long totalElapsedMilliseconds) {

        //Checks if the current boat has finished the race or not.
        boolean finish = this.isLastLeg(boat.getCurrentLeg());

        if (!finish) {


            //Calculates the distance travelled, in meters, in the current timeslice.
            double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds);

            //Scale it.
            distanceTravelledMeters = distanceTravelledMeters * this.scaleFactor;


            //Move the boat forwards that many meters, and advances its time counters by enough milliseconds.
            boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor);


            //Calculate the boat's bearing bounds, to ensure that it doesn't go out of the course.
            Bearing[] bearingBounds = this.calculateBearingBounds(boat);

            //Calculate the new VMG.
            VMG newVMG = this.calculateVMG(boat, bearingBounds);





            //If the new vmg improves velocity, use it.
            if (improvesVelocity(boat, newVMG)) {
                boat.setVMG(newVMG);
            }else if (!GPSCoordinate.isInsideBoundary(boat.getCurrentPosition(), boundary)){
                //checks to see if the new vmg sends the boat out of bounds and if so mirrors its direction in the wind
                Azimuth azimuth = Azimuth.fromBearing(boat.getBearing());
                GPSCoordinate test = GPSCoordinate.calculateNewPosition(boat.getCurrentPosition(),
                        (100.0 / Constants.NMToMetersConversion), azimuth);
                double currDegrees = newVMG.getBearing().degrees();
                double windDirectionDegrees = this.windDirection.degrees();
                double tempHeading = (currDegrees - windDirectionDegrees +90)%360;
                newVMG.getBearing().setDegrees(tempHeading);
                boat.setVMG(newVMG);
            }


            //Check the boats position (update leg and stuff).
            this.checkPosition(boat, totalTimeElapsed);

        }

    }


    /**
     * Calculates the upper and lower bounds that the boat may have in order to not go outside of the course.
     * @param boat The boat to check.
     * @return An array of bearings. The first is the lower bound, the second is the upper bound.
     */
    private Bearing[] calculateBearingBounds(Boat boat) {

        Bearing[] bearings = new Bearing[2];

        Bearing lowerBearing = Bearing.fromDegrees(0);
        Bearing upperBearing = Bearing.fromDegrees(359.999);


        boolean foundAnyBadBearing = false;
        boolean foundLowerBearing = false;

        //Check all bearings between [0, 360).
        for (double angle = 0; angle < 360; angle += 1) {

            //Create bearing from angle.
            Bearing bearing = Bearing.fromDegrees(angle);

            //Check that if it is acceptable.
            boolean bearingIsGood = this.checkBearingInsideCourse(bearing, boat.getCurrentPosition());

            if (bearingIsGood) {

                //The lower bearing will be the first acceptable bearing after finding any unacceptable bearing.
                if (foundAnyBadBearing && !foundLowerBearing) {
                    lowerBearing = bearing;
                    foundLowerBearing = true;
                }

                //The upper bearing will be the last acceptable bearing before finding any unacceptable bearing.
                if (!foundAnyBadBearing) {
                    upperBearing = bearing;
                }


            } else {

                foundAnyBadBearing = true;

            }

        }


        bearings[0] = lowerBearing;
        bearings[1] = upperBearing;

        return bearings;
    }



    /**
     * Checks if a given bearing, starting at a given position, would put a boat out of the course boundaries.
     * @param bearing The bearing to check.
     * @param position The position to start from.
     */
    private boolean checkBearingInsideCourse(Bearing bearing, GPSCoordinate position) {

        //Get azimuth from bearing.
        Azimuth azimuth = Azimuth.fromBearing(bearing);


        //Tests to see if a point in front of the boat is out of bounds.
        double epsilonMeters = 100d;
        GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(position, epsilonMeters, azimuth);

        //If it isn't inside the boundary, calculate new bearing.
        if (GPSCoordinate.isInsideBoundary(testCoord, this.boundary)) {
            return true;
        } else {
            return false;
        }

    }


    /**
     * Checks if a boat has finished any legs, or has pulled out of race (DNF).
     * @param boat The boat to check.
     * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started.
     */
    protected void checkPosition(Boat boat, long timeElapsed) {

        //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker.
        double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10.

        if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) {
            //Boat has reached its target marker, and has moved on to a new leg.



            //Calculate how much the boat overshot the marker by.
            double overshootMeters = boat.calculateDistanceToNextMarker();


            //Move boat on to next leg.
            Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1);
            boat.setCurrentLeg(nextLeg);

            //Add overshoot distance into the distance travelled for the next leg.
            boat.setDistanceTravelledInLeg(overshootMeters);

            //Setting a high value for this allows the boat to immediately do a large turn, as it needs to in order to get to the next mark.
            boat.setTimeSinceTackChange(999999);


            //Check if the boat has finished or stopped racing.

            if (this.isLastLeg(boat.getCurrentLeg())) {
                //Boat has finished.
                boat.setTimeFinished(timeElapsed);
                boat.setCurrentSpeed(0);
                boat.setStatus(BoatStatusEnum.FINISHED);

            } else if (doNotFinish()) {
                //Boat has pulled out of race.
                boat.setTimeFinished(timeElapsed);
                boat.setCurrentLeg(new Leg("DNF", -1));
                boat.setCurrentSpeed(0);
                boat.setStatus(BoatStatusEnum.DNF);

            }

        }

    }


    /**
     * Determines whether or not a specific leg is the last leg in the race.
     * @param leg The leg to check.
     * @return Returns true if it is the last, false otherwse.
     */
    private boolean isLastLeg(Leg leg) {

        //Get the last leg.
        Leg lastLeg = this.legs.get(this.legs.size() - 1);

        //Check its ID.
        int lastLegID = lastLeg.getLegNumber();

        //Get the specified leg's ID.
        int legID = leg.getLegNumber();


        //Check if they are the same.
        return legID == lastLegID;
    }


    /**
     * Sets the chance each boat has of failing at a gate or marker
     *
     * @param chance percentage chance a boat has of failing per checkpoint.
     */
    protected void setDnfChance(int chance) {
        if (chance >= 0 && chance <= 100) {
            dnfChance = chance;
        }
    }

    /**
     * Decides if a boat should received a DNF status.
     * @return True means it should DNF, false means it shouldn't.
     */
    protected boolean doNotFinish() {
        Random rand = new Random();
        return rand.nextInt(100) < dnfChance;
    }


    /**
     * Returns the current race status.
     * @return The current race status.
     */
    public RaceStatusEnum getRaceStatusEnum() {
        return raceStatusEnum;
    }

    /**
     * Sets the current race status.
     * @param raceStatusEnum The new status of the race.
     */
    private void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) {
        this.raceStatusEnum = raceStatusEnum;
    }


    /**
     * Returns the type of race this is.
     * @return The type of race this is.
     */
    public RaceTypeEnum getRaceType() {
        return raceType;
    }


    /**
     * Returns the number of boats that are still active in the race.
     * They become inactive by either finishing or withdrawing.
     * @return The number of boats still active in the race.
     */
    protected int getNumberOfActiveBoats() {

        int numberofActiveBoats = 0;

        for (Boat boat : this.boats) {

            //If the boat is currently racing, count it.
            if (boat.getStatus() == BoatStatusEnum.RACING) {
                numberofActiveBoats++;
            }

        }

        return numberofActiveBoats;
    }


    /**
     * Returns an observable list of boats in the race.
     * @return List of boats in the race.
     */
    public ObservableList<Boat> getBoats() {
        return boats;
    }

    protected void initialiseWindDir(){
        windDir = new Random().nextInt(65535+1);
        this.windDirection = new Bearing(BoatLocation.convertHeadingIntToDouble(windDir));
    }

    protected void changeWindDir(){
        int r = new Random().nextInt(changeWind)+1;
        if(r==1){
            windDir+=100;
        } else if (r==2){
            windDir-=100;
        }
        if (windDir > 65535){
            windDir -= 65535;
        }
        if (windDir < 0){
            windDir += 65535;
        }
        this.windDirection = new Bearing(BoatLocation.convertHeadingIntToDouble(windDir));
    }

    protected void setWindDir(int wind){
        if (wind>=0 && wind<=65535){
            windDir = wind;
            this.windDirection = new Bearing(BoatLocation.convertHeadingIntToDouble(windDir));
        }
    }

    protected void setChangeWind(int changeVal){
        if (changeVal>=0){
            changeWind = changeVal;
        }
    }

    protected int getWind(){
        return windDir;
    }
}