#pragma once

#include "hwhelper.h"

extern "C" {

/**
 * @brief Перечисление GPIO пинов
 *
 * Содержит коды всех доступных GPIO пинов системы с их назначением.
 */
enum class HwhGpioPinEnum {
    /* MDB */
    kMdb1SlvMstrSlct = kGpioMdb1SlvMstrSlct,  ///< Выбор режима Master/Slave для MDB1
    kMdb2SlvMstrSlct = kGpioMdb2SlvMstrSlct,  ///< Выбор режима Master/Slave для MDB2
    kMdb1SnifEnable  = kGpioMdb1SnifEnable,   ///< Включение режима сниффера для MDB1
    kMdb2SnifEnable  = kGpioMdb2SnifEnable,   ///< Включение режима сниффера для MDB2
    /* DEX */
    kDex1Dex2Enable = kGpioDex1Dex2Enable,    ///< Включение интерфейса DEX1/DEX2
    /* GPI/GPO */
    kGpi1     = kGpioGpi1,     ///< GPIO вход 1
    kGpi2     = kGpioGpi2,     ///< GPIO вход 2
    kGpo3     = kGpioGpo3,     ///< GPIO выход 3
    kGpo4     = kGpioGpo4,     ///< GPIO выход 4
    kGpiTclk1 = kGpioGpi1,     ///< Альтернативное название для GPIO входа 1 (TCLK)
    kGpiTclk2 = kGpioGpi2,     ///< Альтернативное название для GPIO входа 2 (TCLK)
    kGpoTioa3 = kGpioGpo3,     ///< Альтернативное название для GPIO выхода 3 (TIOA)
    kGpoTioa4 = kGpioGpo4,     ///< Альтернативное название для GPIO выхода 4 (TIOA)
    /* UART */
    kUrxd2    = kGpioUrxd2,    ///< Приёмник UART2
    kUtxd2    = kGpioUtxd2,    ///< Передатчик UART2
    kAuxUrxd4 = kGpioAuxUrxd4, ///< Приёмник AUX UART4
    kAuxUtxd4 = kGpioAuxUtxd4, ///< Передатчик AUX UART4
    kUart2Rx  = kGpioUrxd2,    ///< Альтернативное название для приёмника UART2
    kUart2Tx  = kGpioUtxd2,    ///< Альтернативное название для передатчика UART2
    kUart4Rx  = kGpioAuxUrxd4, ///< Альтернативное название для приёмника UART4
    kUart4Tx  = kGpioAuxUtxd4, ///< Альтернативное название для передатчика UART4
    /* GPIO */
    kGpio0 = kGpioGpio0,       ///< GPIO пин 0 общего назначения
    kGpio1 = kGpioGpio1,       ///< GPIO пин 1 общего назначения
    kGpio2 = kGpioGpio2,       ///< GPIO пин 2 общего назначения
    kGpio3 = kGpioGpio3,       ///< GPIO пин 3 общего назначения
    /* FCOM IO */
    kFcom0Io0 = kGpioFcom0Io0, ///< FCOM0 IO линия 0
    kFcom0Io1 = kGpioFcom0Io1, ///< FCOM0 IO линия 1
    kFcom0Io2 = kGpioFcom0Io2, ///< FCOM0 IO линия 2
    kFcom0Io3 = kGpioFcom0Io3, ///< FCOM0 IO линия 3
    kFcom0Io4 = kGpioFcom0Io4, ///< FCOM0 IO линия 4
    kFcom2Io0 = kGpioFcom2Io0, ///< FCOM2 IO линия 0
    kFcom2Io1 = kGpioFcom2Io1, ///< FCOM2 IO линия 1
    kFcom2Io2 = kGpioFcom2Io2, ///< FCOM2 IO линия 2
    kFcom2Io3 = kGpioFcom2Io3, ///< FCOM2 IO линия 3
    kFcom4Io2 = kGpioFcom4Io2, ///< FCOM4 IO линия 2
    /* FCOM UARTs */
    kAuxFcom2MuxSlct = kGpioAuxFcom2MuxSlct,  ///< Выбор мультиплексора для FCOM2 UART
    /* CLASSD */
    kClassdEnable = kGpioClassdEnable,        ///< Включение аудио усилителя CLASSD
    /* ISO7816 */
    kIso7816Disable = kGpioIso7816Disable,    ///< Отключение интерфейса ISO7816
    /* CAN */
    kCanSilentEnable = kGpioCanSilentEnable,  ///< Включение silent режима CAN интерфейса
    /* RS485 */
    kRs485ReceiverDisable = kGpioRs485ReceiverDisable, ///< Отключение приёмника RS485
    /* Modem power pins */
    kModemPowerCommand = kGpioModemPowerCommand, ///< Управление питанием модема (команда)
    kModemReset        = kGpioModemReset,        ///< Сброс модема
    kModemPowerEnable  = kGpioModemPowerEnable,  ///< Включение питания модема
    /* NFC */
    kNfcReset = kGpioNfcReset,   ///< Сброс NFC контроллера
    kNfcIrq   = kGpioNfcIrq,     ///< Прерывание от NFC контроллера
    kNfcBusy  = kGpioNfcBusy,    ///< Флаг занятости NFC контроллера
    kNfcDwl   = kGpioNfcDwl,     ///< Сигнал загрузки данных в NFC контроллер
    /* ADC MUX */
    kAdcMux = kGpioAdcMux,       ///< Управление мультиплексором ADC
    /* QR Scanner */
    kQrsTrig   = kGpioQrsTrig,   ///< Триггер сканера QR кодов
    kQrsEnable = kGpioQrsEnable, ///< Включение сканера QR кодов
};

/**
 * @brief Перечисление TTY устройств
 *
 * Содержит коды всех доступных TTY устройств системы с их назначением.
 */
enum class HwhTtyDeviceEnum {
    /* MDB */
    kMdb1Exe1Sniffer = kTtyDeviceMdb1Exe1Sniffer, ///< TTY для MDB1 исполнителя/сниффера
    kMdb2Exe2Sniffer = kTtyDeviceMdb2Exe2Sniffer, ///< TTY для MDB2 исполнителя/сниффера
    kMdb1Exe1        = kTtyDeviceMdb1Exe1,        ///< TTY для MDB1 исполнителя
    kMdb2Exe2        = kTtyDeviceMdb2Exe2,        ///< TTY для MDB2 исполнителя
    /* DEX */
    kDex1 = kTtyDeviceDex1, ///< TTY для устройства DEX1
    kDex2 = kTtyDeviceDex2, ///< TTY для устройства DEX2
    /* UART */
    kUart2 = kTtyDeviceUart2, ///< TTY для UART2
    kUart4 = kTtyDeviceUart4, ///< TTY для UART4
    /* FCOM UARTs */
    kFcom0Uart  = kTtyDeviceFcom0Uart,  ///< TTY для FCOM0 UART
    kFcom0UartB = kTtyDeviceFcom0UartB, ///< TTY для FCOM0 UART (альтернативный)
    /* RS485 */
    kRs485 = kTtyDeviceRs485, ///< TTY для RS485 интерфейса
    /* RS232 */
    kRs232 = kTtyDeviceRs232, ///< TTY для RS232 интерфейса
    /* QR Scanner */
    kQrScanner = kTtyDeviceQrScanner, ///< TTY для сканера QR кодов
    /* Debug UART */
    kDebugUart = kTtyDeviceDebugUart,   ///< TTY для отладочного UART
    /* Test Ports */
    kRs485TestReciprocal = kTtyDeviceRs485TestReciprocal,   ///< TTY преобразователя USB-RS485 в QC-тестах
    kRs232TestReciprocal = kTtyDeviceRs232TestReciprocal,   ///< TTY преобразователя USB-RS232 в QC-тестах
};

/// \brief Класс для работы с пинами GPIO
class HwhGpioPin
{
public:
    /** \brief Создаёт объект пина GPIO
    \param [in] pin_enum Код #HwhGpioPinEnum пина GPIO
    */
    explicit HwhGpioPin(HwhGpioPinEnum const pin_enum);
    /** \brief Создаёт копию пина GPIO
    \param gpio_pin [in] Объект пина GPIO, который будет скопирован
    */
    HwhGpioPin(HwhGpioPin const& gpio_pin);
    /** \brief Уничтожает объект пина GPIO
    \details Уничтожает объект и освобождает занятые им ресурсы.
    */
    ~HwhGpioPin();

    /** \brief Возвращает код пина GPIO
    \return Код #HwhGpioPinEnum пина GPIO
    */
    HwhGpioPinEnum getEnum() const;
    /** \brief Выполняет проверку существования пина GPIO
    \details Проверка осуществляется по коду пина, который использовался при создании объекта.
    \retval false Пин не существует
    \retval true Пин существует
    */
    bool exist() const;
    /** \brief Возвращает наименование пина GPIO
    \return Наименование пина GPIO
    */
    char const* name() const;

    /** \brief Устанавливает направление (режим работы) пина GPIO
    \param [in] is_output true - выход, false - вход
    \retval #HWH_OK Функция выполнена успешно
    \retval #hwh_error < 0 Ошибка выполнения функции
    */
    int setDirection(bool const is_output) const;
    /** \brief Возвращает направление (режим работы) пина GPIO
    \retval #HWH_GPIO_IN Вход (INPUT)
    \retval #HWH_GPIO_OUT Выход (OUTPUT)
    \retval #hwh_error < 0 Ошибка выполнения функции
    */
    int getDirection() const;
    /** \brief Устанавливает низкий (LOW) или высокий (HIGH) логический активный уровень пина GPIO
    \param [in] is_active_low true - низкий (LOW) логический уровень пина является активным, flase - активный уровень
    высокий (HIGH)
    \retval #HWH_OK Функция выполнена успешно
    \retval #hwh_error < 0 Ошибка выполнения функции
    */
    int setActiveLow(bool const is_active_low) const;
    /** \brief Устанавливает фронты сигнала, по которым функция HwhGpioPin::poll() будет возвращать уровень пина GPIO
    \param [in] rising true - нарастающий фронт (переход из неактивного уровня пина в активное)
    \param [in] falling true - спадающий фронт (переход из активного уровня пина в неактивное)
    \retval #HWH_OK Функция выполнена успешно
    \retval #hwh_error < 0 Ошибка выполнения функции
    */
    int setEdge(bool rising, bool falling) const;

    /**
     * @brief Активирует или деактивирует pull-up резистор.
     * @param [in] pullup Значение, указывающее, нужно ли активировать pull-up резистор.
     * Если pullup равно true, то pull-up резистор будет активирован, иначе - деактивирован.
     * @retval HWH_OK Функция выполнена успешно.
     * @retval HWH_ERROR_NOT_SUPPORTED Операция не поддерживается для данного пина GPIO.
     * @retval HWH_ERROR_OPEN Ошибка доступа к контролирующему pullup-функционал интерфейсу.
     * @retval HWH_ERROR_WRITE Ошибка записи в контролирующий pullup-функционал интерфейс.
     * @retval HWH_ERROR_CLOSE Ошибка завершения работы с контролирующим pullup-функционал интерфейсом.
     */
    int setPullUp(bool const pullup) const;

    /** \brief Опрос изменения уровня пина GPIO
    \details Настройка фронтов, по которым будет возвращаться значение задаётся функцией HwhGpioPin::setEdge().

    Пример организации опроса:
    \code
    // Создание пина
    HwhGpioPin gpi1_pin{HwhGpioPinEnum::kGpi1};
    // poll() будет возвращать уровень пина GPIO по нарастающему и спадающему фронтам
    gpi1_pin.setEdge(true, true);
    // Активный уровень низкий (LOW)
    gpi1_pin.setActiveLow(true);
    // Считываем текущее состояние пина
    // Если этого не сделать, то первый вызов poll() сразу вернёт результат
    gpi1_pin.read();
    // Цикл опроса. Выход из цикла по ошибке или изменению уровня
    while (true) {
        int val;
        // Опрос c тайм-аутом 1 сек
        int ret = gpi1_pin.poll(1000, &val);

        if (ret < 0) {
            // Ошибка вызова poll()
            break;
        }

        if (ret == HWH_GPIO_POLL_TIMEOUT) {
            // Тайм-аут
        } else if (ret == HWH_OK) {
            if (val == 0) {
                // Был переход из активного состояния в неактивное
                break;
            } else {
                // Был переход из неактивного состояния в активное
                break;
            }
        } else {
            // Неизвестный код возврата poll()
            break;
        }
    }
    \endcode
    \param [in] timeout_ms Тайм-аут опроса, в мс; < 0 - бесконечный опрос
    \param [out] val 0 - неактивный уровень, !0 - активный уровень
    \retval #HWH_OK Функция выполнена успешно
    \retval #HWH_GPIO_POLL_TIMEOUT Тайм-аут опроса
    \retval #HWH_GPIO_POLL_BREAK Опрос прерван с помощью функции HwhGpioPin::pollBreak()
    \retval #hwh_error < 0 Ошибка выполнения функции
    */
    int poll(int timeout_ms, int& val) const;
    /** \brief Включает возможность прерывания опроса пина GPIO HwhGpioPin::poll()
    \retval #HWH_OK Функция выполнена успешно
    \retval #hwh_error < 0 Ошибка выполнения функции
    */
    int pollBreakEnable() const;
    /** \brief Прерывает опрос пина GPIO HwhGpioPin::poll()
    \details Опрос HwhGpioPin::poll() должен предваряться вызовом функции HwhGpioPin::pollBreakEnable().

    Пример организации опроса с прерыванием:
    \code
    // Создание пина
    HwhGpioPin gpi1_pin{HwhGpioPinEnum::kGpi1};
    // poll() будет возвращать уровень пина GPIO по нарастающему и спадающему фронтам
    gpi1_pin.setEdge(true, true);
    // Активный уровень низкий (LOW)
    gpi1_pin.setActiveLow(true);
    // Считываем текущее состояние пина
    // Если этого не сделать, то первый вызов poll() сразу вернёт результат
    gpi1_pin.read();
    // Включаем возможность прерывания опроса
    gpi1_pin.pollBreakEnable();
    // Цикл опроса. Выход из цикла по ошибке или переходу из неактивного состояния в активное с помощью прерывания
    опроса
    while (true) {
        int val;
        // Опрос c тайм-аутом 1 сек
        int ret = gpi1_pin.poll(1000, &val);

        if (ret < 0) {
            // Ошибка вызова poll()
            break;
        }

        if (ret == HWH_GPIO_POLL_TIMEOUT) {
            // Тайм-аут
        } else if (ret == HWH_GPIO_POLL_BREAK) {
            // Опрос прерван
            break;
        } else if (ret == HWH_OK) {
            if (val == 0) {
                // Был переход из активного состояния в неактивное
            } else {
                // Был переход из неактивного состояния в активное
                gpi1_pin.pollBreak();
            }
        } else {
            // Неизвестный код возврата poll()
            break;
        }
    }
    \endcode
    \retval #HWH_OK Функция выполнена успешно
    \retval #hwh_error < 0 Ошибка выполнения функции
    */
    int pollBreak() const;
    /** \brief Устанавливает уровень пина GPIO
    \param [in] val 0 - неактивный уровень, !0 - активный уровень
    \retval #HWH_OK Функция выполнена успешно
    \retval #hwh_error < 0 Ошибка выполнения функции
    */
    int write(int const val) const;
    /** \brief Считывает уровень пина GPIO
    \retval 0 Неактивный уровень
    \retval >0 Активный уровень
    \retval #hwh_error < 0 Ошибка выполнения функции
    */
    int read() const;
    /** \brief Экспортирует пин GPIO в Sysfs
    \details Если пин не экспортирован в Sysfs, то работа с ним невозможна
    \retval #HWH_OK Функция выполнена успешно
    \retval #hwh_error < 0 Ошибка выполнения функции
    */
    int exportToSysfs() const;
    /** \brief Освобождает раннее экспортированный пин GPIO в Sysfs
    \retval #HWH_OK Функция выполнена успешно
    \retval #hwh_error < 0 Ошибка выполнения функции
    */
    int unexportFromSysfs() const;

private:
    HwhGpioPinEnum m_enum;
    hwh_gpio_pin_t m_gpio_pin;
};

/// \brief Класс для работы с устройствами TTY
class HwhTtyDevice
{
public:
    /** \brief Создаёт объект устройства TTY
    \param [in] tty_device_enum Код #HwhTtyDeviceEnum устройства TTY
    */
    explicit HwhTtyDevice(HwhTtyDeviceEnum const tty_device_enum);
    /** \brief Создаёт копию устройства TTY
    \param tty_device [in] Объект устройства TTY, который будет скопирован
    */
    HwhTtyDevice(HwhTtyDevice const& tty_device);
    /** \brief Уничтожает объект устройства TTY
    \details Уничтожает объект и освобождает занятые им ресурсы.
    */
    ~HwhTtyDevice();

    /** \brief Возвращает код устройства TTY
    \return Код #HwhTtyDeviceEnum устройства TTY
    */
    HwhTtyDeviceEnum getEnum() const;

    /** \brief Выполняет проверку существования устройства TTY
    \details Проверка осуществляется по коду устройства, который использовался при создании объекта.
    \retval false Устройство не существует
    \retval true Устройство существует
    */
    bool exist() const;
    /** \brief Возвращает наименование устройства TTY в виде аналогичном `2501400.uart`
     * \return Наименование устройства TTY
     */
    char const* name() const;
    /** \brief Возвращает путь файла устройства TTY в виде аналогичном `/dev/ttyS5`
     * или `/dev/serial/by-path/platform-4200400.usb-usb-0:1:1.0-port0`
     * \return Путь файла устройства TTY
     */
    char const* path() const;
    /** \brief Выполняет проверку, что устройство TTY является FLEXCOM
    \details FLEXCOM - Flexible Serial Communication Controller. Актуально для устройств с MPU фирмы Microchip (Atmel).
    \retval false Устройство не является FLEXCOM
    \retval true Устройство является FLEXCOM
    */
    bool isFlexcom() const;

    hwh_error isBound(bool* is_bound) const;

    /**
     * @brief Привязывает устройство TTY к его драйверу
     *
     * Привязывает устройство, ранее отвязанное вызовом `hwh_tty_device_unbind` либо другими
     * средствами, к его драйверу.
     *
     * Для выполнения необходимы полномочия root.
     *
     * @param [in] tty_device Указатель на объект устройства TTY в памяти (handle)
     * @retval HWH_OK
     * @retval HWH_ERROR_OPEN - Ошибка открытия атрибута "bind" драйвера устройства
     * @retval HWH_ERROR_WRITE - Ошибка записи в атрибут "bind" драйвера устройства
     * @retval HWH_ERROR_NOT_SUPPORTED - Операция привязки не поддерживается для этого устройства
     */
    hwh_error bind() const;

    /**
     * @brief Отвязывает устройство TTY от его драйвера
     *
     * Отвязка устройства освобождает ресурсы, используемые устройством TTY, включая
     * пины TX и RX. После отвязки устройства можно использовать эти пины как GPIO.
     *
     * Для выполнения необходимы полномочия root.
     *
     * @param [in] tty_device Указатель на объект устройства TTY в памяти (handle)
     * @retval HWH_OK
     * @retval HWH_ERROR_OPEN - Ошибка открытия атрибута "unbind" драйвера устройства
     * @retval HWH_ERROR_WRITE - Ошибка записи в атрибут "unbind" драйвера устройства
     * @retval HWH_ERROR_NOT_SUPPORTED - Операция отвязки не поддерживается для этого устройства
     */
    hwh_error unbind() const;

private:
    HwhTtyDeviceEnum m_enum;
    hwh_tty_device_t m_tty_device;
};

}  // extern "C"
