using ProtocalGeneral;

namespace FinsTcp;

public class Fins_Tcp : IDisposable
{
    private FinsTcpBase? _Service;
    private ITcpConnectNority? _tcpConnect;

    private string _IP = string.Empty;
    private int _port = 0;

    private Timer? _reconnectTimer;
    private Timer? _heartBeatTimer;
    private volatile ushort _heartBeatMissCount = 0;
    private ushort _maxHeartBeatMissCount = 5;
    private Func<bool>? _heartBeatCallBack;

    public bool Initialize(ITcpConnectNority? tcpConnect)
    {
        if (this._Service is not null)
            return false;

        this._Service = new FinsTcpBase();

        this._tcpConnect = tcpConnect;

        return true;
    }

    public bool Connect(string IP, int port, Func<bool> func, ushort heartBeatMissCount = 5, short heartBeatInterval = 1000, int sendTimeout = 1000, int receiveTimeout = 1000)
    {
        if (this._Service is null)
            return false;

        this._IP = IP;
        this._port = port;

        try
        {
            if (!this._Service.Link(IP, port, sendTimeout, receiveTimeout))
                return false;

            this._tcpConnect?.Connect(IP, port);
            this.StartHeartBeat(func, heartBeatMissCount, heartBeatInterval);
        }
        catch
        {
            return false;
        }

        return true;
    }

    private bool StartHeartBeat(Func<bool> func, ushort missCount = 5, short interval_ms = 1000)
    {
        if (this._heartBeatTimer is not null)
            return false;
        if (this._Service is null)
            return false;
        if (func is null)
            return false;
        this._maxHeartBeatMissCount = missCount;
        this._heartBeatCallBack = func;
        this._heartBeatTimer = new(HeatBeaterTimerCallBack, func, 0, interval_ms);
        return true;
    }

    private void HeatBeaterTimerCallBack(object? state)
    {
        if (this._heartBeatCallBack is null)
            return;
        if (this._heartBeatCallBack.Invoke())
            return;
        if (++_heartBeatMissCount <= this._maxHeartBeatMissCount)
            return;

        this._heartBeatTimer?.Dispose();
        this._tcpConnect?.DisConnect(this._IP, this._port);
        StartAutoReconnectTimer();
    }

    private void StartAutoReconnectTimer()
    {
        this._heartBeatTimer?.Dispose();
        this._heartBeatTimer = null;
        this._heartBeatMissCount = 0;
        this._reconnectTimer = new(ReconnectTimerCallBack, null, 0, 3000);
    }

    private void ReconnectTimerCallBack(object? state)
    {
        this._Service?.Close();
        this._Service = null;
        this._heartBeatTimer?.Dispose();
        this.Initialize(this._tcpConnect);
        if (!this.Connect(this._IP, this._port, this._heartBeatCallBack!, this._maxHeartBeatMissCount))
            return;
        this._reconnectTimer?.Dispose();
    }


    public bool GetData<T>(string address, out T? data)
    {
        data = default;

        if (this._Service is null)
            return false;

        try
        {
            switch (Type.GetTypeCode(typeof(T)))
            {
                case TypeCode.Boolean:
                    if (!this._Service.GetBitState(address, out short state))
                        return false;
                    data = (T)(object)state;
                    return true;
                case TypeCode.Int16:
                    if (!this._Service.ReadWord(address, out short s))
                        return false;
                    data = (T)(object)s;
                    return true;
                case TypeCode.Int32:
                    if (!this._Service.ReadInt32(address, out int i))
                        return false;
                    data = (T)(object)i;
                    return true;
                case TypeCode.Single:
                    if (!this._Service.ReadReal(address, out float f))
                        return false;
                    data = (T)(object)f;
                    return true;
                default:
                    return false;
            }

        }
        catch
        {
            return false;
        }
    }

    public bool SetData<T>(string address, T data) where T : new()
    {
        if (this._Service is null)
            return false;

        try
        {
            return data switch
            {
                bool b => this._Service.SetBitState(address, b ? BitState.ON : BitState.OFF),
                short s => this._Service.WriteWord(address, s),
                int i => this._Service.WriteInt32(address, i),
                float f => this._Service.WriteReal(address, f),
                _ => false
            };
        }
        catch
        {
            return false;
        }
    }

    #region Dispose
    private bool disposedValue;
    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                this._Service?.Close();
                this._Service = null;
                this._heartBeatTimer?.Dispose();
                this._reconnectTimer?.Dispose();
                this._tcpConnect?.DisConnect(this._IP, this._port);
                this._heartBeatMissCount = 0;
            }
            disposedValue = true;
        }
    }



    public void Dispose()
    {
        // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
    #endregion
}
