#pragma once

#include "main.h"
#include <optional>
#include <string_view>

class GpioHelper {
public:
    struct Gpio {
        GPIO_TypeDef* port;
        uint16_t pin;

        void Set() const {
            port->BSRR = pin;
        }

        void Reset() const {
            port->BSRR = pin << 16;
        }

        void Toggle() const {
            port->ODR ^= pin;
        }

        [[nodiscard]] GPIO_PinState Read() const {
            return port->IDR & pin ? GPIO_PIN_SET : GPIO_PIN_RESET;
        }
    };

    struct GpioPlus {
        std::string_view name;
        Gpio gpio;
    };

    static void EnableAllGpioPeripheral() {
        __HAL_RCC_GPIOA_CLK_ENABLE();
        __HAL_RCC_GPIOB_CLK_ENABLE();
        __HAL_RCC_GPIOC_CLK_ENABLE();
        __HAL_RCC_GPIOD_CLK_ENABLE();
        __HAL_RCC_GPIOE_CLK_ENABLE();
        __HAL_RCC_GPIOF_CLK_ENABLE();
        __HAL_RCC_GPIOG_CLK_ENABLE();
    }

    static Gpio GpioInit(GPIO_TypeDef* port, const uint16_t pin, const uint32_t mode, const uint32_t pull,
        const std::optional<GPIO_PinState> state = std::nullopt) {
        static GPIO_InitTypeDef o = {};
        if (state.has_value()) {
            HAL_GPIO_WritePin(port, pin, state.value());
        }
        o.Pin   = pin;
        o.Mode  = mode;
        o.Pull  = pull;
        o.Speed = GPIO_SPEED_FREQ_LOW;
        HAL_GPIO_Init(port, &o);
        return {port, pin};
    }

    static Gpio GpioInit(const Gpio& gpio, const uint32_t mode, const uint32_t pull, const GPIO_PinState state) {
        return GpioInit(gpio.port, gpio.pin, mode, pull, state);
    }

    static void GpioDeInit(const Gpio& gpio) {
        HAL_GPIO_DeInit(gpio.port, gpio.pin);
    }

    static void GpioDeInit(GPIO_TypeDef* port, const uint16_t pin) {
        HAL_GPIO_DeInit(port, pin);
    }
};
