/**
 * @file gpio.cpp
 * @brief GPIO wrapper component implementation
 * @author Mahmoud Elmohtady
 * @company Nabd solutions - ASF
 * @copyright Copyright (c) 2025
 */

#include "gpio.hpp"
#include "logger.hpp"

static const char* TAG = "GPIO_WRAPPER";

/**
 * @brief Constructor - Initialize GPIO wrapper instance
 * @details Initializes the GPIO wrapper with default settings
 */
Gpio::Gpio()
    : m_isrInstalled_(false)
{
    ASF_LOGI(TAG, 2300, asf::logger::Criticality::LOW, "GPIO wrapper initialized");
}

/**
 * @brief Destructor - Clean up GPIO wrapper resources
 * @details Uninstalls ISR service if it was installed
 */
Gpio::~Gpio()
{
    if (m_isrInstalled_) {
        uninstallIsr();
    }
    ASF_LOGI(TAG, 2301, asf::logger::Criticality::LOW, "GPIO wrapper destroyed");
}

bool Gpio::configure(uint32_t pin, GpioMode mode)
{
    if (!isValidPin(pin)) {
        ASF_LOGE(TAG, 2302, asf::logger::Criticality::HIGH, "Invalid GPIO pin: %lu", pin);
        return false;
    }

    gpio_config_t config = {};
    config.pin_bit_mask = (1ULL << pin);
    config.mode = convertMode(mode);
    config.intr_type = GPIO_INTR_DISABLE;
    
    // Configure pull-up/down based on mode
    switch (mode) {
        case GpioMode::INPUT_PULLUP:
            config.pull_up_en = GPIO_PULLUP_ENABLE;
            config.pull_down_en = GPIO_PULLDOWN_DISABLE;
            break;
        case GpioMode::INPUT_PULLDOWN:
            config.pull_up_en = GPIO_PULLUP_DISABLE;
            config.pull_down_en = GPIO_PULLDOWN_ENABLE;
            break;
        default:
            config.pull_up_en = GPIO_PULLUP_DISABLE;
            config.pull_down_en = GPIO_PULLDOWN_DISABLE;
            break;
    }

    esp_err_t ret = gpio_config(&config);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2303, asf::logger::Criticality::HIGH, "Failed to configure GPIO pin %lu: %s", pin, esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2304, asf::logger::Criticality::LOW, "GPIO pin %lu configured successfully", pin);
    return true;
}

bool Gpio::setLevel(uint32_t pin, uint32_t level)
{
    if (!isValidPin(pin)) {
        ASF_LOGE(TAG, 2302, asf::logger::Criticality::HIGH, "Invalid GPIO pin: %lu", pin);
        return false;
    }

    esp_err_t ret = gpio_set_level(static_cast<gpio_num_t>(pin), level);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2305, asf::logger::Criticality::HIGH, "Failed to set GPIO pin %lu level: %s", pin, esp_err_to_name(ret));
        return false;
    }

    return true;
}

int32_t Gpio::getLevel(uint32_t pin)
{
    if (!isValidPin(pin)) {
        ASF_LOGE(TAG, 2302, asf::logger::Criticality::HIGH, "Invalid GPIO pin: %lu", pin);
        return -1;
    }

    int level = gpio_get_level(static_cast<gpio_num_t>(pin));
    return level;
}

bool Gpio::toggleLevel(uint32_t pin)
{
    int32_t currentLevel = getLevel(pin);
    if (currentLevel < 0) {
        return false;
    }
    
    return setLevel(pin, currentLevel == 0 ? 1 : 0);
}

bool Gpio::installIsr(int flags)
{
    if (m_isrInstalled_) {
        ASF_LOGW(TAG, 2306, asf::logger::Criticality::MEDIUM, "GPIO ISR already installed");
        return true;
    }

    esp_err_t ret = gpio_install_isr_service(flags);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2307, asf::logger::Criticality::HIGH, "Failed to install GPIO ISR service: %s", esp_err_to_name(ret));
        return false;
    }

    m_isrInstalled_ = true;
    ASF_LOGI(TAG, 2308, asf::logger::Criticality::LOW, "GPIO initialized successfully");
    return true;
}

bool Gpio::uninstallIsr()
{
    if (!m_isrInstalled_) {
        ASF_LOGW(TAG, 2309, asf::logger::Criticality::MEDIUM, "GPIO ISR not installed");
        return true;
    }

    gpio_uninstall_isr_service();
    m_isrInstalled_ = false;
    ASF_LOGI(TAG, 2310, asf::logger::Criticality::LOW, "GPIO ISR service uninstalled");
    return true;
}

bool Gpio::attachInterrupt(uint32_t pin, GpioIntType intType, GpioCallback callback, void* arg)
{
    if (!isValidPin(pin)) {
        ASF_LOGE(TAG, 2302, asf::logger::Criticality::HIGH, "Invalid GPIO pin: %lu", pin);
        return false;
    }

    if (!m_isrInstalled_) {
        ASF_LOGW(TAG, 2311, asf::logger::Criticality::MEDIUM, "GPIO ISR not installed, installing now");
        if (!installIsr()) {
            return false;
        }
    }

    // Set interrupt type
    esp_err_t ret = gpio_set_intr_type(static_cast<gpio_num_t>(pin), convertIntType(intType));
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2312, asf::logger::Criticality::HIGH, "Failed to set interrupt type for pin %lu: %s", pin, esp_err_to_name(ret));
        return false;
    }

    // Add ISR handler
    ret = gpio_isr_handler_add(static_cast<gpio_num_t>(pin), 
                                reinterpret_cast<gpio_isr_t>(callback), arg);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2313, asf::logger::Criticality::HIGH, "Failed to add ISR handler for pin %lu: %s", pin, esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2314, asf::logger::Criticality::LOW, "Interrupt attached to GPIO pin %lu", pin);
    return true;
}

bool Gpio::detachInterrupt(uint32_t pin)
{
    if (!isValidPin(pin)) {
        ASF_LOGE(TAG, 2302, asf::logger::Criticality::HIGH, "Invalid GPIO pin: %lu", pin);
        return false;
    }

    // Disable interrupt
    esp_err_t ret = gpio_set_intr_type(static_cast<gpio_num_t>(pin), GPIO_INTR_DISABLE);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2315, asf::logger::Criticality::HIGH, "Failed to disable interrupt for pin %lu: %s", pin, esp_err_to_name(ret));
        return false;
    }

    // Remove ISR handler
    ret = gpio_isr_handler_remove(static_cast<gpio_num_t>(pin));
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2316, asf::logger::Criticality::HIGH, "Failed to remove ISR handler for pin %lu: %s", pin, esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2317, asf::logger::Criticality::LOW, "Interrupt detached from GPIO pin %lu", pin);
    return true;
}

bool Gpio::enableInterrupt(uint32_t pin)
{
    if (!isValidPin(pin)) {
        ASF_LOGE(TAG, 2302, asf::logger::Criticality::HIGH, "Invalid GPIO pin: %lu", pin);
        return false;
    }

    esp_err_t ret = gpio_intr_enable(static_cast<gpio_num_t>(pin));
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2318, asf::logger::Criticality::HIGH, "Failed to enable interrupt for pin %lu: %s", pin, esp_err_to_name(ret));
        return false;
    }

    return true;
}

bool Gpio::disableInterrupt(uint32_t pin)
{
    if (!isValidPin(pin)) {
        ASF_LOGE(TAG, 2302, asf::logger::Criticality::HIGH, "Invalid GPIO pin: %lu", pin);
        return false;
    }

    esp_err_t ret = gpio_intr_disable(static_cast<gpio_num_t>(pin));
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2319, asf::logger::Criticality::HIGH, "Failed to disable interrupt for pin %lu: %s", pin, esp_err_to_name(ret));
        return false;
    }

    return true;
}

bool Gpio::isValidPin(uint32_t pin)
{
    return GPIO_IS_VALID_GPIO(pin);
}

gpio_mode_t Gpio::convertMode(GpioMode mode)
{
    switch (mode) {
        case GpioMode::INPUT:
        case GpioMode::INPUT_PULLUP:
        case GpioMode::INPUT_PULLDOWN:
            return GPIO_MODE_INPUT;
        case GpioMode::OUTPUT:
            return GPIO_MODE_OUTPUT;
        case GpioMode::OUTPUT_OD:
            return GPIO_MODE_OUTPUT_OD;
        default:
            return GPIO_MODE_INPUT;
    }
}

gpio_int_type_t Gpio::convertIntType(GpioIntType intType)
{
    switch (intType) {
        case GpioIntType::DISABLE:
            return GPIO_INTR_DISABLE;
        case GpioIntType::RISING_EDGE:
            return GPIO_INTR_POSEDGE;
        case GpioIntType::FALLING_EDGE:
            return GPIO_INTR_NEGEDGE;
        case GpioIntType::ANY_EDGE:
            return GPIO_INTR_ANYEDGE;
        case GpioIntType::LOW_LEVEL:
            return GPIO_INTR_LOW_LEVEL;
        case GpioIntType::HIGH_LEVEL:
            return GPIO_INTR_HIGH_LEVEL;
        default:
            return GPIO_INTR_DISABLE;
    }
}
