using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Interop;
using InABox.Core;
using NPOI.HPSF;
using NPOI.SS.Formula.Functions;
using PRSServer;

namespace InABox.DigitalMatter
{
    public static class DMFactory
    {
        private static readonly Dictionary<byte, string> _devices = new();

        private static readonly Dictionary<byte, Type> _messages = new();

        private static readonly Dictionary<byte, Tuple<Type, string>> _fields = new();

        private static readonly Dictionary<byte, Type> _bluetoothtags = new();

        private static DumpFormat _dumpFormat;
        private static string _dumpFile;

        public static void Initialise(DumpFormat dumpFormat, string dumpFile)
        {
            _dumpFormat = dumpFormat;
            _dumpFile = dumpFile;

            RegisterMessage<DMHelloRequest>(0x00);
            RegisterMessage<DMHelloResponse>(0x01);
            RegisterMessage<DMDataRequest>(0x04);
            RegisterMessage<DMConfirmRequest>(0x05);
            RegisterMessage<DMConfirmResponse>(0x06);

            RegisterField<DMGPSField>(0x00, "GPS Data");
            RegisterField<DMDigitalDataField>(0x02, "Digital Data");
            RegisterField<DMOperatorField>(0x03, "Operator Info");
            RegisterField<DMAnalogueDataField16>(0x06, "Analogue Data");
            RegisterField<DMTripDataField>(0x0F, "Trip Data");
            RegisterField<DMBluetoothTagList>(0x1D, "BT Tag List");
            RegisterField<DMBluetoothTagData>(0x1E, "BT Tag Data");

            // Current DMT Devices
            RegisterDevice(0x4B, "Bolt");
            RegisterDevice(0x44, "Dart2");
            RegisterDevice(0x4E, "Eagle");
            RegisterDevice(0x4A, "Falcon");
            RegisterDevice(0x43, "G62 Cellular");
            RegisterDevice(0x4F, "G120");
            RegisterDevice(0x4D, "Oyster2 Cellular");
            RegisterDevice(0x3E, "Remora2");
            RegisterDevice(0x49, "Yabby GPS");
            RegisterDevice(0x48, "Yabby WiFi Cellular");
            RegisterDevice(0x72, "Manta Fusion");
            RegisterDevice(0x73, "G70");

            //Obsolete DMT Devices
            RegisterDevice(0x22, "Dart");
            RegisterDevice(0x1E, "Flexi1");
            RegisterDevice(0x11, "G52 Solar");
            RegisterDevice(0x17, "G60");
            RegisterDevice(0x1C, "G100");
            RegisterDevice(0x21, "Remora");
            RegisterDevice(0x3A, "Oyster Cellular(v1)");
            RegisterDevice(0x34, "Sting");


            RegisterBluetoothTag<DMGuppyBluetoothTag>(0x00);
            RegisterBluetoothTag<DMIBeaconBluetoothTag>(0x01);
            RegisterBluetoothTag<DMEddystoneBluetoothTag>(0x02);
            RegisterBluetoothTag<DMIngicsiBS01BasicBluetoothTag>(0x03);
            RegisterBluetoothTag<DMIngicsiBS01TemperatureBluetoothTag>(0x04);
            RegisterBluetoothTag<DMSensorNodeBluetoothTag>(0x05);
            RegisterBluetoothTag<DMEddystoneTLMBluetoothTag>(0x06);
            RegisterBluetoothTag<DMTechnotonES7BluetoothTag>(0x07);
            RegisterBluetoothTag<DMGeoboxTPMSBluetoothTag>(0x08);
            RegisterBluetoothTag<DMEscortFuelBluetoothTag>(0x09);
            RegisterBluetoothTag<DMIngicsiBS04BluetoothTag>(0x0A);
            RegisterBluetoothTag<DMGenericBluetoothTag>(0xFF);
        }

        public static void RegisterDevice(byte identifier, string name)
        {
            _devices[identifier] = name;
        }

        public static string GetDeviceName(byte identifier)
        {
            if (_devices.ContainsKey(identifier))
                return _devices[identifier];
            return string.Format("Unknown Device (0x{0})", BitConverter.ToString(new[] { identifier }));
        }

        public static void RegisterMessage<TMessage>(byte identifier) where TMessage : DMMessage
        {
            _messages[identifier] = typeof(TMessage);
        }

        public static int PeekPayloadLength(IList<byte> data)
        {
            if(data.Count < 5)
            {
                return 0;
            }
            return BitConverter.ToUInt16(new byte[] { data[3], data[4] }, 0);
        }

        private static DMMessage ParseMessage(IDMReadBuffer buffer)
        {
            if (buffer.BufferSize < 3)
                throw new Exception("Invalid Packet Length");

            var magicnumber = buffer.PeekBytes(0, 2);
            if (magicnumber[0] != 0x02 || magicnumber[1] != 0x55)
                throw new Exception("Invalid Header");

            var messagetype = buffer.PeekByte(2);
            if (!_messages.ContainsKey(messagetype))
                throw new Exception(string.Format("Invalid Message Type({0})", messagetype));

            var payloadlength = buffer.PeekUInt16(3);
            if (buffer.BufferSize - payloadlength < 5)
                throw new Exception(string.Format("Payload Length Mismatch - Expected: {0} Actual {1}", payloadlength, buffer.BufferSize - 5));

            var message = (Activator.CreateInstance(_messages[messagetype]) as DMMessage)!;
            try
            {
                message.Decode(buffer);
            }
            catch (Exception e)
            {
                Logger.Send(
                    LogType.Error,
                    Environment.CurrentManagedThreadId.ToString(),
                    string.Format("Unable to Parse Message: {0} Data=({1})", e.Message,
                        BitConverter.ToString(buffer.PeekBytes(0, payloadlength + 3)))
                );
            }

            return message;
        }

        private static void DumpMessageBytes(byte[] data, DMMessage message)
        {
            switch (_dumpFormat)
            {
                case DumpFormat.Raw:
                    using (var stream = DumpStream())
                    {
                        stream.Write(data); // Write data
                        stream.WriteByte(0x0a); // Newline
                    }
                    break;
                case DumpFormat.SemiParsed:
                    using (var stream = DumpStream())
                    {
                        stream.Write(message.Dump()); // Write message dump
                        stream.WriteByte(0x0a); // Newline
                    }
                    break;
            }
        }
        private static void DumpMessageBytes(List<byte> data, DMMessage message)
        {
            switch (_dumpFormat)
            {
                case DumpFormat.Raw:
                    using (var stream = DumpStream())
                    {
                        stream.Write(data.ToArray()); // Write data
                        stream.WriteByte(0x0a); // Newline
                    }
                    break;
                case DumpFormat.SemiParsed:
                    using (var stream = DumpStream())
                    {
                        stream.Write(message.Dump()); // Write message dump
                        stream.WriteByte(0x0a); // Newline
                    }
                    break;
            }
        }

        public static DMMessage ParseMessage(byte[] data)
        {
            var buffer = new DMArrayReadBuffer(data);
            var message = ParseMessage(buffer);
            DumpMessageBytes(data, message);

            return message;
        }

        public static DMMessage ParseMessage(List<byte> data)
        {
            var buffer = new DMListReadBuffer(data);
            var message = ParseMessage(buffer);
            DumpMessageBytes(data, message);
            return message;
        }

        public static void RegisterField<TField>(byte identifier, string name) where TField : DMField
        {
            _fields[identifier] = new Tuple<Type, string>(typeof(TField), name);
        }

        public static string GetFieldName(byte identifier)
        {
            if (_fields.ContainsKey(identifier))
                return _fields[identifier].Item2;
            return string.Format("Unknown Field (0x{0})", BitConverter.ToString(new[] { identifier }));
        }

        public static DMField ParseField(IDMReadBuffer buffer)
        {
            if (buffer.BufferSize < 2)
                throw new Exception("Invalid Field Length");

            var type = buffer.PeekByte(0);

            if (!_fields.ContainsKey(type))
                throw new Exception(string.Format("Invalid Field Type ({0})", type));

            var field = Activator.CreateInstance(_fields[type].Item1) as DMField;
            field.Decode(buffer);
            return field;
        }

        public static void RegisterBluetoothTag<TTagType>(byte identifier) where TTagType : DMBluetoothTag
        {
            _bluetoothtags[identifier] = typeof(TTagType);
        }

        public static DMBluetoothTag ParseBluetoothTag(byte type, byte[] data)
        {
            if (!_bluetoothtags.ContainsKey(type))
                throw new Exception(string.Format("Invalid Bluetooth Tag Type ({0})", type));

            var tag = Activator.CreateInstance(_bluetoothtags[type]) as DMBluetoothTag;
            tag.DataLength = (byte)data.Length;
            tag.Decode(data);
            return tag;
        }

        private static Stream DumpStream()
        {
            return new FileStream(_dumpFile, FileMode.Append);
        }

        private static void DumpData(byte[] data)
        {
            try
            {
                using (var stream = new FileStream(_dumpFile, FileMode.Append))
                {
                    stream.Write(data);
                }
            }
            catch (Exception e)
            {
                Logger.Send(LogType.Error, "", $"Error while dumping data: {CoreUtils.FormatException(e)}");
            }
        }
    }
}