using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Runtime.InteropServices;

namespace WCS.Core;

public abstract class ProtocolProxyBase : IProtocolProxy
{
    public DateTime ChangedTime;

    private byte[] Data = new byte[0];
    public DateTime Frame;
    private readonly string Id = Guid.NewGuid().ToString();
    public Dictionary<string, PlcItem> Items = new();
    public Type ProtocolType, ProtocolDataType;

    public ProtocolProxyBase(Device dev, ProtocolInfo info, Type protocolType, World world)
    {
        World = world;
        Frame = world.Frame;
        ChangedTime = world.Frame;
        Device = dev;
        Info = info;
        Db = info.DBInfo.Ex(world);
        Db.DbChanged += Db_DbChanged;
        ProtocolType = protocolType;

        var bitStart = info.Position * 8; //偏移量,按位算

        //this.Start = start;
        //PlcDB db = new PlcDB(null, 50, 100);
        var lst = GetType().GetInterfaces().ToList();

        var ps = lst.SelectMany(v => v.GetProperties()).ToArray();
        foreach (var p in ps)
        {
            if (p.GetIndexParameters().Length > 0) continue;
            if (typeof(ProtocolProxyBase).GetProperty(p.Name,
                    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) != null) continue;

            #region 计算偏移量

            var modeNum = 0;

            var t = p.PropertyType;
            if (t.IsEnum)
                t = t.GetEnumUnderlyingType();
            if (t == typeof(bool)) //bit类型，db块中无须补齐16位
                modeNum = 1;
            else if (t.IsPrimitive && Marshal.SizeOf(t) == 1) //byte char 等单字节类型，db块中无须补齐16位
                modeNum = 8;
            else //其他类型，db块中必须补齐16位
                modeNum = 16;
            var mod = bitStart % modeNum;
            if (mod > 0) bitStart += modeNum - mod;

            #endregion 计算偏移量

            byte arrlen = 0;
            byte strLen = 0;
            if (t.IsArray)
            {
                var attr =
                    p.GetCustomAttributes(typeof(MaxLengthAttribute), true).FirstOrDefault() as MaxLengthAttribute;
                if (attr != null)
                    arrlen = (byte)attr.Length;
            }
            else if (p.PropertyType == typeof(string))
            {
                var attr =
                    p.GetCustomAttributes(typeof(StringLengthAttribute), true)
                        .FirstOrDefault() as StringLengthAttribute;
                strLen = (byte)attr.MaximumLength;
            }

            var m = typeof(DataBlock).GetMethod("Regist");
            m = m.MakeGenericMethod(p.PropertyType);
            var item = m.Invoke(Db, new object[] { this, Id, p.Name, bitStart, arrlen, strLen }) as PlcItem;
            Items.Add(p.Name, item);
            bitStart += item.DataSizeOfBits;
            BytesCount += item.DataSize;
        }

        ProtocolDataType = ProtocolType.Assembly.GetTypes().Where(v => v.IsClass || v.IsValueType)
            .First(v => v.GetInterface(ProtocolType.Name) != null && v != GetType());
    }

    public ProtocolInfo Info { get; }
    public int BytesCount { get; }
    public Device Device { get; }
    public World World { get; }
    public DataBlock Db { get; }

    public string Code => Device.Code;

    private void Db_DbChanged(byte[] data)
    {
        var pos = Info.Position - Db.Start;
        var bytes = data.Skip(pos).Take(BytesCount).ToArray();
        if (Data.SequenceEqual(bytes))
            return;
        Frame = World.Frame;
        ChangedTime = World.Frame;
        Data = bytes;
        DataChanged();
    }

    protected abstract void DataChanged();

    #region

    public void AddEvent(string eventName, Delegate delgate)
    {
        throw new NotImplementedException();
    }

    public void RemoveEvent(string eventName, Delegate delgate)
    {
        throw new NotImplementedException();
    }

    public T CallReturn<T>(string methodName, params object[] args)
    {
        throw new NotImplementedException();
    }

    public void CallVoid(string methodName, params object[] args)
    {
        throw new NotImplementedException();
    }

    public T Get<T>(string propertyName)
    {
        var item = Items[propertyName] as PlcItem<T>;
        var res = item.Value;
        var channel = Ltc.GetChannel();
        return res;
    }

    public void Set<T>(string propertyName, T value)
    {
        var item = Items[propertyName] as PlcItem<T>;
        item.Value = value;
        var channel = Ltc.GetChannel();
        if (channel != null)World.OnInternalLog(channel, $"设置值:设置{Device.Code}.{ProtocolType.Name}.{propertyName} 值:{value}");
        Frame = World.Frame;
        ChangedTime = World.Frame;
        DataChanged();
    }

    #endregion
}