using CartActivator;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
using FundamentalLib;


namespace MedullaAdapter
{
    // todo: currently use this ladderlogic to act interaction with Medulla.
    // todo: directly integrate into CartActivator.

    // MCU->Medulla is intervally exchanged information.
    public class MCUInterface<T> : LadderLogic<T> where T : DiverCartDefinition
    {
        
        /// ////////////////////////////// IMPLEMENT THESE ////////////////////////////////////////////

        public static void SetMCUProgram(string mcu_device_url, byte[] program)
        {
            List<byte[]> SplitArrayIntoChunks(byte[] array, int chunkSize)
            {
                List<byte[]> chunks = new List<byte[]>();

                for (int i = 0; i < array.Length; i += chunkSize)
                {
                    int currentChunkSize = Math.Min(chunkSize, array.Length - i);
                    byte[] chunk = new byte[currentChunkSize];
                    Array.Copy(array, i, chunk, 0, currentChunkSize);
                    chunks.Add(chunk);
                }
                return chunks;
            }
            //todo: modify this.
            var codeList = SplitArrayIntoChunks(program, 1024);
            int i = 0;
            foreach (var codePack in codeList)
            {
                var downloadCode =
                    new DownloadCodePack(program.Length, 1024 * i, codePack.Length, codePack).GetPack();
                Console.WriteLine(string.Join(" ", downloadCode.Select(p => $"{p:X2}")));
                cart.Embedded.SendMessage(downloadCode);
                Console.WriteLine($"send bytes length:{downloadCode.Length}");
                i++;
                Thread.Sleep(50);
                //while (true)
                //{
                //    var receive = cart.Embedded.GetMessage();
                //    if (receive == null) continue;
                //    if (receive[5] == 0x90 && (receive[6] == 0x05 || receive[6]==0x06)) break;
                //}
            }
            var controlPack2 = new ControlPack(0x01).GetPack();
            cart.Embedded.SendMessage(controlPack2);
            Console.WriteLine("下发代码");
            //MCUTestRunner.DebugSetMCUProgram(program, (bs) => NotifyLowerData("default", bs));
        }

        public static void SendUpperData(string mcu_device_url, byte[] data)
        {
            //todo: this is for VM data exchange, contains upperIO/lowerIO modifications.
            var upperPack = new UpperIOPack(data, data.Length).GetPack();
            cart.Embedded.SendMessage(upperPack);
            //MCUTestRunner.DebugSendUpper(data);
        }


        /// ////////////////////////////// INTERFACES ////////////////////////////////////////////

        public static void NotifyPrint(string mcu_device_url, string message)
        {
            Console.WriteLine($"{mcu_device_url}:{message}");
        }

        // whenever a lower io data is uploaded, call this.
        public static void NotifyLowerData(string mcu_device_url, byte[] lowerIOData)
        {
            using var ms = new MemoryStream(lowerIOData);
            using var br = new BinaryReader(ms);
            if (mcu_logics.TryGetValue(mcu_device_url, out var tup))
            {
                Hedingben.ToastText($"recv iter {br.ReadInt32()} lowerIO data from {mcu_device_url}, operation {tup.name}", $"DIVER-{tup.name}");
                while (ms.Position < lowerIOData.Length)
                {
                    var cid = br.ReadInt16();
                    if (cid < 0 || cid > tup.fields.Length) throw new Exception("invalid Cartfield id!");
                    // if it's upperio skip, otherwise write data.
                    var typeid = br.ReadByte();
                    if (tup.fields[cid].typeid != typeid)
                        throw new Exception($"??? typeid not match for {tup.fields[cid].field}({cid}), expected {tup.fields[cid].typeid} got {typeid}");
                    
                    object value;
                    switch (tup.fields[cid].typeid)
                    {
                        case 0:
                            value = br.ReadBoolean();
                            break;
                        case 1:
                            value = br.ReadByte();
                            break;
                        case 2:
                            value = br.ReadSByte();
                            break;
                        case 3:
                            value = br.ReadChar();
                            break;
                        case 4:
                            value = br.ReadInt16();
                            break;
                        case 5:
                            value = br.ReadUInt16();
                            break;
                        case 6:
                            value = br.ReadInt32();
                            break;
                        case 7:
                            value = br.ReadUInt32();
                            break;
                        case 8:
                            value = br.ReadSingle();
                            break;
                        default:
                            throw new Exception($"Unsupported type ID: {tup.fields[cid].typeid}");
                    }
                    
                    if (tup.fields[cid].isUpper) continue;
                    tup.fields[cid].fi.SetValue(cart, value);
                }

                // ok to send current data.
                using var sends = new MemoryStream();
                using var bw = new BinaryWriter(sends);
                bw.Write(tup.iterations++);
                for (var cid = 0; cid < tup.fields.Length; cid++)
                {
                    bw.Write((short)cid);
                    bw.Write((byte)tup.fields[cid].typeid);
                    var val = tup.fields[cid].fi.GetValue(cart);
                    switch (tup.fields[cid].typeid)
                    {
                        case 0:
                            bw.Write((bool)val);
                            break;
                        case 1:
                            bw.Write((byte)val);
                            break;
                        case 2:
                            bw.Write((sbyte)val);
                            break;
                        case 3:
                            bw.Write((char)val);
                            break;
                        case 4:
                            bw.Write((short)val);
                            break;
                        case 5:
                            bw.Write((ushort)val);
                            break;
                        case 6:
                            bw.Write((int)val);
                            break;
                        case 7:
                            bw.Write((uint)val);
                            break;
                        case 8:
                            bw.Write((float)val);
                            break;
                    }
                }

                SendUpperData(mcu_device_url, sends.ToArray());
            }
            else
            {
                Hedingben.ToastText($"warning: {mcu_device_url} received lowerIOData but not registered","DIVERWarning");
            }
        }
       
        public static void Start(T c)
        {
            cart = c;

            var logics = typeof(T).Assembly.GetTypes()
                .Where(p => p.GetCustomAttribute<LogicRunOnMCUAttribute>() != null).ToArray();
            foreach (var logic in logics)
            {
                var attr = logic.GetCustomAttribute<LogicRunOnMCUAttribute>();
                Console.WriteLine($"Set logic {logic.Name} to run on MCU-VM @ device {attr.mcu_url}");

                byte[] ReadAllBytes(Stream stream)
                {
                    using (var ms = new MemoryStream())
                    {
                        stream.CopyTo(ms);
                        return ms.ToArray();
                    }
                }
                var bytes = ReadAllBytes(Assembly.GetExecutingAssembly().GetManifestResourceStream($"{logic.Name}.bin"));
                SetMCUProgram(attr.mcu_url, bytes);
                var json = UTF8Encoding.UTF8.GetString(ReadAllBytes(Assembly.GetExecutingAssembly()
                    .GetManifestResourceStream($"{logic.Name}.bin.json")));
                Console.WriteLine(json);
                var fields = JsonConvert.DeserializeObject<PField[]>(json);
                if (mcu_logics.ContainsKey(attr.mcu_url))
                    throw new Exception($"Already have logic for {attr.mcu_url}: LadderLogic {logic.Name}");
                mcu_logics[attr.mcu_url] = new LogicInfo() { fields = fields, name = logic.Name };
                foreach (var pField in fields)
                {
                    pField.fi = typeof(T).GetField(pField.field);
                    if (pField.fi == null)
                        throw new Exception($"field {pField.field} doesn't exist in cart object?");
                    pField.isUpper = pField.fi.IsDefined(typeof(AsUpperIO));
                    // todo: check type.
                }
            }
        }

        /// ///////////////////////////////// DONT CARE /////////////////////////////////////
        private static T cart;

        class PField
        {
            public string field;
            public FieldInfo fi;
            public bool isUpper;
            public int typeid, offset;
        }

        class LogicInfo
        {
            public string name;
            public PField[] fields;
            public int iterations;
        }
        private static Dictionary<string, LogicInfo> mcu_logics = new();
        public override void Operation(int iteration)
        {
            // just send LIO.
        }
    }

    //unsafe class MCUTestRunner
    //{

    //    public delegate void NotifyLowerDelegate(byte* changedStates, int length);
    //    [DllImport("MCURuntime.dll", CallingConvention = CallingConvention.Cdecl)]
    //    public static extern void set_lowerio_cb(NotifyLowerDelegate callback);

    //    private static NotifyLowerDelegate DNotifyStateChanged = StateChanged;

    //    [DllImport("MCURuntime.dll")]
    //    static extern void test(byte* bin, int len);
    //    [DllImport("MCURuntime.dll")]
    //    static extern void put_upper(byte* bin, int len);

    //    private static Action<byte[]> lo_notifier;
    //    private static void StateChanged(byte* changedstates, int length)
    //    {
    //        byte[] byteArray = new byte[length];
    //        Marshal.Copy((IntPtr)changedstates, byteArray, 0, length);
    //        lo_notifier(byteArray); 
    //    }

    //    public static void DebugSendUpper(byte[] data)
    //    { 
    //        fixed (byte* ptr = data)
    //        {
    //            put_upper(ptr, data.Length);
    //        }
    //    }
    //    public static void DebugSetMCUProgram(byte[] program, Action<byte[]> notifier)
    //    {
    //        lo_notifier = notifier;
    //        set_lowerio_cb(DNotifyStateChanged);
    //        new Thread(() =>
    //        {
    //            var allb = new byte[10240]; //10K runtime
    //            Array.Copy(program, allb, program.Length);
    //            fixed (byte* ptr = allb)
    //            {
    //                test(ptr, 10240);
    //            }
    //        }).Start();
    //    }
    //}
}
