using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace AvaloniaApplication1.ViewModels
{
    public class ModBusMaster
    {
        public byte slaveId { get; set; } = 0x01;           // Slave ID (your ESP slave ID)
                                                            //byte functionCode = 0x05;  // Function code for Write Single Coil
                                                            //byte coilAddressHigh = 0x00;  // Coil address (MSB)
                                                            //byte coilAddressLow = 0x00;   // Coil address (LSB)
                                                            //byte coilValueHigh = 0x00;    // Coil value (0xFF00 to turn on, 0x0000 to turn off)
                                                            //byte coilValueLow = 0x00;

        // Function to read coils
        public async Task<bool[]> ReadCoils(SerialPort port, ushort startAddress, ushort numberOfCoils)
        {
            try
            {
                byte functionCode = 0x01;  // Function code for Read Coils
                byte[] frame = new byte[6];  // Request frame size (without CRC)

                // Construct the request frame
                frame[0] = slaveId;  // Slave address
                frame[1] = functionCode;  // Function code (0x01 for Read Coils)
                frame[2] = (byte)(startAddress >> 8);  // Start address high byte
                frame[3] = (byte)(startAddress & 0xFF);  // Start address low byte
                frame[4] = (byte)(numberOfCoils >> 8);  // Number of coils high byte
                frame[5] = (byte)(numberOfCoils & 0xFF);  // Number of coils low byte

                // Calculate CRC and append it to the frame
                byte[] crc = CalculateCRC(frame);
                byte[] fullFrame = new byte[frame.Length + crc.Length];
                Array.Copy(frame, fullFrame, frame.Length);
                Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length);

                // Send the frame over the serial port
                port.DiscardOutBuffer();
                port.DiscardInBuffer();

                port.Write(fullFrame, 0, fullFrame.Length);
                Thread.Sleep(500);
                Console.WriteLine("Read Coils request sent.");

                // Buffer for the response (modbus slave response size can vary)
                byte[] response = new byte[256];
                int bytesRead = port.Read(response, 0, response.Length);
                Console.WriteLine("Response Coils sent.");


                // Ensure that the response is valid (check slave address and function code)
                if (response[0] != slaveId || response[1] != functionCode)
                {
                    Console.WriteLine("Invalid response or error in slave response.");
                    return null;
                }

                // Calculate the number of bytes based on the number of coils
                int expectedByteCount = (numberOfCoils + 7) / 8;
                if (response[2] != expectedByteCount)
                {
                    Console.WriteLine("Incorrect byte count in the response.");
                    return null;
                }

                // The response bytes are in bit form, and we need to extract them
                bool[] coilStates = new bool[numberOfCoils];  // Array to store the coil states
                int byteIndex = 3;  // Start reading from byte 3 (after slave ID and function code)
                byte coilByte = response[byteIndex];  // The byte representing the coil states

                // Loop over all coils and check the corresponding bit
                for (int coilIndex = 0; coilIndex < numberOfCoils; coilIndex++)
                {
                    // Check the state of the coil by checking the corresponding bit in coilByte
                    coilStates[coilIndex] = coilByte == 1 ? true : false; /*(coilByte & (1 << (7 - coilIndex))) != 0;*/
                }
                if (coilStates==null)
                {
                    Console.WriteLine("status wael: null");

                }
                else
                {
                    Console.WriteLine($"status: {coilStates[0]}");

                }

                return coilStates;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"error wael: {ex.Message}");
                return null;
            }
         
        }
       

        // Function to write a single coil
        public async Task<byte[]> WriteSingleCoil(ushort coilAddress, bool state)
            {
                byte functionCode = 0x05;
                byte[] frame = new byte[6];

                frame[0] = slaveId;  // Slave address
                frame[1] = functionCode;  // Function code
                frame[2] = (byte)(coilAddress >> 8);  // Coil address high byte
                frame[3] = (byte)(coilAddress & 0xFF);  // Coil address low byte
                frame[4] = (byte)(state ? 0xFF : 0x00);  // Coil value (ON=0xFF, OFF=0x00)  
                frame[5] = (byte)(0x00);  // Coil value (ON=0xFF, OFF=0x00)

                // Calculate CRC and append it
                byte[] crc = CalculateCRC(frame);
                byte[] fullFrame = new byte[frame.Length + crc.Length];
                Array.Copy(frame, fullFrame, frame.Length);
                Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length);

                return fullFrame;
            }
	      
            
        

        // Function to write multiple coils
        public  byte[] WriteMultipleCoils( ushort startAddress, bool[] coilValues)
        {
            byte functionCode = 0x0F;
            int byteCount = (coilValues.Length + 7) / 8;  // Calculate the number of bytes needed
            byte[] frame = new byte[5 + byteCount];  // Basic frame without CRC

            frame[0] = slaveId;  // Slave address
            frame[1] = functionCode;  // Function code
            frame[2] = (byte)(startAddress >> 8);  // Start address high byte
            frame[3] = (byte)(startAddress & 0xFF);  // Start address low byte
            frame[4] = (byte)(coilValues.Length);  // Number of coils

            frame[5] = (byte)byteCount;  // Number of bytes to follow (coil values)
            for (int i = 0; i < coilValues.Length; i++)
            {
                int byteIndex = 5 + 1 + (i / 8);  // Start at byte 6, accounting for byte count
                if (coilValues[i])
                {
                    frame[byteIndex] |= (byte)(1 << (i % 8));  // Set the bit corresponding to the coil
                }
            }

            // Calculate CRC and append it
            byte[] crc = CalculateCRC(frame);
            byte[] fullFrame = new byte[frame.Length + crc.Length];
            Array.Copy(frame, fullFrame, frame.Length);
            Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length);

            return fullFrame;
        }
        // Function to write multiple registers
        public async Task<byte[]> WriteSingleRegister(ushort startAddress, int value)
        {
            byte functionCode = 0x06; // Function code for writing a single register
            byte[] frame = new byte[6];  // Basic frame without CRC

            frame[0] = slaveId;  // Slave address
            frame[1] = functionCode;  // Function code
            frame[2] = (byte)(startAddress >> 8);  // Start address high byte
            frame[3] = (byte)(startAddress & 0xFF);  // Start address low byte
            frame[4] = (byte)(value >> 8);  // Register value high byte
            frame[5] = (byte)(value & 0xFF);  // Register value low byte

            // Calculate CRC and append it
            byte[] crc = CalculateCRC(frame);
            byte[] fullFrame = new byte[frame.Length + crc.Length];
            Array.Copy(frame, fullFrame, frame.Length);
            Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length);

            return fullFrame;
        }
        public async Task<byte[]> WriteSingleRegister(ushort startAddress, ushort value)
        {
            byte functionCode = 0x06; // Function code for writing a single register
            byte[] frame = new byte[6];  // Basic frame without CRC

            frame[0] = slaveId;  // Slave address
            frame[1] = functionCode;  // Function code
            frame[2] = (byte)(startAddress >> 8);  // Start address high byte
            frame[3] = (byte)(startAddress & 0xFF);  // Start address low byte
            frame[4] = (byte)(value >> 8);  // Register value high byte
            frame[5] = (byte)(value & 0xFF);  // Register value low byte

            // Calculate CRC and append it
            byte[] crc = CalculateCRC(frame);
            byte[] fullFrame = new byte[frame.Length + crc.Length];
            Array.Copy(frame, fullFrame, frame.Length);
            Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length);

            return fullFrame;
        }
        public async Task<byte[]> ReadHoldingRegister(ushort startAddress, ushort numberOfRegisters)
        {
            byte functionCode = 0x03; // Function code for reading holding registers
            byte[] frame = new byte[6];  // Basic frame without CRC

            frame[0] = slaveId;  // Slave address
            frame[1] = functionCode;  // Function code
            frame[2] = (byte)(startAddress >> 8);  // Start address high byte
            frame[3] = (byte)(startAddress & 0xFF);  // Start address low byte
            frame[4] = (byte)(numberOfRegisters >> 8);  // Number of registers high byte
            frame[5] = (byte)(numberOfRegisters & 0xFF);  // Number of registers low byte

            // Calculate CRC and append it
            byte[] crc = CalculateCRC(frame);
            byte[] fullFrame = new byte[frame.Length + crc.Length];
            Array.Copy(frame, fullFrame, frame.Length);
            Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length);

            return fullFrame;
        }



        public async Task<byte[]> ReadInputRegisters(ushort startAddress, ushort numberOfRegisters)
        {
            byte functionCode = 0x04; // Function code for reading input registers
            byte[] frame = new byte[6];  // Basic frame without CRC

            frame[0] = slaveId;  // Slave address
            frame[1] = functionCode;  // Function code
            frame[2] = (byte)(startAddress >> 8);  // Start address high byte
            frame[3] = (byte)(startAddress & 0xFF);  // Start address low byte
            frame[4] = (byte)(numberOfRegisters >> 8);  // Number of registers high byte
            frame[5] = (byte)(numberOfRegisters & 0xFF);  // Number of registers low byte

            // Calculate CRC and append it
            byte[] crc = CalculateCRC(frame);
            byte[] fullFrame = new byte[frame.Length + crc.Length];
            Array.Copy(frame, fullFrame, frame.Length);
            Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length);

            return fullFrame;
        }


        public async Task<byte[]> WriteMultipleRegisters(ushort startAddress, float[] values)
        {
            byte functionCode = 0x10; // Function code for writing multiple registers
            byte byteCount = (byte)(values.Length * 2); // Total number of bytes for values
            byte[] frame = new byte[7 + byteCount];  // Frame without CRC

            frame[0] = slaveId;  // Slave address
            frame[1] = functionCode;  // Function code
            frame[2] = (byte)(startAddress >> 8);  // Start address high byte
            frame[3] = (byte)(startAddress & 0xFF);  // Start address low byte
            frame[4] = (byte)(values.Length >> 8);  // Number of registers high byte
            frame[5] = (byte)(values.Length & 0xFF);  // Number of registers low byte
            frame[6] = byteCount;  // Byte count

            for (int i = 0; i < values.Length; i++)
            {
                short val = unchecked((short)values[i]);
                frame[7 + i * 2] = (byte)(val >> 8);  // Register value high byte
                frame[8 + i * 2] = (byte)(val & 0xFF);  // Register value low byte1
            }

            // Calculate CRC and append it
            byte[] crc = CalculateCRC(frame);
            byte[] fullFrame = new byte[frame.Length + crc.Length];
            Array.Copy(frame, fullFrame, frame.Length);
            Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length);

            return fullFrame;
        }



        // Function to calculate CRC16 for Modbus RTU frame
        public byte[] CalculateCRC(byte[] data)
        {
            ushort crc = 0xFFFF;

            foreach (byte byteData in data)
            {
                crc ^= byteData;

                for (int i = 8; i > 0; i--)
                {
                    if ((crc & 0x0001) == 0x0001)
                    {
                        crc >>= 1;
                        crc ^= 0xA001;
                    }
                    else
                    {
                        crc >>= 1;
                    }
                }
            }

            return new byte[] { (byte)(crc & 0xFF), (byte)((crc >> 8) & 0xFF) };
        }


    }
}
