using GeneralData;
using HardwareData;
using Mini8Communicator;
using Mini8CommunicatorData;
using ModBusTcp;
using ProtocalGeneral;
using System.Collections.Concurrent;
using Universal;

namespace Mini8CommunicatorModbus;

public class Mini8Communcator_Modbus_FluentModbus : IMini8Communicator, ITcpConnectNority
{
    //Mini8 can not handle commands faster than this interval frequency (~200ms)
    private int _interval = 200;

    //Modbus Max request length is 125
    private ushort _groupeCount = 125;
    private ushort _totalCount = 500;

    //The first Address of Mini8 communication is 15500
    private ushort _startIndex = 15500;

    //A mini8 Data Area for Limits
    private ushort _limitCount = 64;
    private ushort _limitStartIndex = 5193;

    private int _port = -1;
    private string _ip = string.Empty;

    private IDictionary<byte, ChannelAddress>? _addresses;


    private readonly ConcurrentDictionary<byte, Mini8Output> _outputCache = [];

    private Timer? _updateDataTimer;
    private IMini8Provider? _provider;
    private DataSpilterByte? _spilter;
    private Modbus_Tcp? _modbus;

    bool IMini8Communicator.Initialize(IMini8Provider provider, object mini8Config, object address)
    {
        if (provider is null || this._modbus is not null)
            return false;

        if (mini8Config is not Mini8Address mini8)
            return false;

        if (address is not IDictionary<byte, ChannelAddress> addresses)
            return false;

        if (string.IsNullOrEmpty(mini8.Address) || !mini8.Port.InRange(0, 65535))
            return false;

        this._interval = mini8.Interval;
        this._groupeCount = mini8.GroupeCount;
        this._totalCount = mini8.TotalCount;
        this._startIndex = mini8.StartIndex;
        this._limitCount = mini8.LimitCount;
        this._limitStartIndex = mini8.LimitStartIndex;

        this._ip = mini8.Address;
        this._port = mini8.Port;
        this._addresses = addresses;
        this._provider = provider;
        return true;
    }

    bool IMini8Communicator.Open()
    {
        if (this._modbus is not null)
            return false;
        if (this._provider is null)
            return false;
        if (this._addresses is null)
            return false;

        if (string.IsNullOrEmpty(this._ip) || !this._port.InRange(0, 65535))
            return false;

        Modbus_Tcp modbus = new();
        modbus.Initialize($"Mini8Communicator_Modbus {this._ip} : {this._port}", this);

        if (!modbus.Open(this._ip, (ushort)this._port))
            return false;

        this._modbus = modbus;

        this._spilter = new DataSpilterByte(_startIndex);
        this._addresses.Foreach(t => _outputCache[t.Value.ChannelIndex] = new());

        GetMini8Output(false);
        return true;

    }

    bool IMini8Communicator.GetRealtimeDataFromDevice(out Dictionary<byte, Mini8Output>? data)
    {
        data = null;
        if (Checker.IsNull(_provider, _modbus, _addresses, _spilter, _outputCache))
            return false;

        lock (_outputCache)
            data = _outputCache.ToDictionary();

        return true;
    }

    bool IMini8Communicator.GetRealtimeLimit(out Dictionary<byte, Mini8Limit>? data)
    {
        data = null;

        if (Checker.IsNull(_provider, _modbus, _addresses))
            return false;

        data = [];

        foreach (ChannelAddress item in _addresses!.Values)
        {
            data[item.ChannelIndex] = new();
            byte[]? caps = this._modbus!.GetBuffer(item.Caps, 1);
            byte[]? floor = this._modbus.GetBuffer(item.Floor, 1);
            byte[]? capsWarning = this._modbus.GetBuffer(item.CapsWarning, 1);
            byte[]? floorWarning = this._modbus.GetBuffer(item.FloorWarning, 1);

            data[item.ChannelIndex].Caps = BufferToUshort(caps);
            data[item.ChannelIndex].Floor = BufferToUshort(floor);
            data[item.ChannelIndex].CapsWarning = BufferToUshort(capsWarning);
            data[item.ChannelIndex].FloorWarning = BufferToUshort(floorWarning);
        }

        return true;
    }

    bool IMini8Communicator.SendMini8Data(byte channelIndex, Mini8Input output)
    {
        if (Checker.IsNull(_provider, _modbus, _addresses))
            return false;

        if (!this._addresses!.TryGetValueNotNull(channelIndex, out ChannelAddress value))
            return false;

        bool b = true;
        b &= this._modbus!.SetFloat(value.SetPoint, output.SetPoint);
        b &= this._modbus.SetFloat(value.Running_P, output.Running_P);
        b &= this._modbus.SetFloat(value.Running_I, output.Running_I);
        b &= this._modbus.SetFloat(value.Running_D, output.Running_D);
        b &= this._modbus.SetFloat(value.SetpointUpRate, output.SetpointUpRate);
        b &= this._modbus.SetFloat(value.SetpointDownRate, output.SetpointDownRate);

        try
        {
            b &= this._modbus.SetUshort(value.Caps, Convert.ToUInt16(output.Caps));
            b &= this._modbus.SetUshort(value.Floor, Convert.ToUInt16(output.Floor));
            b &= this._modbus.SetUshort(value.CapsWarning, Convert.ToUInt16(output.CapsWarning));
            b &= this._modbus.SetUshort(value.FloorWarning, Convert.ToUInt16(output.FloorWarning));
        }
        catch
        {
        }

        return b;
    }

    bool IMini8Communicator.EnableChannel(byte channelIndex, Inhibit inhibit)
    {
        if (Checker.IsNull(_provider, _modbus, _addresses, _spilter))
            return false;

        if (!this._addresses!.TryGetValueNotNull(channelIndex, out ChannelAddress value))
            return false;

        return this._modbus!.SetUshort(value.Inhibit, (ushort)inhibit);
    }

    bool IMini8Communicator.EnableChannelAutoTune(byte channelIndex, bool isEnable)
    {
        if (Checker.IsNull(_provider, _modbus, _addresses, _spilter))
            return false;

        if (!this._addresses!.TryGetValueNotNull(channelIndex, out ChannelAddress value))
            return false;

        (ActiveTuneSet set, AutotuneActive active) = isEnable switch
        {
            true => (ActiveTuneSet.AutoTune, AutotuneActive.Active),
            false => (ActiveTuneSet.Running, AutotuneActive.OFF),
        };

        if (!this._modbus!.SetUshort(value.ActiveTuneSet, (ushort)set))
            return false;

        Thread.Sleep(_interval);

        if (!this._modbus!.SetUshort(value.ActiveAutoTune, (ushort)active))
            return false;

        return true;
    }

    bool IMini8Communicator.SelectPID(byte channelIndex, ActiveTuneSet activeTuneSet)
    {
        if (Checker.IsNull(_provider, _modbus, _addresses))
            return false;

        if (!this._addresses!.TryGetValueNotNull(channelIndex, out ChannelAddress value))
            return false;

        return this._modbus!.SetUshort(value.ActiveTuneSet, (ushort)activeTuneSet);
    }

    bool IMini8Communicator.Pause()
    {
        this._updateDataTimer?.Dispose();
        this._provider?.OnDisconnected(this._ip, this._port);
        return true;
    }

    bool IMini8Communicator.StartDataCollector()
    {
        StartDataCollection();
        this._provider?.OnConnected(this._ip, this._port);

        return true;
    }

    bool IMini8Communicator.Close()
    {
        this._updateDataTimer?.Dispose();
        this._modbus?.Close();
        this._provider = null;
        this._modbus = null;
        return true;
    }


    private bool StartDataCollection()
    {
        if (Checker.IsNull(_provider, _modbus, _addresses, _spilter))
            return false;

        _updateDataTimer?.Dispose();
        _updateDataTimer = new(GetMini8Output, true, 1000, 1000);
        return true;
    }

    private void GetMini8Output(object? requireNotify)
    {
        if (requireNotify is not bool notify)
            return;

        if (this._spilter is null || this._addresses is null)
            return;

        if (!this.TryGetMini8Data(out byte[]? data) || data is null || data.Length < _totalCount)
            return;

        this._spilter.Source = data;

        lock (_outputCache)
            foreach (ChannelAddress item in _addresses.Values)
            {
                Mini8Output output = _outputCache[item.ChannelIndex];
                output.CollectTime = DateTime.Now;
                GetMini8Input(output, item, this._spilter);
                if (!notify)
                    continue;
                output.ChannelIndex = item.ChannelIndex;
                this._provider?.ChannelInfoNotify(item.ChannelIndex, output);
            }
    }

    private readonly byte[] _cache = new byte[1000];
    private bool TryGetMini8Data(out byte[]? data)
    {
        data = null;
        if (this._modbus is null)
            return false;

        for (int index = 0; index < _totalCount; index += _groupeCount)
        {
            byte[]? result = this._modbus?.GetBuffer((ushort)(_startIndex + index), _groupeCount);

            if (result is null)
                return false;

            Array.Copy(result, 0, _cache, index * 2, result.Length);

            Thread.Sleep(_interval);
        }

        data = this._cache;

        return true;
    }

    private unsafe ushort BufferToUshort(byte[]? bytes)
    {
        if (bytes is null)
            return ushort.MaxValue;

        Span<byte> sb = bytes.AsSpan<byte>();
        sb.Reverse();

        fixed (byte* pByte = sb)
        {
            ushort* pResult = (ushort*)pByte;
            return *pResult;
        }
    }

    private static void GetMini8Input(Mini8Output ref_source, ChannelAddress address, DataSpilterByte spilter)
    {
        ref_source.PV = spilter.GetFloat(address.PV);
        ref_source.WorkingOutput = spilter.GetFloat(address.WorkingOutput);
        ref_source.AutoTuneStatus = (AutoTuneStatus)spilter.GetUshort(address.AutoTuneStatus);
        ref_source.AutoTune_P = spilter.GetFloat(address.AutoTune_P);
        ref_source.AutoTune_I = spilter.GetFloat(address.AutoTune_I);
        ref_source.AutoTune_D = spilter.GetFloat(address.AutoTune_D);
        ref_source.SetPoint = spilter.GetFloat(address.SetPoint);
        ref_source.ActiveTuneSet = (ActiveTuneSet)spilter.GetUshort(address.ActiveTuneSet);
        ref_source.Running_P = spilter.GetFloat(address.Running_P);
        ref_source.Running_I = spilter.GetFloat(address.Running_I);
        ref_source.Running_D = spilter.GetFloat(address.Running_D);
        ref_source.SetpointUpRate = spilter.GetFloat(address.SetpointUpRate);
        ref_source.SetpointDownRate = spilter.GetFloat(address.SetpointDownRate);
        ref_source.Inhibit = (Inhibit)spilter.GetUshort(address.Inhibit);
        ref_source.AutotuneActive = (AutotuneActive)spilter.GetUshort(address.ActiveAutoTune);
        ref_source.SensorBreakAlarm = address.ChannelIndex switch
        {
            <= 8 => (TcBorken)spilter.GetBit(address.SensorBreakAlarm1, address.ChannelIndex),
            _ => (TcBorken)spilter.GetBit(address.SensorBreakAlarm2, address.ChannelIndex - 8),
        };
    }

    void ITcpConnectNority.Connect(string ip, int port)
    {
        this._provider?.OnConnected(this._ip, this._port);
        StartDataCollection();
    }

    void ITcpConnectNority.DisConnect(string ip, int port)
    {
        this._updateDataTimer?.Dispose();
        this._provider?.OnDisconnected(this._ip, this._port);
    }
}


internal class DataSpilterByte(ushort startIndex)
{
    public byte[]? Source { get; set; }

    public unsafe float GetFloat(ushort address)
    {
        address -= startIndex;

        if (Source is null || Source.Length < address * 2 + 4)
            return -1f;

        Span<byte> buffer = Source.AsSpan<byte>().Slice(address * 2, 4);
        buffer.Reverse();

        fixed (byte* pByte = buffer)
        {
            float* pResult = (float*)pByte;
            return *pResult;
        }
    }

    public unsafe Half GetHalf(ushort address)
    {
        address -= startIndex;

        if (Source is null || Source.Length < address * 2 + 2)
            return Half.MaxValue;

        Span<byte> buffer = Source.AsSpan<byte>().Slice(address * 2, 2);
        buffer.Reverse();

        fixed (byte* pByte = buffer)
        {
            Half* pResult = (Half*)pByte;
            return *pResult;
        }
    }

    public unsafe ushort GetUshort(ushort address)
    {
        address -= startIndex;

        if (Source is null || Source.Length < address * 2 + 2)
            return ushort.MaxValue;

        Span<byte> buffer = Source.AsSpan<byte>().Slice(address * 2, 2);
        buffer.Reverse();

        fixed (byte* pByte = buffer)
        {
            ushort* pResult = (ushort*)pByte;
            return *pResult;
        }
    }

    public unsafe byte GetBit(ushort address, int index)
    {
        address -= startIndex;

        if (Source is null || Source.Length < address + 1)
            return byte.MaxValue;

        if (index > 8 || index < 0)
            return byte.MaxValue;

        return (byte)((Source[address * 2 + 1] >> (index - 1)) & 0b01);
    }
}