using System.Net.Sockets;
using System.Text.RegularExpressions;

namespace FinsTcp;
//This is a copy of a Open library POmronFinsTCP.Net
//I have modified tcpclient sendtimeout and receivetimeout time to make sure it can know connect status when device connect into a switch
//Please ignore all the warnings and Messages,I know the code is in a mess, but it is working.
//I will modified and optimize the code later - Zixuan
//Fix all warnings and Messages and make code more readable - 2025/06/10 Zixuan
public class FinsTcpBase : Tcp
{
    public byte PlcNode { get; private set; }
    public byte PcNode { get; private set; }

    //
    // Summary:
    //     Fins读写指令生成
    //
    // Parameters:
    //   rw:
    //     读写类型
    //
    //   mr:
    //     寄存器类型
    //
    //   mt:
    //     地址类型
    //
    //   ch:
    //     起始地址
    //
    //   offset:
    //     位地址：00-15,字地址则为00
    //
    //   cnt:
    //     地址个数,按位读写只能是1


    public bool Link(string rIP, int rPort = 9600, int sendTimeout = 1000, int receiveTimeout = 1000)
    {
        if (this._client is null)
            return false;

        _client.SendTimeout = sendTimeout;
        _client.ReceiveTimeout = receiveTimeout;
        _client.NoDelay = true;

        if (!_client.ConnectAsync(rIP, rPort).Wait(sendTimeout))
            return false;
        _stream = _client.GetStream();
        Thread.Sleep(10);
        if (!SendData(FinsClass.HandShakePack))
            return false;

        byte[] array = new byte[24];
        if (!ReceiveData(array))
            return false;

        if (array[15] != 0)
            return false;

        PcNode = array[19];
        PlcNode = array[23];
        return true;
    }

    public bool Close()
    {
        try
        {
            _stream?.Close();
            _client?.Close();
            return true;
        }
        catch
        {
            return false;
        }
    }

    public bool ReadWords(string mrch, short cnt, out short[]? reData)
    {
        reData = default;
        if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory))
            return false;
        return ReadWords(plcMemory, short.Parse(txtq), cnt, out reData);
    }

    public bool ReadWords(PlcMemory mr, short ch, short cnt, out short[] reData)
    {
        reData = new short[cnt];
        int num = 30 + cnt * 2;
        byte[] array = new byte[num];
        byte[] sd = FinsClass.FinsCmd(Operation.Read, mr, MemoryType.Word, ch, 0, cnt, PlcNode, PcNode);
        if (!SendData(sd))
            return false;

        if (!ReceiveData(array))
            return false;

        bool flag = array[11] switch
        {
            3 => ErrorCode.CheckHeadError(array[15]),
            _ => true
        };

        if (!flag)
            return false;

        if (!ErrorCode.CheckEndCode(array[28], array[29]))
            return false;

        for (int i = 0; i < cnt; i++)
        {
            byte[] value = [array[30 + i * 2 + 1], array[30 + i * 2]];
            reData[i] = BitConverter.ToInt16(value, 0);
        }

        return true;
    }

    public bool ReadWord(string mrch, out short reData)
    {
        reData = default;
        if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory))
            return false;
        return ReadWord(plcMemory, short.Parse(txtq), out reData);
    }

    public bool ReadWord(PlcMemory mr, short ch, out short reData)
    {
        reData = 0;
        if (!ReadWords(mr, ch, 1, out var reData2))
            return false;

        reData = reData2[0];
        return true;
    }

    public bool WriteWords(string mrch, short cnt, short[] inData)
    {
        if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory))
            return false;
        return WriteWords(plcMemory, short.Parse(txtq), cnt, inData);
    }

    public bool WriteWords(PlcMemory mr, short ch, short cnt, short[] inData)
    {
        byte[] array = new byte[30];
        byte[] array2 = FinsClass.FinsCmd(Operation.Write, mr, MemoryType.Word, ch, 0, cnt, PlcNode, PcNode);
        byte[] array3 = new byte[cnt * 2];
        for (int i = 0; i < cnt; i++)
        {
            byte[] bytes = BitConverter.GetBytes(inData[i]);
            array3[i * 2] = bytes[1];
            array3[i * 2 + 1] = bytes[0];
        }

        byte[] array4 = new byte[cnt * 2 + 34];
        array2.CopyTo(array4, 0);
        array3.CopyTo(array4, 34);
        if (!SendData(array4))
            return false;
        if (!ReceiveData(array))
            return false;
        bool flag = array[11] switch
        {
            3 => ErrorCode.CheckHeadError(array[15]),
            _ => true
        };

        if (!flag)
            return false;

        return ErrorCode.CheckEndCode(array[28], array[29]);
    }

    public bool WriteWord(string mrch, short inData)
    {
        if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory))
            return false;
        return WriteWord(plcMemory, short.Parse(txtq), inData);
    }

    public bool WriteWord(PlcMemory mr, short ch, short inData)
    {
        short[] inData2 = [inData];
        if (!WriteWords(mr, ch, 1, inData2))
            return false;

        return true;
    }

    public bool GetBitState(string mrch, out short bs)
    {
        bs = default;
        if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory))
            return false;
        return GetBitState(plcMemory, txtq, out bs);
    }

    public bool GetBitStates(string mrch, out bool[]? bs, short cnt = 1)
    {
        bs = default;
        if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory))
            return false;
        return GetBitStates(plcMemory, txtq, out bs, cnt);
    }

    public bool GetBitStates(PlcMemory mr, string ch, out bool[] bs, short cnt = 1)
    {
        bs = new bool[cnt];
        byte[] array = new byte[30 + cnt];
        short ch2 = short.Parse(ch.Split(['.'])[0]);
        short offset = short.Parse(ch.Split(['.'])[1]);
        byte[] command = FinsClass.FinsCmd(Operation.Read, mr, MemoryType.Bit, ch2, offset, cnt, PlcNode, PcNode);
        if (!SendData(command))
            return false;
        if (!ReceiveData(array))
            return false;
        bool flag = array[11] switch
        {
            3 => ErrorCode.CheckHeadError(array[15]),
            _ => true
        };

        if (!flag)
            return false;
        if (!ErrorCode.CheckEndCode(array[28], array[29]))
            return false;
        for (int i = 0; i < cnt; i++)
            bs[i] = array[30 + i] == 1;

        return true;
    }

    public bool GetBitState(PlcMemory mr, string ch, out short bs)
    {
        bs = 0;
        byte[] array = new byte[31];
        string[] spilts = ch.Split('.');
        short ch2 = short.Parse(spilts[0]);
        short offset = short.Parse(spilts[1]);
        byte[] sd = FinsClass.FinsCmd(Operation.Read, mr, MemoryType.Bit, ch2, offset, 1, PlcNode, PcNode);
        if (!SendData(sd))
            return false;

        if (!ReceiveData(array))
            return false;

        bool flag = array[11] switch
        {
            3 => ErrorCode.CheckHeadError(array[15]),
            _ => true
        };

        if (!flag)
            return false;

        if (!ErrorCode.CheckEndCode(array[28], array[29]))
            return false;

        bs = array[30];
        return true;
    }

    public bool SetBitState(string mrch, BitState bs)
    {
        if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory))
            return false;
        return SetBitState(plcMemory, txtq, bs);
    }

    public bool SetBitState(PlcMemory mr, string ch, BitState bs)
    {
        byte[] array = new byte[30];
        string[] spilts = ch.Split('.');
        short ch2 = short.Parse(spilts[0]);
        short offset = short.Parse(spilts[1]);
        byte[] array2 = FinsClass.FinsCmd(Operation.Write, mr, MemoryType.Bit, ch2, offset, 1, PlcNode, PcNode);
        byte[] array3 = new byte[35];
        array2.CopyTo(array3, 0);
        array3[34] = (byte)bs;
        if (!SendData(array3))
            return false;
        if (!ReceiveData(array))
            return false;


        bool flag = array[11] switch
        {
            3 => ErrorCode.CheckHeadError(array[15]),
            _ => true
        };

        if (!flag)
            return false;

        if (!ErrorCode.CheckEndCode(array[28], array[29]))
            return false;

        return true;
    }

    public bool ReadReal(string mrch, out float reData)
    {
        reData = default;
        if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory))
            return false;
        return ReadReal(plcMemory, short.Parse(txtq), out reData);
    }

    public bool ReadReal(PlcMemory mr, short ch, out float reData)
    {
        reData = 0f;
        int num = 34;
        byte[] array = new byte[num];
        byte[] sd = FinsClass.FinsCmd(Operation.Read, mr, MemoryType.Word, ch, 0, 2, PlcNode, PcNode);
        if (!SendData(sd))
            return false;
        if (!ReceiveData(array))
            return false;

        bool flag = array[11] switch
        {
            3 => ErrorCode.CheckHeadError(array[15]),
            _ => true
        };

        if (!flag)
            return false;
        if (!ErrorCode.CheckEndCode(array[28], array[29]))
            return false;
        byte[] value = [array[31], array[30], array[33], array[32]];
        reData = BitConverter.ToSingle(value, 0);
        return true;
    }

    public bool WriteReal(string mrch, float reData)
    {
        if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory))
            return false;
        return WriteReal(plcMemory, short.Parse(txtq), reData);
    }

    public bool WriteReal(PlcMemory mr, short ch, float reData)
    {
        if (BitConverter.GetBytes(reData) is not byte[] bytes)
            return false;

        short[] array = new short[2];
        array[0] = BitConverter.ToInt16(bytes, 0);

        if (bytes.Length > 2)
            array[1] = BitConverter.ToInt16(bytes, 2);

        return WriteWords(mr, ch, 2, array);
    }

    public bool ReadInt32(string mrch, out int reData)
    {
        reData = default;
        if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory))
            return false;
        return ReadInt32(plcMemory, short.Parse(txtq), out reData);
    }

    public bool ReadInt32(PlcMemory mr, short ch, out int reData)
    {
        reData = 0;
        int num = 34;
        byte[] array = new byte[num];
        byte[] sd = FinsClass.FinsCmd(Operation.Read, mr, MemoryType.Word, ch, 0, 2, PlcNode, PcNode);
        if (!SendData(sd))
            return false;

        if (!ReceiveData(array))
            return false;

        bool flag = array[11] switch
        {
            3 => ErrorCode.CheckHeadError(array[15]),
            _ => true
        };

        if (!flag)
            return false;
        if (!ErrorCode.CheckEndCode(array[28], array[29]))
            return false;
        byte[] value = [array[31], array[30], array[33], array[32]];
        reData = BitConverter.ToInt32(value, 0);
        return true;
    }

    public bool WriteInt32(string mrch, int reData)
    {
        if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory))
            return false;
        return WriteInt32(plcMemory, short.Parse(txtq), reData);
    }

    public bool WriteInt32(PlcMemory mr, short ch, int reData)
    {
        if (BitConverter.GetBytes(reData) is not byte[] bytes)
            return false;

        short[] array = new short[2];
        array[0] = BitConverter.ToInt16(bytes, 0);

        if (bytes.Length > 2)
            array[1] = BitConverter.ToInt16(bytes, 2);

        return WriteWords(mr, ch, 2, array);
    }
}

public class Tcp
{
    public Tcp()
    {
        this._client = new();
    }
    protected readonly TcpClient _client;
    protected NetworkStream? _stream;

    protected bool SendData(byte[] sd)
    {
        if (_stream == null)
            return false;

        try
        {
            _stream.Write(sd, 0, sd.Length);
            return true;
        }
        catch
        {
            return false;
        }
    }
    protected bool ReceiveData(byte[] rd)
    {
        if (_stream == null)
            return false;

        try
        {
            int num = 0;
            do
            {
                int num2 = _stream.Read(rd, num, rd.Length - num);
                if (num2 == 0)
                    return false;

                num += num2;
            }
            while (num < rd.Length);
            return true;
        }
        catch
        {
            return false;
        }
    }
}

internal class ConvertClass
{
    //
    // Summary:
    //     得到枚举值
    //
    // Parameters:
    //   txt:
    //     如：D100,W100.1
    //
    //   txtq:100.1
    internal static bool GetPlcMemory(string txt, out string txtq, out PlcMemory plcMemory)
    {
        txtq = string.Empty;
        char c = txt.Trim().ToUpper().FirstOrDefault();
        plcMemory = c switch
        {
            'D' => PlcMemory.DM,
            'W' => PlcMemory.WR,
            'H' => PlcMemory.HR,
            'A' => PlcMemory.AR,
            'C' => PlcMemory.CNT,
            'I' => PlcMemory.CIO,
            'T' => PlcMemory.TIM,
            _ => PlcMemory.Undefined,
        };

        if (plcMemory == PlcMemory.Undefined)
            return false;
        txtq = Regex.Replace(txt, "[^0-9.]", "");
        return true;
    }


}

internal class ErrorCode
{
    //
    // Summary:
    //     （若返回的头指令为3）检查命令头中的错误代码
    //
    // Parameters:
    //   Code:
    //     错误代码
    //
    // Returns:
    //     指示程序是否可以继续进行
    internal static bool CheckHeadError(byte Code)
    {
        Console.WriteLine($"Error Code {Code}");
        return Code == 0;
    }

    //
    // Summary:
    //     检查命令帧中的EndCode
    //
    // Parameters:
    //   Main:
    //     主码
    //
    //   Sub:
    //     副码
    //
    // Returns:
    //     指示程序是否可以继续进行
    internal static bool CheckEndCode(byte Main, byte Sub)
    {
        //Totally dont understand what's doing here, Replace with the code below
        return Main == 0 && (Sub == 64 || Sub == 0);
        //switch (Main)
        //{
        //    case 0:
        //        switch (Sub)
        //        {
        //            case 0:
        //            case 64:
        //                return true;
        //            case 1:
        //                return false;
        //        }

        //        break;
        //    case 1:
        //        switch (Sub)
        //        {
        //            case 1:
        //            case 2:
        //            case 3:
        //            case 4:
        //            case 5:
        //            case 6:
        //                return false;
        //        }

        //        break;
        //    case 2:
        //        switch (Sub)
        //        {
        //            case 1:
        //            case 2:
        //            case 3:
        //            case 4:
        //            case 5:
        //                return false;
        //        }

        //        break;
        //    case 3:
        //        switch (Sub)
        //        {
        //            case 1:
        //            case 2:
        //            case 3:
        //            case 4:
        //                return false;
        //        }

        //        break;
        //    case 4:
        //        switch (Sub)
        //        {
        //            case 1:
        //            case 2:
        //                return false;
        //        }

        //        break;
        //    case 5:
        //        switch (Sub)
        //        {
        //            case 1:
        //            case 2:
        //            case 3:
        //            case 4:
        //                return false;
        //        }

        //        break;
        //    case 16:
        //        switch (Sub)
        //        {
        //            case 1:
        //            case 2:
        //            case 3:
        //            case 4:
        //            case 5:
        //                return false;
        //        }

        //        break;
        //    case 17:
        //        switch (Sub)
        //        {
        //            case 1:
        //            case 2:
        //            case 3:
        //            case 4:
        //            case 6:
        //            case 9:
        //            case 10:
        //            case 11:
        //            case 12:
        //                return false;
        //        }

        //        break;
        //    case 32:
        //        switch (Sub)
        //        {
        //            case 2:
        //            case 3:
        //            case 4:
        //            case 5:
        //            case 6:
        //            case 7:
        //                return false;
        //        }

        //        break;
        //    case 33:
        //        switch (Sub)
        //        {
        //            case 1:
        //            case 2:
        //            case 3:
        //            case 5:
        //            case 6:
        //            case 7:
        //            case 8:
        //                return false;
        //        }

        //        break;
        //    case 34:
        //        switch (Sub)
        //        {
        //            case 1:
        //            case 2:
        //            case 3:
        //            case 4:
        //            case 5:
        //            case 6:
        //            case 7:
        //            case 8:
        //                return false;
        //        }

        //        break;
        //    case 35:
        //        switch (Sub)
        //        {
        //            case 1:
        //            case 2:
        //            case 3:
        //                return false;
        //        }

        //        break;
        //    case 36:
        //        {
        //            byte b5 = Sub;
        //            byte b6 = b5;
        //            if (b6 != 1)
        //            {
        //                break;
        //            }

        //            return false;
        //        }
        //    case 37:
        //        switch (Sub)
        //        {
        //            case 2:
        //            case 3:
        //            case 4:
        //            case 5:
        //            case 6:
        //            case 7:
        //            case 9:
        //            case 10:
        //            case 13:
        //            case 15:
        //            case 16:
        //                return false;
        //        }

        //        break;
        //    case 38:
        //        switch (Sub)
        //        {
        //            case 1:
        //            case 2:
        //            case 4:
        //            case 5:
        //            case 6:
        //            case 7:
        //            case 8:
        //            case 9:
        //            case 10:
        //            case 11:
        //                return false;
        //        }

        //        break;
        //    case 48:
        //        {
        //            byte b3 = Sub;
        //            byte b4 = b3;
        //            if (b4 != 1)
        //            {
        //                break;
        //            }

        //            return false;
        //        }
        //    case 64:
        //        {
        //            byte b = Sub;
        //            byte b2 = b;
        //            if (b2 != 1)
        //            {
        //                break;
        //            }

        //            return false;
        //        }
        //}

        //return false;
    }
}

internal class FinsClass
{
    //
    // Summary:
    //     获取内存区码
    //
    // Parameters:
    //   mr:
    //     寄存器类型
    //
    //   mt:
    //     地址类型
    internal static byte GetMemoryCode(PlcMemory mr, MemoryType mt)
    {
        if (mt == MemoryType.Bit)
        {
            return mr switch
            {
                PlcMemory.CIO => 48,
                PlcMemory.WR => 49,
                PlcMemory.HR => 50,
                PlcMemory.AR => 51,
                PlcMemory.DM => 2,
                PlcMemory.CNT or PlcMemory.TIM => 9,
                _ => 0,
            };
        }

        return mr switch
        {
            PlcMemory.CIO => 176,
            PlcMemory.WR => 177,
            PlcMemory.HR => 178,
            PlcMemory.AR => 179,
            PlcMemory.DM => 130,
            PlcMemory.CNT or PlcMemory.TIM => 137,
            _ => 0,
        };
    }
    internal static byte[] FinsCmd(Operation rw, PlcMemory mr, MemoryType mt, short ch, short offset, short cnt, byte PlcNode, byte PcNode)
    {
        byte[] array =
         [
            70, 73, 78, 83, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0
         ];

        if (rw == Operation.Read)
        {
            array[6] = 0;
            array[7] = 26;
        }
        else if (mt == MemoryType.Word)
        {
            array[6] = (byte)((cnt * 2 + 26) / 256);
            array[7] = (byte)((cnt * 2 + 26) % 256);
        }
        else
        {
            array[6] = 0;
            array[7] = 27;
        }

        array[11] = 2;
        array[16] = 128;
        array[18] = 2;
        array[20] = PlcNode;
        array[23] = PcNode;
        array[25] = byte.MaxValue;

        if (rw == Operation.Read)
        {
            array[26] = 1;
            array[27] = 1;
        }
        else
        {
            array[26] = 1;
            array[27] = 2;
        }

        array[28] = FinsClass.GetMemoryCode(mr, mt);
        if (mr == PlcMemory.CNT)
        {
            array[29] = (byte)(ch / 256 + 128);
            array[30] = (byte)(ch % 256);
        }
        else
        {
            array[29] = (byte)(ch / 256);
            array[30] = (byte)(ch % 256);
            array[31] = (byte)offset;
        }

        array[32] = (byte)(cnt / 256);
        array[33] = (byte)(cnt % 256);
        return array;
    }

    public static byte[] Header = [0x46, 0x49, 0x4E, 0x53];

    public static byte[] HandShakePack =
        [
            0x46, 0x49, 0x4E, 0x53,     //Header
            0x00, 0x00, 0x00, 0x0c,     //Length
            0x00, 0x00, 0x00, 0x00,     //Command
            0x00, 0x00, 0x00, 0x00,     //ErrorCode
            0x00, 0x00, 0x00, 0x00      //Client Node
        ];
}

public enum PlcMemory
{
    CIO,
    WR,
    HR,
    AR,
    DM,
    CNT,
    TIM,
    Undefined,
}

public enum Operation
{
    Read,
    Write
}

public enum MemoryType
{
    Bit,
    Word
}

public enum BitState
{
    ON = 1,
    OFF = 0
}