#pragma once

#include <DebugLog.h>
#include <Arduino.h>
#include <HardwareSerial.h> // Reference the ESP32 built-in serial port library
#include <CRC16.h>
#include <memory>

#define Extension_CH1 1      // Expansion Channel 1
#define Extension_CH2 2      // Expansion Channel 2
#define Extension_CH3 3      // Expansion Channel 3
#define Extension_CH4 4      // Expansion Channel 4
#define Extension_CH5 5      // Expansion Channel 5
#define Extension_CH6 6      // Expansion Channel 6
#define Extension_CH7 7      // Expansion Channel 7
#define Extension_CH8 8      // Expansion Channel 8
#define Extension_ALL_ON 9   // Expansion ALL ON
#define Extension_ALL_OFF 10 // Expansion ALL OFF

namespace drivers
{
    class RS485
    {
        static const uint8_t PORT = 1;

    public:
        RS485(const uint32_t baud, const SerialConfig conf);
        const bool write(const std::vector<uint8_t> data);
        const bool readAll(std::vector<uint8_t> &data);
        const bool readN(const uint16_t nBytes, std::vector<uint8_t> &data);
        const bool readUntil(const uint8_t ch, std::vector<uint8_t> &data);

    private:
        HardwareSerial &m_serial;
    };

    class MODBUS : public RS485
    {

        static const uint8_t RESP_HEADER_SIZE = 3;
        static const uint8_t RESP_CRC_SIZE = 2;

        typedef struct
        {
            uint8_t device;
            uint8_t func;
            uint16_t reg;
            uint16_t data;
        } req_t;

        typedef struct
        {
            uint8_t device;
            uint8_t func;
            uint16_t reg;
            uint16_t qty;
            uint8_t bytes;
        } req_multi_t;

        typedef req_t resp_t;
        typedef uint16_t crc_t;

    public:

        MODBUS(const uint32_t baud, const SerialConfig conf);

        // Func 0x01
        const bool readCoils(const uint8_t device, const uint16_t reg, const uint16_t num, std::vector<bool> &coils);

        // Func 0x02
        const bool readInputs(const uint8_t device, const uint16_t reg, const uint8_t num, std::vector<bool> &inputs);

        // Func 0x03
        const bool readHoldingRegisters(const uint8_t device, const uint16_t reg, const uint8_t num, std::vector<uint16_t> &values);

        // Func 0x04
        const bool readInputRegisters(const uint8_t device, const uint16_t reg, const uint8_t num, std::vector<uint16_t> &values);

        // Func 0x05
        const bool writeCoil(const uint8_t device, const uint16_t coil, const bool value);

        // Func 0x06
        const bool writeRegister(const uint8_t device, const uint16_t reg, const uint16_t value);

        // Func 0x0F
        const bool writeCoils(const uint8_t device, const uint16_t coils, const std::vector<bool> &values);

        // Func 0x10
        const bool writeRegisters(const uint8_t device, const uint16_t reg, const std::vector<uint16_t> &values);

    private:
        CRC16 m_crc;
        const std::vector<uint8_t> singleRequest(const uint8_t device, const uint8_t func, const uint16_t reg, const uint16_t data);
        const std::vector<uint8_t> multiRequest(const uint8_t device, const uint8_t func, const uint16_t reg, const uint16_t qty, const std::vector<uint8_t> &data);
        const bool readBinary(const uint8_t device, const uint8_t func, const uint16_t reg, const uint16_t bits, std::vector<bool> &out);
        const bool readInteger(const uint8_t device, const uint8_t func, const uint16_t reg, const uint16_t num, std::vector<uint16_t> &out);
        const bool writeBinary(const uint8_t device, const uint8_t func, const uint16_t reg, const std::vector<bool> &in);
        const bool writeInteger(const uint8_t device, const uint8_t func, const uint16_t reg, const std::vector<uint16_t> &in);
        const bool verifyCrc(const std::vector<uint8_t> &data);
    };
}
