using System.Net;
using DotNext;

namespace DDSClient;

static class DDSAddr
{
    /*地址定义：（以2路输出，4波形存储为例）
    00    R/W   CHANNEL0        wave_sel
    01    R/W   CHANNEL0 STORE0 freq_ctrl
    02    R/W   CHANNEL0 STORE1 freq_ctrl
    03    R/W   CHANNEL0 STORE2 freq_ctrl
    04    R/W   CHANNEL0 STORE3 freq_ctrl
    05    R/W   CHANNEL0 STORE0 phase_ctrl
    06    R/W   CHANNEL0 STORE1 phase_ctrl
    07    R/W   CHANNEL0 STORE2 phase_ctrl
    08    R/W   CHANNEL0 STORE3 phase_ctrl
    09    R/W   CHANNEL0        dds_wr_enable
    0A     WO   CHANNEL0        data

    10    R/W   CHANNEL1        wave_sel
    11    R/W   CHANNEL1 STORE0 freq_ctrl
    12    R/W   CHANNEL1 STORE1 freq_ctrl
    13    R/W   CHANNEL1 STORE2 freq_ctrl
    14    R/W   CHANNEL1 STORE3 freq_ctrl
    15    R/W   CHANNEL1 STORE0 phase_ctrl
    16    R/W   CHANNEL1 STORE1 phase_ctrl
    17    R/W   CHANNEL1 STORE2 phase_ctrl
    18    R/W   CHANNEL1 STORE3 phase_ctrl
    19    R/W   CHANNEL1        dds_wr_enable
    1A     WO   CHANNEL1        data
    */
    public const UInt32 Base = 0x4000_0000;
    public static readonly ChannelAddr[] Channel = { new ChannelAddr(0x00), new ChannelAddr(0x10) };

    public class ChannelAddr
    {
        private readonly UInt32 Offset;
        public readonly UInt32 WaveSelect;
        public readonly UInt32[] FreqCtrl;
        public readonly UInt32[] PhaseCtrl;
        public readonly UInt32 WriteEnable;
        public readonly UInt32 WriteData;

        public ChannelAddr(UInt32 offset)
        {
            this.Offset = offset;
            this.WaveSelect = Base + Offset + 0x00;
            this.FreqCtrl = new UInt32[]
            {
                Base + offset + 0x01,
                Base + offset + 0x02,
                Base + offset + 0x03,
                Base + offset + 0x04
            };
            this.PhaseCtrl = new UInt32[] {
                Base + Offset + 0x05,
                Base + Offset + 0x06,
                Base + Offset + 0x07,
                Base + Offset + 0x08
            };
            this.WriteEnable = Base + Offset + 0x09;
            this.WriteData = Base + Offset + 0x0A;
        }
    }
}

/// <summary>
/// [TODO:description]
/// </summary>
public class DDS
{
    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

    readonly int timeout = 2000;

    readonly int port;
    readonly string address;
    private IPEndPoint ep;

    /// <summary>
    /// [TODO:description]
    /// </summary>
    /// <param name="address">[TODO:parameter]</param>
    /// <param name="port">[TODO:parameter]</param>
    /// <param name="timeout">[TODO:parameter]</param>
    /// <returns>[TODO:return]</returns>
    public DDS(string address, int port, int timeout = 2000)
    {
        if (timeout < 0)
            throw new ArgumentException("Timeout couldn't be negative", nameof(timeout));
        this.address = address;
        this.port = port;
        this.ep = new IPEndPoint(IPAddress.Parse(address), port);
        this.timeout = timeout;
    }

    /// <summary>
    /// [TODO:description]
    /// </summary>
    /// <param name="channelNum">[TODO:parameter]</param>
    /// <param name="waveNum">[TODO:parameter]</param>
    /// <returns>[TODO:return]</returns>
    public async ValueTask<Result<bool>> SetWaveNum(int channelNum, int waveNum)
    {
        if (channelNum < 0 || channelNum > 1) return new(new ArgumentException(
            $"Channel number should be 0 ~ 1 instead of {channelNum}", nameof(channelNum)));
        if (waveNum < 0 || waveNum > 3) return new(new ArgumentException(
            $"Wave number should be 0 ~ 3 instead of {waveNum}", nameof(waveNum)));

        await MsgBus.UDPServer.ClearUDPData(this.address);
        logger.Trace("Clear udp data finished");

        var ret = await UDPClientPool.WriteAddr(
                this.ep, DDSAddr.Channel[channelNum].WaveSelect, (UInt32)waveNum, this.timeout);
        if (!ret.IsSuccessful)
            return new(ret.Error);
        return ret.Value;
    }

    /// <summary>
    /// [TODO:description]
    /// </summary>
    /// <param name="channelNum">[TODO:parameter]</param>
    /// <param name="waveNum">[TODO:parameter]</param>
    /// <param name="step">[TODO:parameter]</param>
    /// <returns>[TODO:return]</returns>
    public async ValueTask<Result<bool>> SetFreq(int channelNum, int waveNum, UInt32 step)
    {
        if (channelNum < 0 || channelNum > 1) return new(new ArgumentException(
            $"Channel number should be 0 ~ 1 instead of {channelNum}", nameof(channelNum)));
        if (waveNum < 0 || waveNum > 3) return new(new ArgumentException(
            $"Wave number should be 0 ~ 3 instead of {waveNum}", nameof(waveNum)));

        await MsgBus.UDPServer.ClearUDPData(this.address);
        logger.Trace("Clear udp data finished");

        var ret = await UDPClientPool.WriteAddr(
                this.ep, DDSAddr.Channel[channelNum].FreqCtrl[waveNum], step, this.timeout);
        if (!ret.IsSuccessful)
            return new(ret.Error);
        return ret.Value;
    }

    /// <summary>
    /// [TODO:description]
    /// </summary>
    /// <param name="channelNum">[TODO:parameter]</param>
    /// <param name="waveNum">[TODO:parameter]</param>
    /// <param name="phase">[TODO:parameter]</param>
    /// <returns>[TODO:return]</returns>
    public async ValueTask<Result<bool>> SetPhase(int channelNum, int waveNum, int phase)
    {
        if (channelNum < 0 || channelNum > 1) return new(new ArgumentException(
            $"Channel number should be 0 ~ 1 instead of {channelNum}", nameof(channelNum)));
        if (waveNum < 0 || waveNum > 3) return new(new ArgumentException(
            $"Wave number should be 0 ~ 3 instead of {waveNum}", nameof(waveNum)));
        if (phase < 0 || phase > 4096) return new(new ArgumentException(
            $"Phase should be 0 ~ 4096 instead of {phase}", nameof(phase)));

        await MsgBus.UDPServer.ClearUDPData(this.address);
        logger.Trace("Clear udp data finished");

        var ret = await UDPClientPool.WriteAddr(
                this.ep, DDSAddr.Channel[channelNum].PhaseCtrl[waveNum], (UInt32)phase, this.timeout);
        if (!ret.IsSuccessful)
            return new(ret.Error);
        return ret.Value;
    }
}
