using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;

namespace WCS.Core
{
    public class DataBlock : EntityEx<DBInfo>
    {
        public event Action<byte[]> DbChanged;
        public static List<DataBlock> DBList = new List<DataBlock>();

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

        public ushort Start { get; private set; }
        public ushort Length { get; private set; }
        bool failed = false;
        byte[] Data = new byte[0];
        int id = 0;
        public override string ToString()
        {
            return id.ToString();
        }

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

        public void RefreshData()
        {
            try
            {
                if (Length == 0)
                {
                    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 (Entity.PLCInfo.Ex())
                {
                    data = Entity.PLCInfo.Ex().Accessor.ReadBytes(Entity.No, Start, (ushort)(Length - Start));
                }
                if (!Data.SequenceEqual(data))
                {
                    Data = data;
                    DbChanged?.Invoke(Data);
                }
                failed = false;
            }
            catch
            {
                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)
        {
            try
            {
                bitStart -= Start * 8;
                return (T)Read(typeof(T), ref bitStart, strLength, arrLength);
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        private object Read(Type type, ref int bitStart, int strLength, int arrLength)
        {
            if (failed)
                throw new Exception(this.Entity.No + "连接失败");

            if (type.IsArray)
            {
                var t = type.GetElementType();
                if (t.IsArray)
                {
                    throw new Exception("只支持一维数组");
                }
                var arr = Array.CreateInstance(t, arrLength);

                for (int i = 0; i < arr.Length; i++)
                {
                    var value = Read(t, ref bitStart, strLength, 0);
                    arr.SetValue(value, i);
                }
                return arr;
            }
            else 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;
            }
            else if (type == typeof(bool))
            {
                return ReadBit(ref bitStart);
            }
            else if (type == typeof(string))
            {
                return ReadString(ref bitStart, strLength);
            }
            else if (type.IsEnum)
            {
                return ReadEnum(type, ref bitStart);
            }
            else 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);
            }
            else
            {
                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;
                }
                else
                    throw new Exception("类型不支持");
            }
        }

        public static object ByteToStruct(Type type, byte[] dataBuffer)
        {
            int size = Marshal.SizeOf(type);
            IntPtr 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();
            }
            else if (type == typeof(bool))
            {
                return BitConverter.ToBoolean(data, 0);
            }
            else if (type == typeof(char))
            {
                return BitConverter.ToChar(data, 0);
            }
            else if (type == typeof(short))
            {
                return BitConverter.ToInt16(data, 0);
            }
            else if (type == typeof(ushort))
            {
                return BitConverter.ToUInt16(data, 0);
            }
            else if (type == typeof(int))
            {
                return BitConverter.ToInt32(data, 0);
            }
            else if (type == typeof(uint))
            {
                return BitConverter.ToUInt32(data, 0);
            }
            else if (type == typeof(long))
            {
                return BitConverter.ToInt64(data, 0);
            }
            else if (type == typeof(ulong))
            {
                return BitConverter.ToUInt64(data, 0);
            }
            else if (type == typeof(float))
            {
                return BitConverter.ToSingle(data, 0);
            }
            else if (type == typeof(double))
            {
                return BitConverter.ToDouble(data, 0);
            }
            else
            {
                throw new Exception("类型不支持");
            }
        }

        private byte[] ReadBytes(ref int bitStart, ushort length)
        {
            var start = GetByteStart(bitStart);
            if (Data.Length < start + length)
                throw new Exception($"读取数据的位置超出数据总长度:Data长度--{Data.Length}；总长度--{start + length};Data内容--{Data}");
            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 title = new byte[] { (byte)strLen, (byte)data.Length };
                data = title.Concat(data).ToArray();
            }
            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[] { (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())
            {
                var start = GetByteStart(bitStart);

                Entity.PLCInfo.Ex().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())
            {
                var start = GetByteStart(bitStart);
                var bitIndex = GetBitIndex(bitStart);

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

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

                bitStart += 1;
            }
        }

        #endregion 写入

    }

}
