package seng302.Networking.MessageEncoders;


import seng302.Networking.Messages.*;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import static seng302.Networking.Utils.ByteConverter.*;

/**
 * Created by fwy13 on 19/04/17.
 */
public class RaceVisionByteEncoder {

    /**
     * Serializes a heartbeat message.
     * @param seq Heartbeat value.
     * @return Serialized message.
     */
    public static byte[] heartBeat(long seq){
        ByteBuffer heartBeat = ByteBuffer.allocate(4);
        heartBeat.put(longToBytes(seq, 4));
        byte [] result = heartBeat.array();
        return result;
    }

    /**
     * Serializes a RaceStatus message.
     * @param raceStatus Message to serialize.
     * @return Serialized (byte array) message, ready to be written to a socket.
     */
    public static byte[] raceStatus(RaceStatus raceStatus){

        List<BoatStatus> boatStatuses = raceStatus.getBoatStatuses();

        ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20* boatStatuses.size());
        //Version Number 1 bytes
        byte versionNum = 0b10; //this changes with the pdf. (2)
        byte[] timeBytes = longToBytes(raceStatus.getCurrentTime(), 6);//time (6 bytes)
        byte[] raceID = ByteBuffer.allocate(4).put(intToBytes(raceStatus.getRaceID())).array();//race identifier incase multiple races are going at once.
        byte[] raceStatusByte = intToBytes(raceStatus.getRaceStatus(), 1);//race status 0 - 10
        byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6);//number of milliseconds from Jan 1, 1970 for when the data is valid
        byte[] raceWind = ByteBuffer.allocate(2).put(intToBytes(raceStatus.getWindDirection(), 2)).array();//North = 0x0000 East = 0x4000 South = 0x8000.
        byte[] windSpeed = ByteBuffer.allocate(2).put(intToBytes(raceStatus.getWindSpeed(), 2)).array();//mm/sec
        byte[] numBoats = intToBytes(boatStatuses.size(), 1);
        byte[] bytesRaceType = intToBytes(raceStatus.getRaceType(), 1);//1 match race, 2 fleet race

        raceStatusMessage.put(versionNum);
        raceStatusMessage.put(timeBytes);
        raceStatusMessage.put(raceID);
        raceStatusMessage.put(raceStatusByte);
        raceStatusMessage.put(expectedStart);
        raceStatusMessage.put(raceWind);
        raceStatusMessage.put(windSpeed);
        raceStatusMessage.put(numBoats);
        raceStatusMessage.put(bytesRaceType);

        for (int i = 0; i < boatStatuses.size(); i++){
            byte[] sourceID = intToBytes(boatStatuses.get(i).getSourceID());
            byte[] boatStatus = intToBytes(boatStatuses.get(i).getBoatStatus(), 1);
            byte[] legNum = intToBytes(boatStatuses.get(i).getLegNumber(), 1);
            byte[] numPenalties = intToBytes(boatStatuses.get(i).getNumPenaltiesAwarded(), 1);
            byte[] numPenaltiesServed = intToBytes(boatStatuses.get(i).getNumPenaltiesServed(), 1);
            byte[] estNextMarkTime = longToBytes(boatStatuses.get(i).getEstTimeAtNextMark(), 6);
            byte[] estFinishTime = longToBytes( boatStatuses.get(i).getEstTimeAtFinish(), 6);

            raceStatusMessage.put(sourceID);
            raceStatusMessage.put(boatStatus);
            raceStatusMessage.put(legNum);
            raceStatusMessage.put(numPenalties);
            raceStatusMessage.put(numPenaltiesServed);
            raceStatusMessage.put(estNextMarkTime);
            raceStatusMessage.put(estFinishTime);
        }

        return raceStatusMessage.array();
    }

    public byte[] displayTextMessage(RaceMessage[] message){
        //ByteBuffer result = ByteBuffer.allocate(4 + numLines * 32);
        int messageVersionNumber = 0b1;//version number
        short ackNum = 0;//no clue what this does just a placeholder for 2 bytes.
        byte[] messLines = intToBytes(message.length, 1);

//        result.putInt(messageVersionNumber);
//        result.putShort(ackNum);
//        result.put(messLines);

        ArrayList<byte[]> messages = new ArrayList<byte[]>();
        int size = 4;

        for (int i = 0; i < message.length; i ++){
            int messageLen = message[i].getMessageText().getBytes().length;
            byte[] messageAsBytes = message[i].getMessageText().getBytes();
            if (messageLen < 30){
                messageLen = 30;
            }
            ByteBuffer mess = ByteBuffer.allocate(2 + messageLen);
            mess.put(intToBytes(message[i].getLineNumber(), 1));
            mess.put(intToBytes(messageLen, 1));
            for (int j = 0; j < messageLen; j ++){
                mess.put(messageAsBytes[j]);
            }
            messages.add(mess.array());
            size += 2 + messageLen;
        }

        ByteBuffer result = ByteBuffer.allocate(size);
        result.put(intToBytes(messageVersionNumber, 1));
        result.putShort(ackNum);
        result.put(messLines);

        for(byte[] mess: messages){
            result.put(mess);
        }

        return result.array();
    }

    public byte[] raceStartStatus(long time, short ack, long startTime, int raceID, char notification){
        int messageVersion = 0b1;
        byte[] timestamp = longToBytes(time, 6);
        byte[] ackNumber = intToBytes(ack, 2);
        byte[] raceStartTime = longToBytes(startTime, 6);
        int raceIdentifier = raceID;
        byte[] notificationType = intToBytes(notification, 1);

        ByteBuffer result = ByteBuffer.allocate(20);
        result.put(intToBytes(messageVersion, 1));
        result.put(timestamp);
        result.put(ackNumber);
        result.put(raceStartTime);
        result.put(intToBytes(raceIdentifier));
        result.put(notificationType);

        return result.array();
    }

    public byte[] yachtEventCode(long time, short acknowledgeNumber, int raceID, int destSourceID, int incidentID,
                                 int eventID){
        int messageVersion = 0b10;
        byte[] encodeTime = longToBytes(time, 6);
        short ackNum = acknowledgeNumber;
        int raceUID = raceID;//TODO chekc if this is an into for a 4 char string.
        int destSource = destSourceID;
        int incident = incidentID;
        byte[] event = intToBytes(eventID, 1);

        ByteBuffer result = ByteBuffer.allocate(22);
        result.put(intToBytes(messageVersion, 1));
        result.put(encodeTime);
        result.putShort(ackNum);
        result.put(intToBytes(raceUID));
        result.put(intToBytes(destSource));
        result.put(intToBytes(incident));
        result.put(event);
        return result.array();
    }

    public byte[] chatterText(int messageType, String message){
        int messageVersion = 0b1;
        byte[] type = intToBytes(messageType, 1);
        byte[] text = message.getBytes();
        byte[] length = intToBytes(text.length, 1);

        ByteBuffer result = ByteBuffer.allocate(3 + text.length);
        result.put(intToBytes(messageVersion, 1));
        result.put(type);
        result.put(length);
        result.put(text);

        return result.array();
    }

    public static byte[] boatLocation(BoatLocation boatLocation){
        int messageVersionNumber = 0b1;
        byte[] time = longToBytes(boatLocation.getTime(), 6);
        byte[] sourceID = intToBytes(boatLocation.getSourceID(), 4);
        byte[] seqNum = longToBytes(boatLocation.getSequenceNumber(), 4);
        byte[] deviceType = intToBytes(boatLocation.getDeviceType(), 1);
        byte[] latitude = intToBytes(boatLocation.getLatitude(), 4);
        byte[] longitude = intToBytes(boatLocation.getLongitude(), 4);
        byte[] altitude = intToBytes(boatLocation.getAltitude(), 4);
        byte[] heading = intToBytes(boatLocation.getHeading(), 2);
        byte[] pitch = intToBytes(boatLocation.getPitch(), 2);
        byte[] roll = intToBytes(boatLocation.getRoll(), 2);
        byte[] boatSpeed = intToBytes(boatLocation.getBoatSpeed(), 2);
        byte[] cog = intToBytes(boatLocation.getBoatCOG(), 2);
        byte[] sog = intToBytes(boatLocation.getBoatSOG(), 2);
        byte[] apparentWindSpeed = intToBytes(boatLocation.getApparentWindSpeed(), 2);
        byte[] apparentWindAngle = intToBytes(boatLocation.getApparentWindAngle(), 2);
        byte[] trueWindSpeed = intToBytes(boatLocation.getTrueWindSpeed(), 2);
        byte[] trueWindDirection = intToBytes(boatLocation.getTrueWindDirection(), 2);
        byte[] trueWindAngle = intToBytes(boatLocation.getTrueWindAngle(), 2);
        byte[] currentDrift = intToBytes(boatLocation.getCurrentDrift(), 2);
        byte[] currentSet = intToBytes(boatLocation.getCurrentSet(), 2);
        byte[] rudderAngle = intToBytes(boatLocation.getRudderAngle(), 2);

        ByteBuffer result = ByteBuffer.allocate(56);
        result.put(intToBytes(messageVersionNumber, 1));
        result.put(time);
        result.put(sourceID);
        result.put(seqNum);
        result.put(deviceType);
        result.put(latitude);
        result.put(longitude);
        result.put(altitude);
        result.put(heading);
        result.put(pitch);
        result.put(roll);
        result.put(boatSpeed);
        result.put(cog);
        result.put(sog);
        result.put(apparentWindSpeed);
        result.put(apparentWindAngle);
        result.put(trueWindSpeed);
        result.put(trueWindDirection);
        result.put(trueWindAngle);
        result.put(currentDrift);
        result.put(currentSet);
        result.put(rudderAngle);
        return result.array();
    }

    public byte[] markRounding(int time, int ackNumber, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){
        int messageVersionNumber = 0b1;
        byte[] byteTime = longToBytes(time, 6);
        byte[] byteAck = intToBytes(ackNumber, 2);
        byte[] byteRaceID = intToBytes(raceID, 4);
        byte[] byteSourceID = intToBytes(sourceID, 4);
        byte[] byteBoatStatus = intToBytes(boatStatus, 1);
        byte[] byteRoundingSide = intToBytes(roundingSide, 1);
        byte[] byteMarkType = intToBytes(markType, 1);
        byte[] byteMarkID = intToBytes(markID, 1);

        ByteBuffer result = ByteBuffer.allocate(21);
        result.put(intToBytes(messageVersionNumber, 1));
        result.put(byteTime);
        result.put(byteAck);
        result.put(byteRaceID);
        result.put(byteSourceID);
        result.put(byteBoatStatus);
        result.put(byteRoundingSide);
        result.put(byteMarkType);
        result.put(byteMarkID);
        return result.array();
    }

    public byte[] courseWind(byte windID, ArrayList<CourseWind> courseWinds){
        int messageVersionNumber = 0b1;
        byte byteWindID = windID;
        byte[] loopcount = intToBytes(courseWinds.size(), 1);
        ByteBuffer result = ByteBuffer.allocate(3 + 20 * courseWinds.size());
        result.put(intToBytes(messageVersionNumber, 1));
        result.put(byteWindID);
        result.put(loopcount);
        for (CourseWind wind: courseWinds){
            result.put(intToBytes(wind.getID(), 1));
            result.put(longToBytes(wind.getTime(), 6));
            result.put(intToBytes(wind.getRaceID(), 4));
            result.put(intToBytes(wind.getWindDirection(), 2));
            result.put(intToBytes(wind.getWindSpeed(), 2));
            result.put(intToBytes(wind.getBestUpwindAngle(), 2));
            result.put(intToBytes(wind.getBestDownwindAngle(), 2));
            result.put(intToBytes(wind.getFlags(), 1));
        }
        return result.array();
    }

    public byte[] averageWind(int time, int rawPeriod, int rawSampleSpeed, int period2, int speed2, int period3, int speed3, int period4, int speed4){
        int messageVersionNumber = 0b1;
        byte[] byteTime = longToBytes(time,6);
        byte[] byteRawPeriod = intToBytes(rawPeriod, 2);
        byte[] byteRawSpeed = intToBytes(rawSampleSpeed, 2);
        byte[] bytePeriod2 = intToBytes(period2, 2);
        byte[] byteSpeed2 = intToBytes(speed2, 2);
        byte[] bytePeriod3 = intToBytes(period3, 2);
        byte[] byteSpeed3 = intToBytes(speed3, 2);
        byte[] bytePeriod4 = intToBytes(period4, 2);
        byte[] byteSpeed4 = intToBytes(speed4, 2);

        ByteBuffer result = ByteBuffer.allocate(23);
        result.put(intToBytes(messageVersionNumber, 1));
        result.put(byteTime);
        result.put(byteRawPeriod);
        result.put(byteRawSpeed);
        result.put(bytePeriod2);
        result.put(byteSpeed2);
        result.put(bytePeriod3);
        result.put(byteSpeed3);
        result.put(bytePeriod4);
        result.put(byteSpeed4);
        return result.array();
    }

}
