/**
 * @file dma.hpp
 * @brief DMA wrapper component header - Wrapper for ESP-IDF DMA functionality
 * @author Mahmoud Elmohtady
 * @company Nabd solutions - ASF
 * @copyright Copyright (c) 2025
 */

#ifndef DMA_HPP
#define DMA_HPP

#include <cstdint>
#include "esp_dma_utils.h"
#include "esp_err.h"
#include "esp_heap_caps.h"
#include "soc/soc_caps.h"

#if SOC_GDMA_SUPPORTED
#include "soc/gdma_struct.h"
#include "hal/gdma_ll.h"
#include "driver/gdma.h"
#endif

/**
 * @brief DMA transfer direction enumeration
 */
enum class DmaDirection
{
    MEM_TO_MEM,     ///< Memory to memory transfer
    MEM_TO_PERIPH,  ///< Memory to peripheral transfer
    PERIPH_TO_MEM,  ///< Peripheral to memory transfer
    PERIPH_TO_PERIPH ///< Peripheral to peripheral transfer
};

/**
 * @brief DMA transfer type enumeration
 */
enum class DmaTransferType
{
    SINGLE,         ///< Single transfer
    BLOCK,          ///< Block transfer
    LINKED_LIST     ///< Linked list transfer
};

/**
 * @brief DMA priority enumeration
 */
enum class DmaPriority
{
    LOW = 0,
    MEDIUM = 1,
    HIGH = 2,
    HIGHEST = 3
};

/**
 * @brief DMA channel enumeration
 */
enum class DmaChannel
{
    CHANNEL_0,
    CHANNEL_1,
    CHANNEL_2,
    CHANNEL_3,
    CHANNEL_4,
    CHANNEL_5,
    CHANNEL_6,
    CHANNEL_7
};

/**
 * @brief DMA configuration structure
 */
struct DmaConfig
{
    DmaPriority priority;
    uint32_t flags;
};

/**
 * @brief DMA descriptor structure
 */
struct DmaDescriptor
{
    uint32_t size;          ///< Size of data to transfer
    uint32_t length;        ///< Actual length of valid data
    uint32_t sosf : 1;      ///< Start of sub-frame
    uint32_t eof : 1;       ///< End of frame
    uint32_t owner : 1;     ///< Owner (0: CPU, 1: DMA)
    uint32_t reserved : 29; ///< Reserved bits
    void* buffer;           ///< Pointer to data buffer
    DmaDescriptor* next;    ///< Pointer to next descriptor
};

/**
 * @brief DMA transfer configuration structure
 */
struct DmaTransferConfig
{
    void* srcAddr;              ///< Source address
    void* dstAddr;              ///< Destination address
    size_t dataSize;            ///< Size of data to transfer
    DmaDirection direction;     ///< Transfer direction
    DmaTransferType type;       ///< Transfer type
    DmaPriority priority;       ///< Transfer priority
    bool autoReload;            ///< Auto-reload mode
    bool enableInterrupt;       ///< Enable transfer complete interrupt
};

/**
 * @brief DMA channel configuration structure
 */
struct DmaChannelConfig
{
    DmaPriority priority;       ///< Channel priority
    uint32_t flags;             ///< Configuration flags
};

/**
 * @brief DMA callback function type
 */
#if SOC_GDMA_SUPPORTED
using DmaCallback = void (*)(gdma_channel_handle_t dmaChannel, gdma_event_data_t* eventData, void* userData);
#else
using DmaCallback = void (*)(void* dmaChannel, void* eventData, void* userData);
#endif

/**
 * @brief DMA wrapper class
 * 
 * Provides a C++ wrapper for ESP-IDF GDMA functionality.
 * This class encapsulates ESP-IDF GDMA driver functions in an object-oriented interface.
 */
class Dma
{
public:
    /**
     * @brief Constructor
     * @details Initializes the DMA wrapper instance
     */
    Dma();

    /**
     * @brief Destructor
     * @details Cleans up resources and deinitializes all DMA channels
     */
    ~Dma();

    /**
     * @brief Initialize DMA component
     * @param channel DMA channel
     * @param config DMA configuration
     * @return true if initialized successfully, false otherwise
     */
    bool initialize(DmaChannel channel, const DmaConfig& config);

    /**
     * @brief Allocate DMA channel
     * @param config Channel configuration
     * @param channelHandle Pointer to store channel handle
     * @return true if allocated successfully, false otherwise
     */
#if SOC_GDMA_SUPPORTED
    bool allocateChannel(const DmaChannelConfig& config, gdma_channel_handle_t* channelHandle);
#else
    bool allocateChannel(const DmaChannelConfig& config, void** channelHandle);
#endif

    /**
     * @brief Deallocate DMA channel
     * @param channelHandle Channel handle to deallocate
     * @return true if deallocated successfully, false otherwise
     */
#if SOC_GDMA_SUPPORTED
    bool deallocateChannel(gdma_channel_handle_t channelHandle);
#else
    bool deallocateChannel(void* channelHandle);
#endif

    /**
     * @brief Configure DMA transfer
     * @param channelHandle Channel handle
     * @param config Transfer configuration
     * @return true if configured successfully, false otherwise
     */
#if SOC_GDMA_SUPPORTED
    bool configureTransfer(gdma_channel_handle_t channelHandle, const DmaTransferConfig& config);
#else
    bool configureTransfer(void* channelHandle, const DmaTransferConfig& config);
#endif

    /**
     * @brief Start DMA transfer
     * @param channelHandle Channel handle
     * @return true if started successfully, false otherwise
     */
#if SOC_GDMA_SUPPORTED
    bool startTransfer(gdma_channel_handle_t channelHandle);
#else
    bool startTransfer(void* channelHandle);
#endif

    /**
     * @brief Stop DMA transfer
     * @param channelHandle Channel handle
     * @return true if stopped successfully, false otherwise
     */
#if SOC_GDMA_SUPPORTED
    bool stopTransfer(gdma_channel_handle_t channelHandle);
#else
    bool stopTransfer(void* channelHandle);
#endif

    /**
     * @brief Reset DMA channel
     * @param channelHandle Channel handle
     * @return true if reset successfully, false otherwise
     */
#if SOC_GDMA_SUPPORTED
    bool resetChannel(gdma_channel_handle_t channelHandle);
#else
    bool resetChannel(void* channelHandle);
#endif

    /**
     * @brief Allocate DMA-capable memory
     * @param size Size of memory to allocate
     * @param caps Memory capabilities (default: DMA capable)
     * @return Pointer to allocated memory, or nullptr on failure
     */
    void* allocateDmaMemory(size_t size, uint32_t caps = MALLOC_CAP_DMA);

    /**
     * @brief Free DMA-capable memory
     * @param ptr Pointer to memory to free
     */
    void freeDmaMemory(void* ptr);

    /**
     * @brief Create DMA descriptor
     * @param buffer Pointer to data buffer
     * @param size Size of data
     * @param eof End of frame flag
     * @return Pointer to created descriptor, or nullptr on failure
     */
    DmaDescriptor* createDescriptor(void* buffer, size_t size, bool eof = true);

    /**
     * @brief Link DMA descriptors
     * @param desc1 First descriptor
     * @param desc2 Second descriptor
     * @return true if linked successfully, false otherwise
     */
    bool linkDescriptors(DmaDescriptor* desc1, DmaDescriptor* desc2);

    /**
     * @brief Free DMA descriptor
     * @param descriptor Pointer to descriptor to free
     */
    void freeDescriptor(DmaDescriptor* descriptor);

    /**
     * @brief Set DMA callback
     * @param channelHandle Channel handle
     * @param callback Callback function
     * @param userData User data passed to callback
     * @return true if set successfully, false otherwise
     */
#if SOC_GDMA_SUPPORTED
    bool setCallback(gdma_channel_handle_t channelHandle, DmaCallback callback, void* userData);
#else
    bool setCallback(void* channelHandle, DmaCallback callback, void* userData);
#endif

    /**
     * @brief Enable DMA channel
     * @param channelHandle Channel handle
     * @return true if enabled successfully, false otherwise
     */
#if SOC_GDMA_SUPPORTED
    bool enableChannel(gdma_channel_handle_t channelHandle);
#else
    bool enableChannel(void* channelHandle);
#endif

    /**
     * @brief Disable DMA channel
     * @param channelHandle Channel handle
     * @return true if disabled successfully, false otherwise
     */
#if SOC_GDMA_SUPPORTED
    bool disableChannel(gdma_channel_handle_t channelHandle);
#else
    bool disableChannel(void* channelHandle);
#endif

    /**
     * @brief Get DMA channel state
     * @param channelHandle Channel handle
     * @return Current channel state
     */
#if SOC_GDMA_SUPPORTED
    gdma_channel_state_t getChannelState(gdma_channel_handle_t channelHandle);
#else
    int getChannelState(void* channelHandle);
#endif

    /**
     * @brief Perform memory-to-memory copy using DMA
     * @param dst Destination address
     * @param src Source address
     * @param size Size of data to copy
     * @param channelHandle Channel handle (optional, will allocate if nullptr)
     * @return true if copy completed successfully, false otherwise
     */
#if SOC_GDMA_SUPPORTED
    bool memcpy(void* dst, const void* src, size_t size, gdma_channel_handle_t channelHandle = nullptr);
#else
    bool memcpy(void* dst, const void* src, size_t size, void* channelHandle = nullptr);
#endif

    /**
     * @brief Get default channel configuration
     * @return Default DMA channel configuration
     */
    static DmaChannelConfig getDefaultChannelConfig();

    /**
     * @brief Get default transfer configuration
     * @return Default DMA transfer configuration
     */
    static DmaTransferConfig getDefaultTransferConfig();

    /**
     * @brief Check if address is DMA capable
     * @param addr Address to check
     * @return true if DMA capable, false otherwise
     */
    static bool isDmaCapable(const void* addr);

private:
    static constexpr size_t MAX_CHANNELS = 8;  ///< Maximum number of DMA channels
    
#if SOC_GDMA_SUPPORTED
    gdma_channel_handle_t m_channels_[MAX_CHANNELS];  ///< Channel handles
#else
    void* m_channels_[MAX_CHANNELS];
#endif
    bool m_channelAllocated_[MAX_CHANNELS];           ///< Channel allocation status
    
    /**
     * @brief Find free channel slot
     * @return Index of free slot, or -1 if none available
     */
    int findFreeChannelSlot();
    
    /**
     * @brief Find channel slot by handle
     * @param channelHandle Channel handle to find
     * @return Index of channel slot, or -1 if not found
     */
#if SOC_GDMA_SUPPORTED
    int findChannelSlot(gdma_channel_handle_t channelHandle);
#else
    int findChannelSlot(void* channelHandle);
#endif
    
    /**
     * @brief Convert DmaDirection to GDMA direction
     * @param direction DMA direction
     * @return GDMA direction
     */
#if SOC_GDMA_SUPPORTED
    gdma_transfer_ability_t convertDirection(DmaDirection direction);
#endif
    
    /**
     * @brief Convert DmaPriority to GDMA priority
     * @param priority DMA priority
     * @return GDMA priority
     */
    int convertPriority(DmaPriority priority);
};

#endif // DMA_HPP

