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

#include "i2c.hpp"
#include "logger.hpp"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

static const char* TAG = "I2C_WRAPPER";

I2c::I2c()
{
    m_isInitialized_[0] = false;
    m_isInitialized_[1] = false;
    ASF_LOGI(TAG, 2400, asf::logger::Criticality::LOW, "I2C wrapper initialized");
}

I2c::~I2c()
{
    // Deinitialize all ports
    for (int i = 0; i < 2; i++) {
        if (m_isInitialized_[i]) {
            deinitialize(static_cast<I2cPort>(i));
        }
    }
    ASF_LOGI(TAG, 2401, asf::logger::Criticality::LOW, "I2C wrapper destroyed");
}

bool I2c::initialize(I2cPort port, const I2cConfig& config)
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    i2c_port_t i2cPort = convertPort(port);
    
    if (m_isInitialized_[portIdx]) {
        ASF_LOGW(TAG, 2402, asf::logger::Criticality::MEDIUM, "I2C port %d already initialized", portIdx);
        return true;
    }

    // Configure I2C parameters
    i2c_config_t i2cConfig = convertConfig(config);
    
    esp_err_t ret = i2c_param_config(i2cPort, &i2cConfig);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2403, asf::logger::Criticality::HIGH, "Failed to configure I2C port %d: %s", portIdx, esp_err_to_name(ret));
        return false;
    }

    // Install I2C driver
    ret = i2c_driver_install(i2cPort, i2cConfig.mode, config.rxBufferLen, config.txBufferLen, 0);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2404, asf::logger::Criticality::HIGH, "Failed to install I2C driver for port %d: %s", portIdx, esp_err_to_name(ret));
        return false;
    }

    m_isInitialized_[portIdx] = true;
    ASF_LOGI(TAG, 2405, asf::logger::Criticality::LOW, "I2C initialized successfully");
    return true;
}

bool I2c::deinitialize(I2cPort port)
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    i2c_port_t i2cPort = convertPort(port);
    
    if (!m_isInitialized_[portIdx]) {
        ASF_LOGW(TAG, 2406, asf::logger::Criticality::MEDIUM, "I2C port %d not initialized", portIdx);
        return true;
    }

    esp_err_t ret = i2c_driver_delete(i2cPort);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2407, asf::logger::Criticality::HIGH, "Failed to delete I2C driver for port %d: %s", portIdx, esp_err_to_name(ret));
        return false;
    }

    m_isInitialized_[portIdx] = false;
    ASF_LOGI(TAG, 2408, asf::logger::Criticality::LOW, "I2C port %d deinitialized", portIdx);
    return true;
}

int32_t I2c::write(I2cPort port, uint8_t deviceAddress, const uint8_t* data, size_t dataLen, uint32_t timeoutMs)
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    i2c_port_t i2cPort = convertPort(port);
    
    if (!m_isInitialized_[portIdx]) {
        ASF_LOGE(TAG, 2409, asf::logger::Criticality::HIGH, "I2C port %d not initialized", portIdx);
        return -1;
    }
    
    if (data == nullptr || dataLen == 0) {
        ASF_LOGE(TAG, 2410, asf::logger::Criticality::HIGH, "Invalid data or length");
        return -1;
    }

    TickType_t timeout = pdMS_TO_TICKS(timeoutMs);
    
    esp_err_t ret = i2c_master_write_to_device(i2cPort, deviceAddress, data, dataLen, timeout);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2411, asf::logger::Criticality::HIGH, "Failed to write to device 0x%02X on port %d: %s", 
                 deviceAddress, portIdx, esp_err_to_name(ret));
        return -1;
    }

    return static_cast<int32_t>(dataLen);
}

int32_t I2c::read(I2cPort port, uint8_t deviceAddress, uint8_t* data, size_t dataLen, uint32_t timeoutMs)
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    i2c_port_t i2cPort = convertPort(port);
    
    if (!m_isInitialized_[portIdx]) {
        ASF_LOGE(TAG, 2412, asf::logger::Criticality::HIGH, "I2C port %d not initialized", portIdx);
        return -1;
    }
    
    if (data == nullptr || dataLen == 0) {
        ASF_LOGE(TAG, 2413, asf::logger::Criticality::HIGH, "Invalid data buffer or length");
        return -1;
    }

    TickType_t timeout = pdMS_TO_TICKS(timeoutMs);
    
    esp_err_t ret = i2c_master_read_from_device(i2cPort, deviceAddress, data, dataLen, timeout);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2414, asf::logger::Criticality::HIGH, "Failed to read from device 0x%02X on port %d: %s", 
                 deviceAddress, portIdx, esp_err_to_name(ret));
        return -1;
    }

    return static_cast<int32_t>(dataLen);
}

int32_t I2c::writeRead(I2cPort port, uint8_t deviceAddress, uint8_t regAddress, uint8_t* data, size_t dataLen, uint32_t timeoutMs)
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    i2c_port_t i2cPort = convertPort(port);
    
    if (!m_isInitialized_[portIdx]) {
        ASF_LOGE(TAG, 2415, asf::logger::Criticality::HIGH, "I2C port %d not initialized", portIdx);
        return -1;
    }
    
    if (data == nullptr || dataLen == 0) {
        ASF_LOGE(TAG, 2416, asf::logger::Criticality::HIGH, "Invalid data buffer or length");
        return -1;
    }

    TickType_t timeout = pdMS_TO_TICKS(timeoutMs);
    
    esp_err_t ret = i2c_master_write_read_device(i2cPort, deviceAddress, 
                                                 &regAddress, 1, data, dataLen, timeout);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2417, asf::logger::Criticality::HIGH, "Failed to write-read device 0x%02X on port %d: %s", 
                 deviceAddress, portIdx, esp_err_to_name(ret));
        return -1;
    }

    return static_cast<int32_t>(dataLen);
}

bool I2c::writeRegister(I2cPort port, uint8_t deviceAddress, uint8_t regAddress, uint8_t regValue, uint32_t timeoutMs)
{
    uint8_t writeData[2] = {regAddress, regValue};
    int32_t result = write(port, deviceAddress, writeData, 2, timeoutMs);
    return result == 2;
}

int32_t I2c::readRegister(I2cPort port, uint8_t deviceAddress, uint8_t regAddress, uint32_t timeoutMs)
{
    uint8_t regValue = 0;
    int32_t result = writeRead(port, deviceAddress, regAddress, &regValue, 1, timeoutMs);
    
    if (result == 1) {
        return regValue;
    }
    
    return -1;
}

int32_t I2c::scanBus(I2cPort port, uint8_t* devices, size_t maxDevices)
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    i2c_port_t i2cPort = convertPort(port);
    
    if (!m_isInitialized_[portIdx]) {
        ASF_LOGE(TAG, 2418, asf::logger::Criticality::HIGH, "I2C port %d not initialized", portIdx);
        return -1;
    }
    
    if (devices == nullptr || maxDevices == 0) {
        ASF_LOGE(TAG, 2419, asf::logger::Criticality::HIGH, "Invalid devices buffer or size");
        return -1;
    }

    int32_t deviceCount = 0;
    uint8_t testData = 0;
    
    ASF_LOGI(TAG, 2420, asf::logger::Criticality::LOW, "Scanning I2C bus on port %d...", portIdx);
    
    for (uint8_t addr = 0x08; addr < 0x78 && deviceCount < maxDevices; addr++) {
        esp_err_t ret = i2c_master_write_to_device(i2cPort, addr, &testData, 0, pdMS_TO_TICKS(100));
        
        if (ret == ESP_OK) {
            devices[deviceCount] = addr;
            deviceCount++;
            ASF_LOGI(TAG, 2421, asf::logger::Criticality::LOW, "Found device at address 0x%02X", addr);
        }
    }
    
    ASF_LOGI(TAG, 2422, asf::logger::Criticality::LOW, "I2C scan complete. Found %ld devices", deviceCount);
    return deviceCount;
}

bool I2c::isInitialized(I2cPort port) const
{
    uint8_t portIdx = static_cast<uint8_t>(port);
    return m_isInitialized_[portIdx];
}

I2cConfig I2c::getDefaultConfig()
{
    I2cConfig config = {};
    config.mode = I2cMode::MASTER;
    config.sdaPin = GPIO_NUM_21;
    config.sclPin = GPIO_NUM_22;
    config.clkSpeedHz = 100000;  // 100kHz
    config.sdaPullupEnable = true;
    config.sclPullupEnable = true;
    config.slaveAddress = 0;
    config.rxBufferLen = 0;  // Not used in master mode
    config.txBufferLen = 0;  // Not used in master mode
    return config;
}

i2c_port_t I2c::convertPort(I2cPort port)
{
    switch (port) {
        case I2cPort::PORT_0:
            return I2C_NUM_0;
        case I2cPort::PORT_1:
            return I2C_NUM_1;
        default:
            return I2C_NUM_0;
    }
}

i2c_config_t I2c::convertConfig(const I2cConfig& config)
{
    i2c_config_t i2cConfig = {};
    i2cConfig.mode = static_cast<i2c_mode_t>(config.mode);
    i2cConfig.sda_io_num = static_cast<gpio_num_t>(config.sdaPin);
    i2cConfig.scl_io_num = static_cast<gpio_num_t>(config.sclPin);
    i2cConfig.sda_pullup_en = config.sdaPullupEnable;
    i2cConfig.scl_pullup_en = config.sclPullupEnable;
    
    if (config.mode == I2cMode::MASTER) {
        i2cConfig.master.clk_speed = config.clkSpeedHz;
    } else {
        i2cConfig.slave.addr_10bit_en = 0;
        i2cConfig.slave.slave_addr = config.slaveAddress;
        i2cConfig.slave.maximum_speed = config.clkSpeedHz;
    }
    
    i2cConfig.clk_flags = 0;
    return i2cConfig;
}
