package seng302.Networking.Messages;

/**
 * Created by f123 on 21-Apr-17.
 */

import SharedModel.Constants;
import seng302.Networking.Utils.AC35UnitConverter;
import seng302.Networking.Messages.Enums.MessageType;

import static seng302.Networking.Utils.AC35UnitConverter.convertGPS;
import static seng302.Networking.Utils.AC35UnitConverter.convertGPSToInt;

/**
 * Represents the information in a boat location message (AC streaming spec: 4.9).
 */
public class BoatLocation extends AC35Data {

    public static final byte Unknown = 0;
    public static final byte RacingYacht = 1;
    public static final byte CommitteeBoat = 2;
    public static final byte Mark = 3;
    public static final byte Pin = 4;
    public static final byte ChaseBoat = 5;
    public static final byte MedicalBoat = 6;
    public static final byte MarshallBoat = 7;
    public static final byte UmpireBoat = 8;
    public static final byte UmpireSoftwareApplication = 9;
    public static final byte PrincipalRaceOfficerApplication = 10;
    public static final byte WeatherStation = 11;
    public static final byte Helicopter = 12;
    public static final byte DataProcessingApplication = 13;
    ///Version number of the message - is always 1.
    private byte messageVersionNumber = 1;
    ///Time of the event - milliseconds since jan 1 1970. Proper type is 6 byte int.
    private long time;
    ///Source ID of the boat.
    private int sourceID;
    ///Sequence number of the message.
    private long sequenceNumber;
    ///Device type of the message (physical source of the message).
    private byte deviceType;
    ///Latitude of the boat.
    private int latitude;

    ///Longitude of the boat.
    private int longitude;

    ///Altitude of the boat.
    private int altitude;

    ///Heading of the boat. Clockwise, 0 = north. Proper type is unsigned 2 byte int.
    private int heading;

    ///Pitch of the boat.
    private short pitch;

    ///Roll of the boat.
    private short roll;

    ///Speed of the boat. Proper type is unsigned 2 byte int. millimeters per second.
    private int boatSpeed;

    ///Course over ground (COG) of the boat. Proper type is unsigned 2 byte int.
    private int boatCOG;

    ///Speed over ground (SOG) of the boat. Proper type is unsigned 2 byte int. millimeters per second.
    private int boatSOG;

    ///Apparent wind speed at time of event. Proper type is unsigned 2 byte int. millimeters per second.
    private int apparentWindSpeed;

    ///Apparent wind angle at time of the event. Wind over starboard = positive.
    private short apparentWindAngle;

    ///True wind speed. Proper type is unsigned 2 byte int. millimeters per second.
    private int trueWindSpeed;

    ///True wind direction. Proper type is unsigned 2 byte int. 0x0000 = North, etc..
    private int trueWindDirection;

    ///True wind angle. Clockwise compass direction, 0 = north.
    private short trueWindAngle;

    ///Current drift. Proper type is unsigned 2 byte int. millimeters per second.
    private int currentDrift;

    ///Current set. Proper type is unsigned 2 byte int. Clockwise compass direction, 0 = north.
    private int currentSet;

    ///Rudder angle. Positive is rudder set to turn yacht to port.
    private short rudderAngle;


    /**
     * Ctor. Default.
     */
    public BoatLocation() {
        super(MessageType.BOATLOCATION);
    }

    /**
     * Ctor, with all parameters.
     *
     * @param messageVersionNumber message number
     * @param time time of message
     * @param sourceID id of boat
     * @param sequenceNumber number of boat message
     * @param deviceType type of boat
     * @param latitude lat of boat
     * @param longitude lon of boat
     * @param altitude altitude of boat
     * @param heading heading of boat
     * @param pitch pitch of boat
     * @param roll roll of boat
     * @param boatSpeed boats speed
     * @param boatCOG boat cog
     * @param boatSOG boat sog
     * @param apparentWindSpeed wind speed
     * @param apparentWindAngle wind angle
     * @param trueWindSpeed true wind speed
     * @param trueWindDirection true wind direction
     * @param trueWindAngle true wind angle
     * @param currentDrift current drift
     * @param currentSet current set
     * @param rudderAngle rudder angle
     */
    public BoatLocation(byte messageVersionNumber, long time, int sourceID, long sequenceNumber, byte deviceType, int latitude, int longitude, int altitude, int heading, short pitch, short roll, int boatSpeed, int boatCOG, int boatSOG, int apparentWindSpeed, short apparentWindAngle, int trueWindSpeed, int trueWindDirection, short trueWindAngle, int currentDrift, int currentSet, short rudderAngle) {
        super(MessageType.BOATLOCATION);

        this.messageVersionNumber = messageVersionNumber;
        this.time = time;
        this.sourceID = sourceID;
        this.sequenceNumber = sequenceNumber;
        this.deviceType = deviceType;
        this.latitude = latitude;
        this.longitude = longitude;
        this.altitude = altitude;
        this.heading = heading;
        this.pitch = pitch;
        this.roll = roll;
        this.boatSpeed = boatSpeed;
        this.boatCOG = boatCOG;
        this.boatSOG = boatSOG;
        this.apparentWindSpeed = apparentWindSpeed;
        this.apparentWindAngle = apparentWindAngle;
        this.trueWindSpeed = trueWindSpeed;
        this.trueWindDirection = trueWindDirection;
        this.trueWindAngle = trueWindAngle;
        this.currentDrift = currentDrift;
        this.currentSet = currentSet;
        this.rudderAngle = rudderAngle;
    }

    public BoatLocation(int sourceID, double lat, double lon, long sequenceNumber, double heading, double boatSpeed) {
        super(MessageType.BOATLOCATION);

        this.messageVersionNumber = (byte) 1;
        this.time = System.currentTimeMillis();
        this.sourceID = sourceID;
        this.sequenceNumber = sequenceNumber;
        this.deviceType = 1;
        this.latitude = convertGPSToInt(lat);
        this.longitude = convertGPSToInt(lon);
        this.altitude = 0;
        this.heading = convertHeadingDoubleToInt(heading);
        this.pitch = 0;
        this.roll = 0;
        this.boatSpeed = convertBoatSpeedDoubleToInt(boatSpeed);
        this.boatCOG = 0;
        this.boatSOG = convertBoatSpeedDoubleToInt(boatSpeed);
        this.apparentWindSpeed = 0;
        this.apparentWindAngle = 0;
        this.trueWindSpeed = 0;
        this.trueWindDirection = 0;
        this.trueWindAngle = 0;
        this.currentDrift = 0;
        this.currentSet = 0;
        this.rudderAngle = 0;
    }


    //Getters and setters for message properties.

    /**
     * Converts a double representing a latitude or longitude coordinate to an int, as required by the streaming spec format.
     *
     * @param coordinate Latitude or longitude to convert. Double.
     * @return int representation of coordinate.
     */
    public static int convertCoordinateDoubleToInt(double coordinate) {
        int coordinateInt = (int) ((coordinate / 180.0) * 2147483648.0);

        return coordinateInt;
    }

    /**
     * Converts an int representing a latitude or longitude coordinate to a double, as required by the streaming spec format.
     *
     * @param coordinate Latitude or longitude to convert. int.
     * @return double representation of coordinate.
     */
    public static double convertCoordinateIntToDouble(int coordinate) {
        double coordinateDouble = (double) ((coordinate * 180.0) / 2147483648.0);

        return coordinateDouble;
    }

    /**
     * Converts an int representing a heading to a double, as required by the streaming spec format.
     *
     * @param heading Heading to convert. int.
     * @return double representation of heading.
     */
    public static double convertHeadingIntToDouble(int heading) {

        double headingDouble = (double) ((heading * 360.0) / 65536.0);

        return headingDouble;
    }

    /**
     * Converts a double representing a heading to an int, as required by the streaming spec format.
     *
     * @param heading Heading to convert. double.
     * @return int representation of heading.
     */
    public static int convertHeadingDoubleToInt(double heading) {

        int headingInt = (int) ((heading / 360.0) * 65536.0);

        return headingInt;
    }

    /**
     * Converts a short representing the wind's true angle to a double, as required by the streaming spec format.
     *
     * @param angle Angle to convert. short.
     * @return double representation of heading.
     */
    public static double convertTrueWindAngleShortToDouble(short angle) {

        double angleDouble = (double) ((angle * 180.0) / 32768.0);

        return angleDouble;
    }

    /**
     * Converts a double representing the wind's true angle to a short, as required by the streaming spec format.
     *
     * @param angle Angle to convert. double.
     * @return short representation of heading.
     */
    public static short convertTrueWindAngleDoubleToShort(double angle) {

        short angleShort = (short) ((angle / 180.0) * 32768.0);

        return angleShort;
    }

    /**
     * Converts a double representing the speed of a boat in knots to an int in millimeters per second, as required by the streaming spec format.
     *
     * @param speed Speed in knots, stored as a double.
     * @return Speed in millimeters per second, stored as an int (using only the two least significant bytes).
     */
    public static int convertBoatSpeedDoubleToInt(double speed) {
        //Calculate meters per second.
        double metersPerSecond = speed * Constants.KnotsToMetersPerSecondConversionFactor;

        //Calculate millimeters per second.
        double millimetersPerSecond = metersPerSecond * 1000.0;

        //Convert to an int.
        int millimetersPerSecondInt = (int) Math.round(millimetersPerSecond);

        return millimetersPerSecondInt;
    }

    /**
     * Converts an int representing the speed of a boat in millimeters per second to a double in knots, as required by the streaming spec format.
     *
     * @param speed Speed in millimeters per second, stored as an int.
     * @return Speed in knots, stored as a double.
     */
    public static double convertBoatSpeedIntToDouble(int speed) {
        //Calculate meters per second.
        double metersPerSecond = speed / 1000.0;

        //Calculate knots.
        double knots = metersPerSecond / Constants.KnotsToMetersPerSecondConversionFactor;

        return knots;
    }

    public byte getMessageVersionNumber() {
        return messageVersionNumber;
    }

    public void setMessageVersionNumber(byte messageVersionNumber) {
        this.messageVersionNumber = messageVersionNumber;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public int getSourceID() {
        return sourceID;
    }

    public void setSourceID(int sourceID) {
        this.sourceID = sourceID;
    }

    public long getSequenceNumber() {
        return sequenceNumber;
    }

    public void setSequenceNumber(long sequenceNumber) {
        this.sequenceNumber = sequenceNumber;
    }

    public byte getDeviceType() {
        return deviceType;
    }

    public void setDeviceType(byte deviceType) {
        this.deviceType = deviceType;
    }

    public int getLatitude() {
        return latitude;
    }

    public void setLatitude(int latitude) {
        this.latitude = latitude;
    }

    public int getLongitude() {
        return longitude;
    }

    public double getLatitudeDouble(){
        return convertGPS(this.latitude);
    }

    public double getLongitudeDouble(){
        return convertGPS(this.longitude);
    }

    public void setLongitude(int longitude) {
        this.longitude = longitude;
    }

    public int getAltitude() {
        return altitude;
    }

    public void setAltitude(int altitude) {
        this.altitude = altitude;
    }

    public int getHeading() {
        return heading;
    }

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

    public short getPitch() {
        return pitch;
    }

    public void setPitch(short pitch) {
        this.pitch = pitch;
    }

    public short getRoll() {
        return roll;
    }

    public void setRoll(short roll) {
        this.roll = roll;
    }

    public int getBoatSpeed() {
        return boatSpeed;
    }

    public void setBoatSpeed(int boatSpeed) {
        this.boatSpeed = boatSpeed;
    }

    public int getBoatCOG() {
        return boatCOG;
    }

    public void setBoatCOG(int boatCOG) {
        this.boatCOG = boatCOG;
    }

    public int getBoatSOG() {
        return boatSOG;
    }

    public void setBoatSOG(int boatSOG) {
        this.boatSOG = boatSOG;
    }

    public int getApparentWindSpeed() {
        return apparentWindSpeed;
    }

    public void setApparentWindSpeed(int apparentWindSpeed) {
        this.apparentWindSpeed = apparentWindSpeed;
    }

    public short getApparentWindAngle() {
        return apparentWindAngle;
    }

    public void setApparentWindAngle(short apparentWindAngle) {
        this.apparentWindAngle = apparentWindAngle;
    }

    public int getTrueWindSpeed() {
        return trueWindSpeed;
    }

    public void setTrueWindSpeed(int trueWindSpeed) {
        this.trueWindSpeed = trueWindSpeed;
    }

    public int getTrueWindDirection()
    {
        return trueWindDirection;
    }

    public void setTrueWindDirection(int trueWindDirection)
    {
        this.trueWindDirection = trueWindDirection;
    }

    public short getTrueWindAngle() {
        return trueWindAngle;
    }

    public void setTrueWindAngle(short trueWindAngle) {
        this.trueWindAngle = trueWindAngle;
    }

    public int getCurrentDrift() {
        return currentDrift;
    }

    public void setCurrentDrift(int currentDrift) {
        this.currentDrift = currentDrift;
    }

    public int getCurrentSet() {
        return currentSet;
    }

    public void setCurrentSet(int currentSet) {
        this.currentSet = currentSet;
    }

    public short getRudderAngle() {
        return rudderAngle;
    }

    public void setRudderAngle(short rudderAngle) {
        this.rudderAngle = rudderAngle;
    }

    public double getHeadingDegrees(){
        return AC35UnitConverter.convertHeading(getHeading());
    }

    public double getTrueWindAngleDegrees(){
        return AC35UnitConverter.convertTrueWindAngle(getTrueWindAngle());
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();

        builder.append("Message version number: ");
        builder.append(this.getMessageVersionNumber());

        builder.append("\nTime: ");
        builder.append(this.getTime());

        builder.append("\nSource ID: ");
        builder.append(this.getSourceID());

        builder.append("\nSequence number: ");
        builder.append(this.getSequenceNumber());

        builder.append("\nDevice type: ");
        builder.append(this.getDeviceType());

        builder.append("\nLatitude: ");
        builder.append(this.getLatitude());

        builder.append("\nLongitude: ");
        builder.append(this.getLongitude());

        builder.append("\nAltitude: ");
        builder.append(this.getAltitude());

        builder.append("\nHeading: ");
        builder.append(this.getHeading());

        builder.append("\nPitch: ");
        builder.append(this.getPitch());

        builder.append("\nRoll: ");
        builder.append(this.getRoll());

        builder.append("\nBoat speed (mm/sec): ");
        builder.append(this.getBoatSpeed());

        builder.append("\nBoat COG: ");
        builder.append(this.getBoatCOG());

        builder.append("\nBoat SOG: ");
        builder.append(this.getBoatSOG());

        builder.append("\nApparent wind speed: ");
        builder.append(this.getApparentWindSpeed());

        builder.append("\nApparent wind angle: ");
        builder.append(this.getApparentWindAngle());

        builder.append("\nTrue wind speed: ");
        builder.append(this.getTrueWindSpeed());

        builder.append("\nTrue wind angle: ");
        builder.append(this.getTrueWindAngle());

        builder.append("\nCurrent drift: ");
        builder.append(this.getCurrentDrift());

        builder.append("\nCurrent set: ");
        builder.append(this.getCurrentSet());

        builder.append("\nRudder angle: ");
        builder.append(this.getRudderAngle());

        return builder.toString();
    }
}
