/**
 * @file gpio_wrapper_example.cpp
 * @brief Example of integrating ASF Logger with GPIO wrapper
 * @author Mahmoud Elmohtady
 * @company Nabd solutions - ASF
 * @copyright Copyright (c) 2025
 */

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

// Module-specific log tag and IDs
static const char* TAG = "GPIO_WRAPPER";

namespace GpioLogIds {
    // Info messages (2001-2199)
    static const uint32_t WRAPPER_INIT = 2001;
    static const uint32_t WRAPPER_DESTROY = 2002;
    static const uint32_t PIN_CONFIGURED = 2003;
    static const uint32_t ISR_INSTALLED = 2004;
    static const uint32_t INTERRUPT_ATTACHED = 2005;
    static const uint32_t INTERRUPT_DETACHED = 2006;
    
    // Debug messages (2201-2399)
    static const uint32_t PIN_LEVEL_SET = 2201;
    static const uint32_t PIN_LEVEL_READ = 2202;
    static const uint32_t PIN_TOGGLED = 2203;
    
    // Warning messages (2401-2599)
    static const uint32_t ISR_ALREADY_INSTALLED = 2401;
    static const uint32_t PORT_NOT_INITIALIZED = 2402;
    
    // Error messages (2601-2799)
    static const uint32_t INVALID_PIN = 2601;
    static const uint32_t CONFIG_FAILED = 2602;
    static const uint32_t SET_LEVEL_FAILED = 2603;
    static const uint32_t ISR_INSTALL_FAILED = 2604;
    static const uint32_t INTERRUPT_ATTACH_FAILED = 2605;
    static const uint32_t INTERRUPT_DETACH_FAILED = 2606;
}

/**
 * @brief Example GPIO wrapper with ASF Logger integration
 */
class GpioWithLogger
{
public:
    GpioWithLogger() : m_isrInstalled_(false)
    {
        ASF_LOGI(TAG, GpioLogIds::WRAPPER_INIT, "GPIO wrapper initialized");
    }

    ~GpioWithLogger()
    {
        if (m_isrInstalled_) {
            uninstallIsr();
        }
        ASF_LOGI(TAG, GpioLogIds::WRAPPER_DESTROY, "GPIO wrapper destroyed");
    }

    bool configure(uint32_t pin, GpioMode mode)
    {
        ASF_LOGI(TAG, GpioLogIds::PIN_CONFIGURED, "Configuring GPIO pin %lu", pin);
        
        if (!isValidPin(pin)) {
            ASF_LOGE(TAG, GpioLogIds::INVALID_PIN, "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;
                ASF_LOGD(TAG, GpioLogIds::PIN_CONFIGURED, "Pin %lu configured with pull-up", pin);
                break;
            case GpioMode::INPUT_PULLDOWN:
                config.pull_up_en = GPIO_PULLUP_DISABLE;
                config.pull_down_en = GPIO_PULLDOWN_ENABLE;
                ASF_LOGD(TAG, GpioLogIds::PIN_CONFIGURED, "Pin %lu configured with pull-down", pin);
                break;
            default:
                config.pull_up_en = GPIO_PULLUP_DISABLE;
                config.pull_down_en = GPIO_PULLDOWN_DISABLE;
                ASF_LOGD(TAG, GpioLogIds::PIN_CONFIGURED, "Pin %lu configured without pull resistors", pin);
                break;
        }

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

        ASF_LOGI(TAG, GpioLogIds::PIN_CONFIGURED, "GPIO pin %lu configured successfully", pin);
        return true;
    }

    bool setLevel(uint32_t pin, uint32_t level)
    {
        if (!isValidPin(pin)) {
            ASF_LOGE(TAG, GpioLogIds::INVALID_PIN, "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, GpioLogIds::SET_LEVEL_FAILED, "Failed to set GPIO pin %lu level: %s", 
                     pin, esp_err_to_name(ret));
            return false;
        }

        ASF_LOGD(TAG, GpioLogIds::PIN_LEVEL_SET, "GPIO pin %lu set to level %lu", pin, level);
        return true;
    }

    int32_t getLevel(uint32_t pin)
    {
        if (!isValidPin(pin)) {
            ASF_LOGE(TAG, GpioLogIds::INVALID_PIN, "Invalid GPIO pin: %lu", pin);
            return -1;
        }

        int level = gpio_get_level(static_cast<gpio_num_t>(pin));
        ASF_LOGD(TAG, GpioLogIds::PIN_LEVEL_READ, "GPIO pin %lu level read: %d", pin, level);
        return level;
    }

    bool toggleLevel(uint32_t pin)
    {
        int32_t currentLevel = getLevel(pin);
        if (currentLevel < 0) {
            return false;
        }
        
        bool result = setLevel(pin, currentLevel == 0 ? 1 : 0);
        if (result) {
            ASF_LOGD(TAG, GpioLogIds::PIN_TOGGLED, "GPIO pin %lu toggled from %ld to %d", 
                     pin, currentLevel, currentLevel == 0 ? 1 : 0);
        }
        return result;
    }

    bool installIsr(int flags = 0)
    {
        if (m_isrInstalled_) {
            ASF_LOGW(TAG, GpioLogIds::ISR_ALREADY_INSTALLED, "GPIO ISR already installed");
            return true;
        }

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

        m_isrInstalled_ = true;
        ASF_LOGI(TAG, GpioLogIds::ISR_INSTALLED, "GPIO ISR service installed successfully");
        return true;
    }

    bool uninstallIsr()
    {
        if (!m_isrInstalled_) {
            ASF_LOGW(TAG, GpioLogIds::PORT_NOT_INITIALIZED, "GPIO ISR not installed");
            return true;
        }

        gpio_uninstall_isr_service();
        m_isrInstalled_ = false;
        ASF_LOGI(TAG, GpioLogIds::ISR_INSTALLED, "GPIO ISR service uninstalled");
        return true;
    }

    bool attachInterrupt(uint32_t pin, GpioIntType intType, GpioCallback callback, void* arg)
    {
        if (!isValidPin(pin)) {
            ASF_LOGE(TAG, GpioLogIds::INVALID_PIN, "Invalid GPIO pin: %lu", pin);
            return false;
        }

        if (!m_isrInstalled_) {
            ASF_LOGW(TAG, GpioLogIds::PORT_NOT_INITIALIZED, "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, GpioLogIds::INTERRUPT_ATTACH_FAILED, 
                     "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, GpioLogIds::INTERRUPT_ATTACH_FAILED, 
                     "Failed to add ISR handler for pin %lu: %s", pin, esp_err_to_name(ret));
            return false;
        }

        ASF_LOGI(TAG, GpioLogIds::INTERRUPT_ATTACHED, "Interrupt attached to GPIO pin %lu", pin);
        return true;
    }

    bool detachInterrupt(uint32_t pin)
    {
        if (!isValidPin(pin)) {
            ASF_LOGE(TAG, GpioLogIds::INVALID_PIN, "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, GpioLogIds::INTERRUPT_DETACH_FAILED, 
                     "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, GpioLogIds::INTERRUPT_DETACH_FAILED, 
                     "Failed to remove ISR handler for pin %lu: %s", pin, esp_err_to_name(ret));
            return false;
        }

        ASF_LOGI(TAG, GpioLogIds::INTERRUPT_DETACHED, "Interrupt detached from GPIO pin %lu", pin);
        return true;
    }

    static bool isValidPin(uint32_t pin)
    {
        return GPIO_IS_VALID_GPIO(pin);
    }

private:
    bool m_isrInstalled_;

    gpio_mode_t 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 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;
        }
    }
};

/**
 * @brief Example usage of GPIO wrapper with ASF Logger
 */
void exampleUsage()
{
    // Initialize ASF Logger
    asf::logger::LoggerConfig config = asf::logger::getDefaultConfig();
    config.minLevel = asf::logger::LogLevel::DEBUG;  // Enable debug messages
    asf::logger::initialize(config);

    ASF_LOGI("MAIN", 1001, "Starting GPIO wrapper example");

    // Create GPIO instance
    GpioWithLogger gpio;

    // Configure GPIO pin as output
    if (gpio.configure(2, GpioMode::OUTPUT)) {
        ASF_LOGI("MAIN", 1002, "GPIO pin 2 configured as output");
        
        // Set pin high
        gpio.setLevel(2, 1);
        
        // Toggle pin
        gpio.toggleLevel(2);
    } else {
        ASF_LOGE("MAIN", 1003, "Failed to configure GPIO pin 2");
    }

    // Configure GPIO pin as input with pull-up
    if (gpio.configure(4, GpioMode::INPUT_PULLUP)) {
        ASF_LOGI("MAIN", 1004, "GPIO pin 4 configured as input with pull-up");
        
        // Read pin level
        int32_t level = gpio.getLevel(4);
        ASF_LOGI("MAIN", 1005, "GPIO pin 4 level: %ld", level);
    }

    ASF_LOGI("MAIN", 1006, "GPIO wrapper example completed");
}

/**
 * @brief Interrupt handler example
 */
void IRAM_ATTR gpioInterruptHandler(uint32_t pin, void* arg)
{
    // Note: In interrupt context, logging should be minimal
    // Consider using a queue to defer logging to a task
    ASF_LOGI("ISR", 9001, "Interrupt on pin %lu", pin);
}

/**
 * @brief Example with interrupt handling
 */
void interruptExample()
{
    ASF_LOGI("MAIN", 1007, "Starting interrupt example");

    GpioWithLogger gpio;

    // Configure pin as input
    gpio.configure(5, GpioMode::INPUT);

    // Attach interrupt
    gpio.attachInterrupt(5, GpioIntType::FALLING_EDGE, gpioInterruptHandler, nullptr);

    ASF_LOGI("MAIN", 1008, "Interrupt example setup complete");
}