/**
 * @file mcp2521_hardware_esp.hpp
 * @author AlexanderHD27
 * @brief 
 * @version 0.1
 * @date 2024-11-16
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#pragma once
#include "mcp2521_hardware_handle.hpp"

#ifdef ESP_PLATFORM

#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

static void handleInteruptTaskCallerFn(void *arg);
static void IRAM_ATTR gpio_isr_can_handler(void* arg);

/**
 * @brief Hardware handle for MCP2521 over SPI on ESP32
 * This is should not be used if multiple MCP2521 are on the same SPI bus
 */
class MCP2521_HardwareHandle_ESP : public I_MCP2521_HardwareHandle {
protected:
    void spi_transmit(spi_transaction_t *t);
    
    /**
     * @brief Sempahore, that is set every time the MCP2521 triggers an interrupt by the canInterruptTaskHandle
     */
    SemaphoreHandle_t canInterruptSemaphore = NULL;


    /**
     * @brief Handle to task that handles the MCP2521 interrupt
     * A Handle can be registered via the registerIntHandler method
     */
    TaskHandle_t canInterruptTaskHandle = NULL;

    spi_device_handle_t spi_device_handle;
    
private:
    spi_bus_config_t * spi_bus_config;
    char spi_tmp_buffer;
    
    spi_device_interface_config_t spi_device_config;

    /**
     * @brief Sempahore, that protects the SPI bus from being accessed by multiple tasks at the same time
     * 
     */
    SemaphoreHandle_t spiMutex = NULL;

    void * intHandlerArg = NULL;
    intHandlerFunction_t intHandler = NULL;

public:
    MCP2521_HardwareHandle_ESP(
        spi_host_device_t spi_host,
        spi_bus_config_t *bus_config,
        gpio_num_t mosi, 
        gpio_num_t miso, 
        gpio_num_t sclk, 
        gpio_num_t cs, 
        gpio_num_t int_pin
    );

    MCP2521_HardwareHandle_ESP(
        spi_host_device_t spi_host,
        spi_bus_config_t *bus_config,
        gpio_num_t cs, 
        gpio_num_t int_pin
    );

    static void initSPIBus(
        spi_host_device_t spi_host,
        gpio_num_t mosi, 
        gpio_num_t miso, 
        gpio_num_t sclk, 
        spi_bus_config_t *bus_config
    );

    void initSPIDevice(
        spi_host_device_t spi_host,
        gpio_num_t cs
    );

    void initPins(
        gpio_num_t int_pin
    );

    ~MCP2521_HardwareHandle_ESP();

    spi_bus_config_t * getSPI_bus_config();

    // ISR Stuff
    /**
     * @brief This function is called by the ISR, that is triggered by the MCP2521
     * This function just set the canInterruptSemaphore, that then unblocks the canInterruptTaskHandle.
     * So this function should be as short as possible, let the reset of the work be done in the canInterruptTaskHandle
     */
    void isr_can_interrupt();

    /**
     * This is the methode run as canInterruptTaskHandle.
     * It should continously aquire the canInterruptSemaphore and then call the intHandler
     */
    void handleIntteruptTaskFn();

    // Inherited from MCP2521_Hardware_Handle
    void execute(uint8_t cmd);
    void execute(uint8_t cmd, uint8_t address);

    void read(uint8_t cmd, uint8_t *data, size_t length, uint8_t address);
    void read(uint8_t cmd, uint8_t *data, size_t length);
    uint8_t read(uint8_t cmd, uint8_t address);
    uint8_t read(uint8_t cmd);
    
    void write(uint8_t cmd, uint8_t *data, size_t length, uint8_t address);
    void write(uint8_t cmd, uint8_t *data, size_t length);
    void write(uint8_t cmd, uint8_t data, uint8_t address);
    void write(uint8_t cmd, uint8_t data);

    void registerIntHandler(intHandlerFunction_t handler, void * arg);
};

#endif
