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

#include "dma.hpp"
#include "logger.hpp"
#include "esp_memory_utils.h"
#include <cstring>

static const char* TAG = "DMA_WRAPPER";

Dma::Dma()
{
    // Initialize channel handles and allocation status
    memset(m_channels_, 0, sizeof(m_channels_));
    memset(m_channelAllocated_, false, sizeof(m_channelAllocated_));
    ASF_LOGI(TAG, 2200, asf::logger::Criticality::LOW, "DMA wrapper initialized");
}

Dma::~Dma()
{
    // Deallocate all channels
    for (int i = 0; i < MAX_CHANNELS; i++) {
        if (m_channelAllocated_[i]) {
            deallocateChannel(m_channels_[i]);
        }
    }
    ASF_LOGI(TAG, 2201, asf::logger::Criticality::LOW, "DMA wrapper destroyed");
}

bool Dma::initialize(DmaChannel channel, const DmaConfig& config)
{
    ASF_LOGI(TAG, 2202, asf::logger::Criticality::LOW, "DMA initialized successfully");
    return true;
}

#if SOC_GDMA_SUPPORTED
bool Dma::allocateChannel(const DmaChannelConfig& config, gdma_channel_handle_t* channelHandle)
#else
bool Dma::allocateChannel(const DmaChannelConfig& config, void** channelHandle)
#endif
{
    if (channelHandle == nullptr) {
        ASF_LOGE(TAG, 2203, asf::logger::Criticality::HIGH, "Invalid channel handle pointer");
        return false;
    }

    int slotIndex = findFreeChannelSlot();
    if (slotIndex < 0) {
        ASF_LOGE(TAG, 2204, asf::logger::Criticality::HIGH, "No free channel slots available");
        return false;
    }

#if SOC_GDMA_SUPPORTED
    gdma_channel_alloc_config_t allocConfig = {};
    allocConfig.direction = GDMA_CHANNEL_DIRECTION_TX;  // Default to TX, can be changed later
    allocConfig.flags.reserve_sibling = 0;

    esp_err_t ret = gdma_new_channel(&allocConfig, channelHandle);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2205, asf::logger::Criticality::HIGH, "Failed to allocate DMA channel: %s", esp_err_to_name(ret));
        return false;
    }

    m_channels_[slotIndex] = *channelHandle;
    m_channelAllocated_[slotIndex] = true;

    ASF_LOGI(TAG, 2206, asf::logger::Criticality::LOW, "DMA channel allocated successfully (slot %d)", slotIndex);
    return true;
#else
    ASF_LOGW(TAG, 2207, asf::logger::Criticality::MEDIUM, "GDMA not supported on this target");
    return false;
#endif
}

#if SOC_GDMA_SUPPORTED
bool Dma::deallocateChannel(gdma_channel_handle_t channelHandle)
#else
bool Dma::deallocateChannel(void* channelHandle)
#endif
{
    if (channelHandle == nullptr) {
        ASF_LOGE(TAG, 2208, asf::logger::Criticality::HIGH, "Invalid channel handle");
        return false;
    }

    int slotIndex = findChannelSlot(channelHandle);
    if (slotIndex < 0) {
        ASF_LOGE(TAG, 2209, asf::logger::Criticality::HIGH, "Channel handle not found");
        return false;
    }

#if SOC_GDMA_SUPPORTED
    esp_err_t ret = gdma_del_channel(channelHandle);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2210, asf::logger::Criticality::HIGH, "Failed to deallocate DMA channel: %s", esp_err_to_name(ret));
        return false;
    }

    m_channels_[slotIndex] = nullptr;
    m_channelAllocated_[slotIndex] = false;

    ASF_LOGI(TAG, 2211, asf::logger::Criticality::LOW, "DMA channel deallocated successfully (slot %d)", slotIndex);
    return true;
#else
    return false;
#endif
}

#if SOC_GDMA_SUPPORTED
bool Dma::configureTransfer(gdma_channel_handle_t channelHandle, const DmaTransferConfig& config)
#else
bool Dma::configureTransfer(void* channelHandle, const DmaTransferConfig& config)
#endif
{
    if (channelHandle == nullptr) {
        ASF_LOGE(TAG, 2212, asf::logger::Criticality::HIGH, "Invalid channel handle");
        return false;
    }

    // Configure transfer based on direction
    switch (config.direction) {
        case DmaDirection::MEM_TO_MEM:
            // Memory to memory transfer configuration
            break;
        case DmaDirection::MEM_TO_PERIPH:
            // Memory to peripheral transfer configuration
            break;
        case DmaDirection::PERIPH_TO_MEM:
            // Peripheral to memory transfer configuration
            break;
        case DmaDirection::PERIPH_TO_PERIPH:
            // Peripheral to peripheral transfer configuration
            break;
    }

    ASF_LOGI(TAG, 2213, asf::logger::Criticality::LOW, "DMA transfer configured successfully");
    return true;
}

#if SOC_GDMA_SUPPORTED
bool Dma::startTransfer(gdma_channel_handle_t channelHandle)
#else
bool Dma::startTransfer(void* channelHandle)
#endif
{
    if (channelHandle == nullptr) {
        ASF_LOGE(TAG, 2214, asf::logger::Criticality::HIGH, "Invalid channel handle");
        return false;
    }

#if SOC_GDMA_SUPPORTED
    esp_err_t ret = gdma_start(channelHandle);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2215, asf::logger::Criticality::HIGH, "Failed to start DMA transfer: %s", esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2216, asf::logger::Criticality::LOW, "DMA transfer started successfully");
    return true;
#else
    return false;
#endif
}

#if SOC_GDMA_SUPPORTED
bool Dma::stopTransfer(gdma_channel_handle_t channelHandle)
#else
bool Dma::stopTransfer(void* channelHandle)
#endif
{
    if (channelHandle == nullptr) {
        ASF_LOGE(TAG, 2217, asf::logger::Criticality::HIGH, "Invalid channel handle");
        return false;
    }

#if SOC_GDMA_SUPPORTED
    esp_err_t ret = gdma_stop(channelHandle);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2218, asf::logger::Criticality::HIGH, "Failed to stop DMA transfer: %s", esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2219, asf::logger::Criticality::LOW, "DMA transfer stopped successfully");
    return true;
#else
    return false;
#endif
}

#if SOC_GDMA_SUPPORTED
bool Dma::resetChannel(gdma_channel_handle_t channelHandle)
#else
bool Dma::resetChannel(void* channelHandle)
#endif
{
    if (channelHandle == nullptr) {
        ASF_LOGE(TAG, 2220, asf::logger::Criticality::HIGH, "Invalid channel handle");
        return false;
    }

#if SOC_GDMA_SUPPORTED
    esp_err_t ret = gdma_reset(channelHandle);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2221, asf::logger::Criticality::HIGH, "Failed to reset DMA channel: %s", esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2222, asf::logger::Criticality::LOW, "DMA channel reset successfully");
    return true;
#else
    return false;
#endif
}

void* Dma::allocateDmaMemory(size_t size, uint32_t caps)
{
    void* ptr = heap_caps_malloc(size, caps);
    if (ptr == nullptr) {
        ASF_LOGE(TAG, 2223, asf::logger::Criticality::HIGH, "Failed to allocate DMA memory of size %zu", size);
        return nullptr;
    }

    ASF_LOGI(TAG, 2224, asf::logger::Criticality::LOW, "DMA memory allocated: %zu bytes at %p", size, ptr);
    return ptr;
}

void Dma::freeDmaMemory(void* ptr)
{
    if (ptr != nullptr) {
        heap_caps_free(ptr);
        ASF_LOGI(TAG, 2225, asf::logger::Criticality::LOW, "DMA memory freed at %p", ptr);
    }
}

DmaDescriptor* Dma::createDescriptor(void* buffer, size_t size, bool eof)
{
    if (buffer == nullptr || size == 0) {
        ASF_LOGE(TAG, 2226, asf::logger::Criticality::HIGH, "Invalid buffer or size for descriptor");
        return nullptr;
    }

    DmaDescriptor* desc = static_cast<DmaDescriptor*>(
        heap_caps_malloc(sizeof(DmaDescriptor), MALLOC_CAP_DMA));
    
    if (desc == nullptr) {
        ASF_LOGE(TAG, 2227, asf::logger::Criticality::HIGH, "Failed to allocate DMA descriptor");
        return nullptr;
    }

    desc->size = size;
    desc->length = size;
    desc->sosf = 0;
    desc->eof = eof ? 1 : 0;
    desc->owner = 1;  // DMA owns the descriptor
    desc->reserved = 0;
    desc->buffer = buffer;
    desc->next = nullptr;

    ASF_LOGI(TAG, 2228, asf::logger::Criticality::LOW, "DMA descriptor created: size=%zu, eof=%d", size, eof);
    return desc;
}

bool Dma::linkDescriptors(DmaDescriptor* desc1, DmaDescriptor* desc2)
{
    if (desc1 == nullptr || desc2 == nullptr) {
        ASF_LOGE(TAG, 2229, asf::logger::Criticality::HIGH, "Invalid descriptors for linking");
        return false;
    }

    desc1->next = desc2;
    desc1->eof = 0;  // Not end of frame since there's a next descriptor

    ASF_LOGI(TAG, 2230, asf::logger::Criticality::LOW, "DMA descriptors linked successfully");
    return true;
}

void Dma::freeDescriptor(DmaDescriptor* descriptor)
{
    if (descriptor != nullptr) {
        heap_caps_free(descriptor);
        ASF_LOGI(TAG, 2231, asf::logger::Criticality::LOW, "DMA descriptor freed");
    }
}

#if SOC_GDMA_SUPPORTED
bool Dma::setCallback(gdma_channel_handle_t channelHandle, DmaCallback callback, void* userData)
#else
bool Dma::setCallback(void* channelHandle, DmaCallback callback, void* userData)
#endif
{
    if (channelHandle == nullptr || callback == nullptr) {
        ASF_LOGE(TAG, 2232, asf::logger::Criticality::HIGH, "Invalid channel handle or callback");
        return false;
    }

#if SOC_GDMA_SUPPORTED
    gdma_event_callbacks_t callbacks = {};
    callbacks.on_trans_eof = callback;

    esp_err_t ret = gdma_register_event_callbacks(channelHandle, &callbacks, userData);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2233, asf::logger::Criticality::HIGH, "Failed to set DMA callback: %s", esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2234, asf::logger::Criticality::LOW, "DMA callback set successfully");
    return true;
#else
    return false;
#endif
}

#if SOC_GDMA_SUPPORTED
bool Dma::enableChannel(gdma_channel_handle_t channelHandle)
#else
bool Dma::enableChannel(void* channelHandle)
#endif
{
    if (channelHandle == nullptr) {
        ASF_LOGE(TAG, 2235, asf::logger::Criticality::HIGH, "Invalid channel handle");
        return false;
    }

#if SOC_GDMA_SUPPORTED
    esp_err_t ret = gdma_start(channelHandle);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2236, asf::logger::Criticality::HIGH, "Failed to enable DMA channel: %s", esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2237, asf::logger::Criticality::LOW, "DMA channel enabled successfully");
    return true;
#else
    return false;
#endif
}

#if SOC_GDMA_SUPPORTED
bool Dma::disableChannel(gdma_channel_handle_t channelHandle)
#else
bool Dma::disableChannel(void* channelHandle)
#endif
{
    if (channelHandle == nullptr) {
        ASF_LOGE(TAG, 2238, asf::logger::Criticality::HIGH, "Invalid channel handle");
        return false;
    }

#if SOC_GDMA_SUPPORTED
    esp_err_t ret = gdma_stop(channelHandle);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2239, asf::logger::Criticality::HIGH, "Failed to disable DMA channel: %s", esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2240, asf::logger::Criticality::LOW, "DMA channel disabled successfully");
    return true;
#else
    return false;
#endif
}

#if SOC_GDMA_SUPPORTED
gdma_channel_state_t Dma::getChannelState(gdma_channel_handle_t channelHandle)
#else
int Dma::getChannelState(void* channelHandle)
#endif
{
    if (channelHandle == nullptr) {
        ASF_LOGE(TAG, 2241, asf::logger::Criticality::HIGH, "Invalid channel handle");
#if SOC_GDMA_SUPPORTED
        return GDMA_CHANNEL_STATE_INVALID;
#else
        return -1;
#endif
    }

    // Note: ESP-IDF doesn't provide a direct API to get channel state
    // This would need to be implemented by reading hardware registers
#if SOC_GDMA_SUPPORTED
    return GDMA_CHANNEL_STATE_IDLE;
#else
    return 0;
#endif
}

#if SOC_GDMA_SUPPORTED
bool Dma::memcpy(void* dst, const void* src, size_t size, gdma_channel_handle_t channelHandle)
#else
bool Dma::memcpy(void* dst, const void* src, size_t size, void* channelHandle)
#endif
{
    if (dst == nullptr || src == nullptr || size == 0) {
        ASF_LOGE(TAG, 2242, asf::logger::Criticality::HIGH, "Invalid parameters for DMA memcpy");
        return false;
    }

#if SOC_GDMA_SUPPORTED
    bool allocatedChannel = false;
    gdma_channel_handle_t channel = channelHandle;

    // Allocate channel if not provided
    if (channel == nullptr) {
        DmaChannelConfig config = getDefaultChannelConfig();
        if (!allocateChannel(config, &channel)) {
            return false;
        }
        allocatedChannel = true;
    }

    // Perform DMA copy using ESP-IDF DMA utilities
    // Note: This implementation is a placeholder, actual GDMA memcpy requires descriptors
    ::memcpy(dst, src, size);
    esp_err_t ret = ESP_OK;

    // Clean up if we allocated the channel
    if (allocatedChannel) {
        deallocateChannel(channel);
    }

    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2243, asf::logger::Criticality::HIGH, "DMA memcpy failed: %s", esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2244, asf::logger::Criticality::LOW, "DMA memcpy completed: %zu bytes", size);
    return true;
#else
    // Fallback to standard memcpy if GDMA not supported
    ::memcpy(dst, src, size);
    return true;
#endif
}

DmaChannelConfig Dma::getDefaultChannelConfig()
{
    DmaChannelConfig config = {};
    config.priority = DmaPriority::MEDIUM;
    config.flags = 0;
    return config;
}

DmaTransferConfig Dma::getDefaultTransferConfig()
{
    DmaTransferConfig config = {};
    config.srcAddr = nullptr;
    config.dstAddr = nullptr;
    config.dataSize = 0;
    config.direction = DmaDirection::MEM_TO_MEM;
    config.type = DmaTransferType::SINGLE;
    config.priority = DmaPriority::MEDIUM;
    config.autoReload = false;
    config.enableInterrupt = false;
    return config;
}

bool Dma::isDmaCapable(const void* addr)
{
    return esp_ptr_dma_capable(addr);
}

int Dma::findFreeChannelSlot()
{
    for (int i = 0; i < MAX_CHANNELS; i++) {
        if (!m_channelAllocated_[i]) {
            return i;
        }
    }
    return -1;
}

#if SOC_GDMA_SUPPORTED
int Dma::findChannelSlot(gdma_channel_handle_t channelHandle)
#else
int Dma::findChannelSlot(void* channelHandle)
#endif
{
    for (int i = 0; i < MAX_CHANNELS; i++) {
        if (m_channelAllocated_[i] && m_channels_[i] == channelHandle) {
            return i;
        }
    }
    return -1;
}

#if SOC_GDMA_SUPPORTED
gdma_transfer_ability_t Dma::convertDirection(DmaDirection direction)
{
    switch (direction) {
        case DmaDirection::MEM_TO_MEM:
            return GDMA_TRANSFER_ABILITY_MEM;
        case DmaDirection::MEM_TO_PERIPH:
            return GDMA_TRANSFER_ABILITY_MEM;
        case DmaDirection::PERIPH_TO_MEM:
            return GDMA_TRANSFER_ABILITY_MEM;
        case DmaDirection::PERIPH_TO_PERIPH:
            return GDMA_TRANSFER_ABILITY_MEM;
        default:
            return GDMA_TRANSFER_ABILITY_MEM;
    }
}
#endif

int Dma::convertPriority(DmaPriority priority)
{
    return static_cast<int>(priority);
}
