using System.Net;
using DotNext;

namespace Peripherals.DebuggerClient;

/// <summary>
/// FPGA调试器的内存地址映射常量
/// </summary>
class DebuggerAddr
{
    /// <summary>
    /// 触发器启动地址
    /// </summary>
    public const UInt32 Start = 0x5100_0000;

    /// <summary>
    /// 刷新操作地址
    /// </summary>
    public const UInt32 Fresh = 0x5100_FFFF;

    /// <summary>
    /// 信号标志读取地址
    /// </summary>
    public const UInt32 Signal = 0x5000_0001;

    /// <summary>
    /// 数据读取基地址
    /// </summary>
    public const UInt32 Data = 0x5100_0000;

    /// <summary>
    /// 捕获模式设置地址
    /// </summary>
    public const UInt32 Mode = 0x5101_0000;
}

/// <summary>
/// FPGA调试器命令常量
/// </summary>
class DebuggerCmd
{
    /// <summary>
    /// 启动触发器命令
    /// </summary>
    public const UInt32 Start = 0xFFFF_FFFF;

    /// <summary>
    /// 刷新命令
    /// </summary>
    public const UInt32 Fresh = 0x0000_0000;

    /// <summary>
    /// 清除信号标志命令
    /// </summary>
    public const UInt32 ClearSignal = 0xFFFF_FFFF;
}

/// <summary>
/// 信号捕获模式枚举
/// </summary>
public enum CaptureMode : byte
{
    /// <summary>
    /// 无捕获模式
    /// </summary>
    None = 0,
    /// <summary>
    /// 低电平触发模式
    /// </summary>
    Logic0 = 1,
    /// <summary>
    /// 高电平触发模式
    /// </summary>
    Logic1 = 2,
    /// <summary>
    /// 上升沿触发模式
    /// </summary>
    Rise = 3,
    /// <summary>
    /// 下降沿触发模式
    /// </summary>
    Fall = 4,
}

/// <summary>
/// FPGA调试器客户端，用于通过UDP协议与FPGA调试器进行通信
/// </summary>
public class DebuggerClient
{
    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

    readonly int timeout = 2000;
    readonly int taskID;
    readonly int port;
    readonly string address;
    private IPEndPoint ep;

    private UInt32 captureDataAddr = 0x5100_0000;

    /// <summary>
    /// 初始化FPGA调试器客户端
    /// </summary>
    /// <param name="address">FPGA设备的IP地址</param>
    /// <param name="port">通信端口号</param>
    /// <param name="taskID">任务标识符</param>
    /// <param name="timeout">通信超时时间（毫秒），默认2000ms</param>
    /// <exception cref="ArgumentException">当timeout为负数时抛出</exception>
    public DebuggerClient(string address, int port, int taskID, int timeout = 2000)
    {
        if (timeout < 0)
            throw new ArgumentException("Timeout couldn't be negative", nameof(timeout));
        this.address = address;
        this.taskID = taskID;
        this.port = port;
        this.ep = new IPEndPoint(IPAddress.Parse(address), port);
        this.timeout = timeout;
    }

    /// <summary>
    /// 设置信号捕获模式
    /// </summary>
    /// <param name="wireNum">要设置的线</param>
    /// <param name="mode">要设置的捕获模式</param>
    /// <returns>操作结果，成功返回true，失败返回错误信息</returns>
    public async ValueTask<Result<bool>> SetMode(UInt32 wireNum, CaptureMode mode)
    {
        if (wireNum > 512)
        {
            return new(new ArgumentException($"Wire Num can't be over 512, but receive num: {wireNum}"));
        }

        UInt32 data = ((UInt32)mode);
        var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Mode + wireNum, data, this.timeout);
        if (!ret.IsSuccessful)
        {
            logger.Error($"Failed to set mode: {ret.Error}");
            return new(ret.Error);
        }
        if (!ret.Value)
        {
            logger.Error("WriteAddr to SetMode returned false");
            return new(new Exception("Failed to set mode"));
        }
        return true;
    }

    /// <summary>
    /// 启动信号触发器开始捕获
    /// </summary>
    /// <returns>操作结果，成功返回true，失败返回错误信息</returns>
    public async ValueTask<Result<bool>> StartTrigger()
    {
        var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Start, DebuggerCmd.Start, this.timeout);
        if (!ret.IsSuccessful)
        {
            logger.Error($"Failed to start trigger: {ret.Error}");
            return new(ret.Error);
        }
        if (!ret.Value)
        {
            logger.Error("WriteAddr to StartTrigger returned false");
            return new(new Exception("Failed to start trigger"));
        }
        return true;
    }

    /// <summary>
    /// 读取触发器状态标志
    /// </summary>
    /// <returns>操作结果，成功返回状态标志字节，失败返回错误信息</returns>
    public async ValueTask<Result<byte>> ReadFlag()
    {
        var ret = await UDPClientPool.ReadAddrByte(this.ep, this.taskID, DebuggerAddr.Signal, this.timeout);
        if (!ret.IsSuccessful)
        {
            logger.Error($"Failed to read flag: {ret.Error}");
            return new(ret.Error);
        }
        if (ret.Value.Options.Data == null || ret.Value.Options.Data.Length < 1)
        {
            logger.Error("ReadAddr returned invalid data for flag");
            return new(new Exception("Failed to read flag"));
        }
        return ret.Value.Options.Data[3];
    }

    /// <summary>
    /// 清除触发器状态标志
    /// </summary>
    /// <returns>操作结果，成功返回true，失败返回错误信息</returns>
    public async ValueTask<Result<bool>> ClearFlag()
    {
        var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Signal, DebuggerCmd.ClearSignal, this.timeout);
        if (!ret.IsSuccessful)
        {
            logger.Error($"Failed to clear flag: {ret.Error}");
            return new(ret.Error);
        }
        if (!ret.Value)
        {
            logger.Error("WriteAddr to ClearFlag returned false");
            return new(new Exception("Failed to clear flag"));
        }
        return true;
    }

    /// <summary>
    /// 从指定偏移地址读取捕获的数据
    /// </summary>
    /// <param name="portNum">Port数量</param>
    /// <returns>操作结果，成功返回捕获数据，失败返回错误信息</returns>
    public async ValueTask<Result<byte[]>> ReadData(UInt32 portNum)
    {
        var captureData = new byte[1024 * 4 * portNum];
        {
            var ret = await UDPClientPool.ReadAddr4Bytes(this.ep, this.taskID, this.captureDataAddr, captureData.Length / 4, this.timeout);
            if (!ret.IsSuccessful)
            {
                logger.Error($"Failed to read data: {ret.Error}");
                return new(ret.Error);
            }

            if (ret.Value.Length != captureData.Length)
            {
                logger.Error($"Receive capture data length should be {captureData.Length} instead of {ret.Value.Length}");
                return new(new Exception($"Receive capture data length should be {captureData.Length} instead of {ret.Value.Length}"));
            }

            Buffer.BlockCopy(ret.Value, 0, captureData, 0, captureData.Length);
        }

        return captureData;
    }

    /// <summary>
    /// 刷新调试器状态，重置内部状态机
    /// </summary>
    /// <returns>操作结果，成功返回true，失败返回错误信息</returns>
    public async ValueTask<Result<bool>> Refresh()
    {
        var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Fresh, DebuggerCmd.Fresh, this.timeout);
        if (!ret.IsSuccessful)
        {
            logger.Error($"Failed to refresh: {ret.Error}");
            return new(ret.Error);
        }
        if (!ret.Value)
        {
            logger.Error("WriteAddr to Refresh returned false");
            return new(new Exception("Failed to refresh"));
        }
        return true;
    }
}
