package seng302.Model;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.paint.Color;
import org.geotools.referencing.GeodeticCalculator;
import seng302.GPSCoordinate;

import java.awt.geom.Point2D;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Boat in the Race extends Boat.
 * Created by esa46 on 15/03/17.
 */
public class BoatInRace extends Boat {

    private Leg currentLeg;
    private double scaledVelocity;
    private double distanceTravelledInLeg;
    private GPSCoordinate currentPosition;
    private long timeFinished;
    private Color colour;
    private boolean finished = false;
    private final StringProperty currentLegName;
    private boolean started = false;
    private final StringProperty position;
    private double heading;
    private static final double WAKE_SCALE = 10;

    private final Queue<TrackPoint> track = new ConcurrentLinkedQueue<>();
    private long nextValidTime = 0;

    private static final float BASE_TRACK_POINT_TIME_INTERVAL = 5000;
    private static float trackPointTimeInterval = 5000; // every 1 seconds

    /**
     * Constructor method.
     *
     * @param name     Name of the boat.
     * @param velocity Speed that the boat travels.
     * @param colour   Colour the boat will be displayed as on the map
     * @param abbrev of boat
     */
    public BoatInRace(String name, double velocity, Color colour, String abbrev) {
        super(name, velocity, abbrev);
        setColour(colour);
        currentLegName = new SimpleStringProperty("");
        position = new SimpleStringProperty("-");
    }

    /**
     * Calculates the azimuth of the travel via map coordinates of the raceMarkers
     *
     * @return the direction that the boat is heading towards in degrees (-180 to 180).
     */
    public double calculateAzimuth() {

        GeodeticCalculator calc = new GeodeticCalculator();
        GPSCoordinate start = currentLeg.getStartMarker().getAverageGPSCoordinate();
        GPSCoordinate end = currentLeg.getEndMarker().getAverageGPSCoordinate();

        calc.setStartingGeographicPoint(start.getLongitude(), start.getLatitude());
        calc.setDestinationGeographicPoint(end.getLongitude(), end.getLatitude());

        return calc.getAzimuth();
    }

    /**
     * Converts an azimuth to a bearing
     *
     * @param azimuth azimuth value to be converted
     * @return the bearings in degrees (0 to 360).
     */
    private static double calculateHeading(double azimuth) {
        if (azimuth >= 0) {
            return azimuth;
        } else {
            return azimuth + 360;
        }
    }

    public double getHeading() {
        return heading;
    }

    public void setHeading(double heading) {
        this.heading = heading;
    }

    /**
     * Calculates the bearing of the travel via map coordinates of the raceMarkers
     *
     * @return the direction that the boat is heading towards in degrees (0 to 360).
     */
    public double calculateHeading() {
        double azimuth = calculateAzimuth();
        return calculateHeading(azimuth);
    }

    /**
     * Returns the position of the end of the boat's wake, which is 180 degrees
     * from the boat's heading, and whose length is proportional to the boat's
     * speed.
     *
     * @return GPSCoordinate of wake endpoint.
     */
    public GPSCoordinate getWake() {
        double reverseHeading = getHeading() - 180;
        double distance = WAKE_SCALE * getVelocity();

        GeodeticCalculator calc = new GeodeticCalculator();
        calc.setStartingGeographicPoint(
                new Point2D.Double(getCurrentPosition().getLongitude(), getCurrentPosition().getLatitude())
        );
        calc.setDirection(reverseHeading, distance);
        Point2D endpoint = calc.getDestinationGeographicPoint();
        return new GPSCoordinate(endpoint.getY(), endpoint.getX());
    }

    /**
     * @return Scaled velocity of the boat
     */
    public double getScaledVelocity() {
        return scaledVelocity;
    }

    /**
     * Sets the boat's scaled velocity
     *
     * @param velocity of boat
     */
    public void setScaledVelocity(double velocity) {
        this.scaledVelocity = velocity;
    }

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

    /**
     * Sets the current position on the GPS that the boat.
     *
     * @param position GPSCoordinate of the position that the boat is currently on.
     * @see GPSCoordinate
     */
    public void setCurrentPosition(GPSCoordinate position) {
        this.currentPosition = position;
    }

    /**
     * @return Returns the time that the boat finished the race.
     */
    public long getTimeFinished() {
        return timeFinished;
    }

    /**
     * Sets the time that the boat finished the race.
     *
     * @param timeFinished Time the boat finished the race.
     */
    public void setTimeFinished(long timeFinished) {
        this.timeFinished = timeFinished;
    }

    /**
     * @return Returns the colour of the boat.
     */
    public Color getColour() {
        return colour;
    }

    /**
     * Sets the colour that boat will be shown as when drawn on the ResizableRaceCanvas.
     *
     * @param colour Colour that the boat is to be set to.
     * @see ResizableRaceCanvas
     */
    private void setColour(Color colour) {
        this.colour = colour;
    }

    /**
     * Gets the current leg that the boat is on.
     *
     * @return returns the leg the boat is on in a Leg class
     * @see Leg
     */
    public Leg getCurrentLeg() {
        return currentLeg;
    }

    /**
     * Sets the boat's current leg.
     *
     * @param currentLeg Leg class that the boat is currently on.
     * @see Leg
     */
    public void setCurrentLeg(Leg currentLeg) {
        this.currentLeg = currentLeg;
        this.currentLegName.setValue(currentLeg.getName());
    }

    /**
     * @return Name of boat's current leg
     */
    public StringProperty getCurrentLegName() {
        return currentLegName;
    }

    /**
     * Gets the distance travelled by the boat in the leg.
     *
     * @return Returns the value in nautical miles (1.852km) that the boat has traversed.
     */
    public double getDistanceTravelledInLeg() {
        return distanceTravelledInLeg;
    }

    /**
     * Sets the distance travelled by the boat in the leg in nautical miles (1.852km)
     *
     * @param distanceTravelledInLeg Distance travelled by the boat in nautical miles.
     */
    public void setDistanceTravelledInLeg(double distanceTravelledInLeg) {
        this.distanceTravelledInLeg = distanceTravelledInLeg;
    }

    /**
     * @return true if boat has finished, false if not
     */
    public boolean isFinished() {
        return this.finished;
    }

    /**
     * Sets whether boat is finished or not
     *
     * @param bool is finished value
     */
    public void setFinished(boolean bool) {
        this.finished = bool;
    }

    public boolean isStarted() {
        return started;
    }

    public void setStarted(boolean started) {
        this.started = started;
    }

    public String getPosition() {
        return position.get();
    }

    public StringProperty positionProperty() {
        return position;
    }

    public void setPosition(String position) {
        this.position.set(position);
    }

    /**
     * Adds a new point to boat's track.
     * @param coordinate of point on track
     * @see seng302.Model.TrackPoint
     */
    public void addTrackPoint(GPSCoordinate coordinate) {
        Boolean added = System.currentTimeMillis() >= nextValidTime;
        long currentTime = System.currentTimeMillis();
        if (added && this.started) {
            nextValidTime = currentTime + (long) trackPointTimeInterval;
            int TRACK_POINT_LIMIT = 10;
            track.add(new TrackPoint(coordinate, currentTime, TRACK_POINT_LIMIT * (long) trackPointTimeInterval));
        }
    }

    /**
     * Returns the boat's sampled track between start of race and current time.
     * @return queue of track points
     * @see seng302.Model.TrackPoint
     */
    public Queue<TrackPoint> getTrack() {
        return track;
    }

    /**
     * Get base track point time interval
     * @return base track point time interval
     */
    public static float getBaseTrackPointTimeInterval() {
        return BASE_TRACK_POINT_TIME_INTERVAL;
    }

    /**
     * Set track point time interval
     * @param value track point time interval value
     */
    public static void setTrackPointTimeInterval(float value) {
        trackPointTimeInterval = value;
    }
}
