using System.Collections;
using System.Reflection;
using System.Runtime.InteropServices;

namespace WCS.Core;

public class DataBlock : EntityEx<DBInfo>
{
    public static List<DataBlock> DBList = new();
    private byte[] Data = new byte[0];

    private DateTime faildTime = DateTime.MinValue;
    private readonly int id;

    private readonly List<ProtocolProxyBase> ProxyList = new();

    public DataBlock(DBInfo ent, World world) : base(ent)
    {
        World = world;
        DBList.Add(this);
        id = DBList.Count;
    }

    public ushort Start { get; private set; }
    public ushort Length { get; private set; }
    public World World { get; }
    public bool Failed { get; private set; } = true;
    public event Action<byte[]> DbChanged;

    public override string ToString()
    {
        return id.ToString();
    }

    public void RefreshData()
    {
        try
        {
            var plc = Entity.PLCInfo.Ex(World);
            if (Entity.PLCInfo.Type != PLCType.VitrualRedisPLC)
                if (Failed)
                    if (!plc.Ping)
                    {
                        if (Entity.PLCInfo.IP == "1") throw new Exception($"{Entity.PLCInfo.IP}无法访问");
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.WriteLine($"{Entity.PLCInfo.IP}无法访问");
                        Console.ResetColor();
                        throw new Exception($"{Entity.PLCInfo.IP}无法访问");
                    }

            Start = (ushort)ProxyList.Min(v => v.Info.Position);
            var last = ProxyList.OrderBy(v => v.Info.Position).Last();
            Length = (ushort)(last.Info.Position + last.BytesCount);
            var data = new byte[0];
            
            lock (plc)
            {
                
                data = plc.Accessor.ReadBytes(Entity.No, Start, (ushort)(Length - Start));
                
            }

            
            if (!Data.SequenceEqual(data))
            {
                Data = data;
                DbChanged?.Invoke(Data);
            }

            Failed = false;
        }
        catch (Exception ex)
        {
            Failed = true;

            throw;
        }
    }

    public PlcItem<T> Regist<T>(ProtocolProxyBase proxy, string objid, string name, int start, byte arrLen = 1,
        byte strLen = 0)
    {
        if (!ProxyList.Contains(proxy))
            ProxyList.Add(proxy);
        var t = typeof(T);
        var pv = new PlcItem<T>(objid, name, this, start, arrLen, strLen);
        //Values.Add(pv);
        return pv;
    }

    private ushort GetByteStart(int bitStart)
    {
        var res = bitStart / 8;
        return (ushort)res;
    }

    private ushort GetBitIndex(int bitStart)
    {
        return (ushort)(bitStart % 8);
    }

    #region 读取

    public T Read<T>(int bitStart, int strLength, int arrLength)
    {
        bitStart -= Start * 8;
        return (T)Read(typeof(T), ref bitStart, strLength, arrLength);
    }

    private object Read(Type type, ref int bitStart, int strLength, int arrLength)
    {
        if (Failed) throw new Exception(Entity.No + "连接失败");
        if (type.IsArray)
        {
            var t = type.GetElementType();
            if (t.IsArray) throw new Exception("只支持一维数组");
            var arr = Array.CreateInstance(t, arrLength);

            for (var i = 0; i < arr.Length; i++)
            {
                var value = Read(t, ref bitStart, strLength, 0);
                arr.SetValue(value, i);
            }

            return arr;
        }

        if (typeof(IList).IsAssignableFrom(type))
        {
            var t = type.GetGenericArguments().First();
            var res = Activator.CreateInstance(type) as IList;
            while (bitStart < Length * 8)
            {
                var value = Read(t, ref bitStart, strLength, 0);
                res.Add(value);
            }

            return res;
        }

        if (type == typeof(bool)) return ReadBit(ref bitStart);

        if (type == typeof(string)) return ReadString(ref bitStart, strLength);

        if (type.IsEnum) return ReadEnum(type, ref bitStart);

        if (type.IsPrimitive)
        {
            var revers = Entity.PLCInfo.Type == PLCType.Siemens;

            return ReadPrimitive(type, ref bitStart, revers);

            //var revers = Entity.PLC.TYPE == PLCType.西门子;

            //return ReadPrimitive(type, ref bitStart, false);
        }

        if (type.IsValueType)
        {
            //ushort size = (ushort)Marshal.SizeOf(type);
            //var s = bitStart;
            //var bytes = ReadBytes(ref s, size).Reverse().ToArray();
            //var o = ByteToStruct(type, bytes);

            var res = Activator.CreateInstance(type);
            foreach (var p in type.GetProperties())
            {
                var value = Read(p.PropertyType, ref bitStart, strLength, arrLength);
                p.SetValue(res, value);
            }

            return res;
        }

        throw new Exception("类型不支持");
    }

    public static object ByteToStruct(Type type, byte[] dataBuffer)
    {
        var size = Marshal.SizeOf(type);
        var allocIntPtr = Marshal.AllocHGlobal(size);
        try
        {
            Marshal.Copy(dataBuffer, 0, allocIntPtr, size);
            return Marshal.PtrToStructure(allocIntPtr, type);
        }
        finally
        {
            Marshal.FreeHGlobal(allocIntPtr);
        }
    }

    private string ReadString(ref int bitStart, int strLen)
    {
        if (Entity.PLCInfo.Type == PLCType.Siemens)
        {
            var data = ReadBytes(ref bitStart, (ushort)strLen);

            //var len = data[1];
            //data = data.Skip(2).Take(len).ToArray();
            var str = Configs.StringEncoding.GetString(data);
            return str;
        }
        else
        {
            var data = ReadBytes(ref bitStart, (ushort)strLen);
            var str = Configs.StringEncoding.GetString(data);
            return str;
        }
    }

    private object ReadEnum(Type enumType, ref int bitStart)
    {
        var type = enumType.GetEnumUnderlyingType();

        var reverse = false;

        if (enumType.GetCustomAttribute<FlagsAttribute>() == null) reverse = Entity.PLCInfo.Type == PLCType.Siemens;

        var value = ReadPrimitive(type, ref bitStart, reverse);
        var res = Enum.Parse(enumType, value.ToString());
        return res;
    }

    private object ReadPrimitive(Type type, ref int bitStart, bool reverse)
    {
        var size = (ushort)Marshal.SizeOf(type);
        if (type == typeof(bool))
            size = 1;
        var data = ReadBytes(ref bitStart, size);
        if (reverse)
            data = data.Reverse().ToArray();
        if (type == typeof(byte))
            return data.First();
        if (type == typeof(bool))
            return BitConverter.ToBoolean(data, 0);
        if (type == typeof(char))
            return BitConverter.ToChar(data, 0);
        if (type == typeof(short))
            return BitConverter.ToInt16(data, 0);
        if (type == typeof(ushort))
            return BitConverter.ToUInt16(data, 0);
        if (type == typeof(int))
            return BitConverter.ToInt32(data, 0);
        if (type == typeof(uint))
            return BitConverter.ToUInt32(data, 0);
        if (type == typeof(long))
            return BitConverter.ToInt64(data, 0);
        if (type == typeof(ulong))
            return BitConverter.ToUInt64(data, 0);
        if (type == typeof(float))
            return BitConverter.ToSingle(data, 0);
        if (type == typeof(double))
            return BitConverter.ToDouble(data, 0);
        throw new Exception("类型不支持");
    }

    private byte[] ReadBytes(ref int bitStart, ushort length)
    {
        var start = GetByteStart(bitStart);
        if (Data.Length < start + length)
        {
            var data1 = "";
            foreach (var item in Data.ToList()) data1 = data1 + $"[{item}]";
            var msg = "读取数据的位置超出数据总长度:";
            msg = msg + $"Data--{Data.Length};";
            msg = msg + $"bitStart--{bitStart};";
            msg = msg + $"start--{start};";
            msg = msg + $"length--{length};";
            msg = msg + $"读取PLC传入参数{Entity.No},{Start}，{(ushort)(Length - Start)};";
            msg = msg + $"Data内容--{data1}";
            throw new Exception(msg);
        }

        var data = Data.Skip(start).Take(length).ToArray();
        bitStart += length * 8;
        return data;
    }

    private bool ReadBit(ref int bitStart)
    {
        var start = GetByteStart(bitStart);
        var b = Data.Skip(start).First();
        var bitIndex = GetBitIndex(bitStart);
        bitStart++;
        return b.GetBit(bitIndex);
    }

    #endregion 读取

    #region 写入

    public void Write<T>(int bitStart, T value, int strLength, int arrLength)
    {
        Write(typeof(T), ref bitStart, value, strLength, arrLength);
    }

    private void Write(Type type, ref int bitStart, object value, int strLength, int arrLength)
    {
        if (type.IsArray)
        {
            var t = type.GetElementType();
            if (t.IsArray) throw new Exception("只支持一维数组");
            var arr = value as Array;
            if (arr.Length > arrLength)
                throw new Exception("数组长度超出");
            foreach (var obj in arr) Write(obj.GetType(), ref bitStart, obj, strLength, arrLength);
        }
        else if (type == typeof(bool))
        {
            WriteBit(ref bitStart, (bool)value);
        }
        else if (type == typeof(string))
        {
            WriteString(ref bitStart, value.ToString(), strLength);
        }
        else if (type.IsEnum)
        {
            WriteEnum(ref bitStart, value);
        }
        else if (type.IsPrimitive)
        {
            var revers = Entity.PLCInfo.Type == PLCType.Siemens;
            WritePrimitive(ref bitStart, value, revers);
        }
        else
        {
            throw new Exception("类型不支持");
        }
    }

    private void WriteString(ref int bitStart, string value, int strLen)
    {
        var data = Configs.StringEncoding.GetBytes(value);
        if (data.Length > strLen)
            throw new Exception("字符串长度超出");
        //if (Entity.PLCInfo.Type == PLCType.Siemens)
        //{
        //    var a = Encoding.UTF8.GetString(data);
        //    var title = new byte[] { (byte)strLen, (byte)data.Length };
        //    data = title.Concat(data).ToArray();
        //    var b = Encoding.UTF8.GetString(data);

        //    var c = 0;
        //}
        WriteBytes(ref bitStart, data);
    }

    private void WriteEnum(ref int bitStart, object value)
    {
        var etype = value.GetType();
        var type = etype.GetEnumUnderlyingType();
        var obj = Convert.ChangeType(value, type);
        var reverse = false;
        if (etype.GetCustomAttribute<FlagsAttribute>() == null) reverse = Entity.PLCInfo.Type == PLCType.Siemens;
        WritePrimitive(ref bitStart, obj, reverse);
    }

    private void WritePrimitive(ref int bitStart, object value, bool reverse)
    {
        var type = value.GetType();
        byte[] data;
        if (type == typeof(char))
            data = BitConverter.GetBytes((char)value);
        else if (type == typeof(byte))
            data = new[] { (byte)value };
        else if (type == typeof(short))
            data = BitConverter.GetBytes((short)value);
        else if (type == typeof(ushort))
            data = BitConverter.GetBytes((ushort)value);
        else if (type == typeof(int))
            data = BitConverter.GetBytes((int)value);
        else if (type == typeof(uint))
            data = BitConverter.GetBytes((uint)value);
        else if (type == typeof(long))
            data = BitConverter.GetBytes((long)value);
        else if (type == typeof(ulong))
            data = BitConverter.GetBytes((ulong)value);
        else
            throw new Exception("类型不支持");
        if (reverse)
            data = data.Reverse().ToArray();
        WriteBytes(ref bitStart, data);
    }

    private void WriteBytes(ref int bitStart, byte[] data)
    {
        lock (Entity.PLCInfo.Ex(World))
        {
            var start = GetByteStart(bitStart);

            Entity.PLCInfo.Ex(World).Accessor.WriteBytes(Entity.No, start, data);
            data.CopyTo(Data, start - Start);
            bitStart += data.Length * 8;
        }
    }

    private void WriteBit(ref int bitStart, bool flag)
    {
        lock (Entity.PLCInfo.Ex(World))
        {
            var start = GetByteStart(bitStart);
            var bitIndex = GetBitIndex(bitStart);

            var b = Data.Skip(start).First();
            b = b.SetBit(bitIndex, flag);
            var data = new[] { b };

            Entity.PLCInfo.Ex(World).Accessor.WriteBytes(Entity.No, start, data);
            data.CopyTo(Data, start - Start);

            bitStart += 1;
        }
    }

    #endregion 写入
}