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

#include "spi.hpp"
#include "logger.hpp"
#include "driver/gpio.h"
#include "driver/spi_common.h"
#include "driver/spi_master.h"
#include <cstring>

static const char* TAG = "SPI_WRAPPER";

Spi::Spi()
{
    m_isInitialized_[0] = false;
    m_isInitialized_[1] = false;
    m_isInitialized_[2] = false;
    ASF_LOGI(TAG, 2500, asf::logger::Criticality::LOW, "SPI wrapper initialized");
}

Spi::~Spi()
{
    // Deinitialize all hosts
    for (int i = 0; i < 3; i++) {
        if (m_isInitialized_[i]) {
            deinitializeBus(static_cast<SpiHost>(i));
        }
    }
    ASF_LOGI(TAG, 2501, asf::logger::Criticality::LOW, "SPI wrapper destroyed");
}

bool Spi::initializeBus(SpiHost host, const SpiBusConfig& config)
{
    uint8_t hostIdx = static_cast<uint8_t>(host);
    spi_host_device_t spiHost = convertHost(host);
    
    if (m_isInitialized_[hostIdx]) {
        ASF_LOGW(TAG, 2502, asf::logger::Criticality::MEDIUM, "SPI host %d already initialized", hostIdx);
        return true;
    }

    // Configure SPI bus
    spi_bus_config_t busConfig = convertBusConfig(config);
    
    esp_err_t ret = spi_bus_initialize(spiHost, &busConfig, SPI_DMA_CH_AUTO);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2503, asf::logger::Criticality::HIGH, "Failed to initialize SPI bus %d: %s", hostIdx, esp_err_to_name(ret));
        return false;
    }

    // Enable pull-ups on all SPI lines to ensure signal stability
    if (config.misoPin >= 0) {
        gpio_set_pull_mode(static_cast<gpio_num_t>(config.misoPin), GPIO_PULLUP_ONLY);
    }
    if (config.mosiPin >= 0) {
        gpio_set_pull_mode(static_cast<gpio_num_t>(config.mosiPin), GPIO_PULLUP_ONLY);
    }
    if (config.sclkPin >= 0) {
        gpio_set_pull_mode(static_cast<gpio_num_t>(config.sclkPin), GPIO_PULLUP_ONLY);
    }

    m_isInitialized_[hostIdx] = true;
    ASF_LOGI(TAG, 2504, asf::logger::Criticality::LOW, "SPI initialized successfully");
    return true;
}

bool Spi::deinitializeBus(SpiHost host)
{
    uint8_t hostIdx = static_cast<uint8_t>(host);
    spi_host_device_t spiHost = convertHost(host);
    
    if (!m_isInitialized_[hostIdx]) {
        ASF_LOGW(TAG, 2505, asf::logger::Criticality::MEDIUM, "SPI host %d not initialized", hostIdx);
        return true;
    }

    esp_err_t ret = spi_bus_free(spiHost);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2506, asf::logger::Criticality::HIGH, "Failed to free SPI bus %d: %s", hostIdx, esp_err_to_name(ret));
        return false;
    }

    m_isInitialized_[hostIdx] = false;
    ASF_LOGI(TAG, 2507, asf::logger::Criticality::LOW, "SPI bus %d deinitialized", hostIdx);
    return true;
}

bool Spi::addDevice(SpiHost host, const SpiDeviceConfig& config, spi_device_handle_t* deviceHandle)
{
    uint8_t hostIdx = static_cast<uint8_t>(host);
    spi_host_device_t spiHost = convertHost(host);
    
    if (!m_isInitialized_[hostIdx]) {
        ASF_LOGE(TAG, 2508, asf::logger::Criticality::HIGH, "SPI host %d not initialized", hostIdx);
        return false;
    }
    
    if (deviceHandle == nullptr) {
        ASF_LOGE(TAG, 2509, asf::logger::Criticality::HIGH, "Invalid device handle pointer");
        return false;
    }

    // Configure SPI device
    spi_device_interface_config_t deviceConfig = convertDeviceConfig(config);
    
    esp_err_t ret = spi_bus_add_device(spiHost, &deviceConfig, deviceHandle);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2510, asf::logger::Criticality::HIGH, "Failed to add SPI device to bus %d: %s", hostIdx, esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2511, asf::logger::Criticality::LOW, "SPI device added to bus %d successfully", hostIdx);
    return true;
}

bool Spi::removeDevice(spi_device_handle_t deviceHandle)
{
    if (deviceHandle == nullptr) {
        ASF_LOGE(TAG, 2512, asf::logger::Criticality::HIGH, "Invalid device handle");
        return false;
    }

    esp_err_t ret = spi_bus_remove_device(deviceHandle);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2513, asf::logger::Criticality::HIGH, "Failed to remove SPI device: %s", esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2514, asf::logger::Criticality::LOW, "SPI device removed successfully");
    return true;
}

int32_t Spi::transmit(spi_device_handle_t deviceHandle, const uint8_t* txData, uint8_t* rxData, size_t length)
{
    if (deviceHandle == nullptr) {
        ASF_LOGE(TAG, 2515, asf::logger::Criticality::HIGH, "Invalid device handle");
        return -1;
    }
    
    if (length == 0) {
        ASF_LOGW(TAG, 2516, asf::logger::Criticality::MEDIUM, "Zero length transfer");
        return 0;
    }

    spi_transaction_t transaction = {};
    transaction.length = length * 8;  // Length in bits
    transaction.tx_buffer = txData;
    transaction.rx_buffer = rxData;

    esp_err_t ret = spi_device_transmit(deviceHandle, &transaction);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2517, asf::logger::Criticality::HIGH, "Failed to transmit SPI data: %s", esp_err_to_name(ret));
        return -1;
    }

    return static_cast<int32_t>(length);
}

int32_t Spi::transmitOnly(spi_device_handle_t deviceHandle, const uint8_t* txData, size_t length)
{
    return transmit(deviceHandle, txData, nullptr, length);
}

int32_t Spi::receiveOnly(spi_device_handle_t deviceHandle, uint8_t* rxData, size_t length)
{
    return transmit(deviceHandle, nullptr, rxData, length);
}

int32_t Spi::transmitWithCmdAddr(spi_device_handle_t deviceHandle, uint32_t command, uint32_t address,
                                 const uint8_t* txData, uint8_t* rxData, size_t length)
{
    if (deviceHandle == nullptr) {
        ASF_LOGE(TAG, 2518, asf::logger::Criticality::HIGH, "Invalid device handle");
        return -1;
    }

    spi_transaction_t transaction = {};
    transaction.cmd = command;
    transaction.addr = address;
    transaction.length = length * 8;  // Length in bits
    transaction.tx_buffer = txData;
    transaction.rx_buffer = rxData;

    esp_err_t ret = spi_device_transmit(deviceHandle, &transaction);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2519, asf::logger::Criticality::HIGH, "Failed to transmit SPI data with cmd/addr: %s", esp_err_to_name(ret));
        return -1;
    }

    return static_cast<int32_t>(length);
}

bool Spi::isInitialized(SpiHost host) const
{
    uint8_t hostIdx = static_cast<uint8_t>(host);
    return m_isInitialized_[hostIdx];
}

SpiBusConfig Spi::getDefaultBusConfig()
{
    SpiBusConfig config = {};
    config.mosiPin = GPIO_NUM_23;
    config.misoPin = GPIO_NUM_19;
    config.sclkPin = GPIO_NUM_18;
    config.quadwpPin = -1;
    config.quadhdPin = -1;
    config.maxTransferSize = 4096;
    return config;
}

SpiDeviceConfig Spi::getDefaultDeviceConfig()
{
    SpiDeviceConfig config = {};
    config.csPin = GPIO_NUM_5;
    config.clockSpeedHz = 1000000;  // 1MHz
    config.mode = SpiMode::MODE_0;
    config.queueSize = 7;
    config.commandBits = 0;
    config.addressBits = 0;
    config.dummyBits = 0;
    config.csEnaPretrans = false;
    config.csEnaPosttrans = false;
    return config;
}

spi_host_device_t Spi::convertHost(SpiHost host)
{
    switch (host) {
        case SpiHost::SPI1_HOST:
            return SPI1_HOST;
        case SpiHost::SPI2_HOST:
            return SPI2_HOST;
        case SpiHost::SPI3_HOST:
            return SPI3_HOST;
        default:
            return SPI2_HOST;
    }
}

spi_bus_config_t Spi::convertBusConfig(const SpiBusConfig& config)
{
    spi_bus_config_t busConfig = {};
    busConfig.mosi_io_num = config.mosiPin;
    busConfig.miso_io_num = config.misoPin;
    busConfig.sclk_io_num = config.sclkPin;
    busConfig.quadwp_io_num = config.quadwpPin;
    busConfig.quadhd_io_num = config.quadhdPin;
    busConfig.max_transfer_sz = config.maxTransferSize;
    busConfig.flags = 0;
    busConfig.intr_flags = 0;
    return busConfig;
}

spi_device_interface_config_t Spi::convertDeviceConfig(const SpiDeviceConfig& config)
{
    spi_device_interface_config_t deviceConfig = {};
    deviceConfig.command_bits = config.commandBits;
    deviceConfig.address_bits = config.addressBits;
    deviceConfig.dummy_bits = config.dummyBits;
    deviceConfig.mode = convertMode(config.mode);
    deviceConfig.duty_cycle_pos = 0;
    deviceConfig.cs_ena_pretrans = config.csEnaPretrans ? 1 : 0;
    deviceConfig.cs_ena_posttrans = config.csEnaPosttrans ? 1 : 0;
    deviceConfig.clock_speed_hz = config.clockSpeedHz;
    deviceConfig.input_delay_ns = 0;
    deviceConfig.spics_io_num = config.csPin;
    deviceConfig.flags = 0;
    deviceConfig.queue_size = config.queueSize;
    deviceConfig.pre_cb = nullptr;
    deviceConfig.post_cb = nullptr;
    return deviceConfig;
}

uint32_t Spi::convertMode(SpiMode mode)
{
    return static_cast<uint32_t>(mode);
}
