// -----------------------
// C#
// Keiser.MvxPlugins.Bluetooth: https://github.com/KeiserCorp/Keiser.MvxPlugins.Bluetooth
// -----------------------

namespace Keiser.M3i.BLE_Parser
{
    using System;

    public class Broadcast
    {
        public bool IsValid { get; set; }   // Indicates that broadcast was parsed successfully
        public string UUID { get; set; }    // Universally Unique Hex String
        public int ID { get; set; }         // Domain Unique Ordinal Number (Decimal)
        public int Cadence { get; set; }    // In RPM
        public int HeartRate { get; set; }  // In BPM
        public int Power { get; set; }      // In Watts
        public int BuildMajor { get; set; }
        public int BuildMinor { get; set; }
        public int Interval { get; set; }   // Raw Interval Value
        public bool IsRealTime              // Determines if current values are real time or averages
        {
            get
            {
                return Interval == 0 || (Interval > 128 && Interval < 255);
            }
        }
        public int IntervalValue            // Converted Interval Value (Matches Displayed Interval)
        {
            get
            {
                if (Interval == 0 || Interval == 255)
                    return 0;
                if (Interval > 128 && Interval < 255)
                    return Interval - 128;
                return Interval;
            }
        }

        public int Energy { get; set; }     // In KCal
        public int Trip { get; set; }       // In Miles
        public int Time { get; set; }       // In Seconds
        public int RSSI { get; set; }       // In dBm
        public int Gear { get; set; }       
    }

    public static class Parser
    {
        // Summary:
        //     Parses raw broadcast data into a Broadcast object
        //
        // Parameters:
        //   address:
        //     The address string returned by the Bluetooth LE scanning method.
        //
        //   advertisingData:
        //     The raw advertising data byte array returned by the Bluetooth LE scanning method. 
        //
        //   rssi:
        //     The raw RSSI value returned by the Bluetooth LE scanning method. 
        //
        // Returns:
        //     A Broadcast object with all properties assigned values based on 
        //     the received advertising data.
        //
        public static Broadcast Parse(string address, byte[] advertisingData, int rssi)
        {
            Broadcast broadcast = new Broadcast();
            // Sets broadcast to invalid until parsing is complete
            broadcast.IsValid = false;

            broadcast.UUID = address;
            broadcast.RSSI = rssi;

            // Checks that broadcast is not debug signal
            if (advertisingData.Length < 4 || advertisingData.Length > 19)
                return broadcast;

            // Sets parser index
            int index = 0;

            // Moves index past prefix bits (some platforms remove prefix bits from data)
            if (advertisingData[index] == 2 && advertisingData[index + 1] == 1)
                index += 2;

            // Assigns build values
            // ** Note: Build values are not converted to hex prior to broadcast
            //          so they arrive in a mutated form.
            broadcast.BuildMajor = BuildValueConvert(advertisingData[index++]);
            broadcast.BuildMinor = BuildValueConvert(advertisingData[index++]);

            // Determins which method to use for parsing based on build major
            // ** Note: Build major 6 currently the only build major.
            if (broadcast.BuildMajor == 6 && advertisingData.Length > (index + 13))
            {
                // Raw Interval Value
                broadcast.Interval = advertisingData[index];
                // Ordginal ID
                broadcast.ID = advertisingData[index + 1];
                // Cadence in RPM (broadcast with decimal precision)
                broadcast.Cadence = TwoByteConcat(advertisingData[index + 2], advertisingData[index + 3]) / 10;
                // Heart Rate in BPM (broadcast with decimal precision)
                broadcast.HeartRate = TwoByteConcat(advertisingData[index + 4], advertisingData[index + 5]) / 10;
                // Power in Watts
                broadcast.Power = TwoByteConcat(advertisingData[index + 6], advertisingData[index + 7]);
                // Energy as KCal ("energy burned")
                broadcast.Energy = TwoByteConcat(advertisingData[index + 8], advertisingData[index + 9]);
                // Time in Seconds (broadcast as minutes and seconds)
                broadcast.Time = advertisingData[index + 10] * 60;
                broadcast.Time += advertisingData[index + 11];
                // Trip in Miles (broadcast in miles or Km)
                broadcast.Trip = TwoByteConcat(advertisingData[index + 12], advertisingData[index + 13]);
                if ((broadcast.Trip & 32768) != 0)
                    broadcast.Trip = (int)(broadcast.Trip * 1.60934);
                // Check for additional parameters added to later builds (v.21+)
                if (broadcast.BuildMinor >= 21 && advertisingData.Length > (index + 14))
                {
                    // Raw Gear Value
                    broadcast.Gear = advertisingData[index + 14];
                }

                // Sets broadcast to valid 
                broadcast.IsValid = true;
            }

            return broadcast;
        }

        public static int TwoByteConcat(byte lower, byte higher)
        {
            return (higher << 8) | lower;
        }

        public static int BuildValueConvert(byte value)
        {
            int converted;
            Int32.TryParse(value.ToString("X"), out converted);
            return converted;
        }
    }
}