#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_spi_flash.h"
#include "esp_vfs_spiffs.h"
#include <stdio.h>
#include <string.h>

#define TAG "Main"

#define GPIO_CRESET     5   // GP5 connected to CRESET of FPGA
#define GPIO_CDONE      6   // GP6 connected to CDONE of FPGA

// SPI Pins
#define PIN_NUM_MISO    13  // GP13 connected to SPI_SO (Slave Out)
#define PIN_NUM_MOSI    11  // GP11 connected to SPI_SI (Slave In)
#define PIN_NUM_CLK     12  // GP12 connected to SPI_SCK
#define PIN_NUM_CS      10  // GP10 connected to SPI_SS

spi_device_handle_t spi;

void init_gpio(void)
{
    // Configure CRESET as output
    gpio_pad_select_gpio(GPIO_CRESET);
    gpio_set_direction(GPIO_CRESET, GPIO_MODE_OUTPUT);
    gpio_set_level(GPIO_CRESET, 1); // Start with FPGA not in reset

    // Configure CDONE as input
    gpio_pad_select_gpio(GPIO_CDONE);
    gpio_set_direction(GPIO_CDONE, GPIO_MODE_INPUT);
}

void init_spi(void)
{
    esp_err_t ret;
    spi_bus_config_t buscfg = {
        .mosi_io_num = PIN_NUM_MOSI,
        .miso_io_num = PIN_NUM_MISO,
        .sclk_io_num = PIN_NUM_CLK,
        .quadwp_io_num = -1, // Not used
        .quadhd_io_num = -1, // Not used
        .max_transfer_sz = 4096, // Adjust as needed
    };

    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 1000000,           // 1 MHz (Adjust as per FPGA requirements)
        .mode = 0,                           // SPI mode 0
        .spics_io_num = PIN_NUM_CS,          // CS pin
        .queue_size = 1,                     // Transaction queue size
        .flags = SPI_DEVICE_HALFDUPLEX,      // Half-duplex mode
    };

    // Initialize the SPI bus
    ret = spi_bus_initialize(HSPI_HOST, &buscfg, SPI_DMA_CH_AUTO);
    ESP_ERROR_CHECK(ret);

    // Attach the FPGA to the SPI bus
    ret = spi_bus_add_device(HSPI_HOST, &devcfg, &spi);
    ESP_ERROR_CHECK(ret);
}

void init_spiffs(void)
{
    esp_vfs_spiffs_conf_t conf = {
        .base_path = "/spiffs",
        .partition_label = NULL,
        .max_files = 5,       // Maximum files that can be open at the same time
        .format_if_mount_failed = true
    };

    esp_err_t ret = esp_vfs_spiffs_register(&conf);

    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
        return;
    }

    size_t total = 0, used = 0;
    ret = esp_spiffs_info(NULL, &total, &used);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret));
    } else {
        ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
    }
}

void program_fpga(void)
{
    esp_err_t ret;
    FILE *f;
    uint8_t buffer[1024]; // Adjust buffer size as needed
    size_t read_bytes;

    // Hold FPGA in reset
    gpio_set_level(GPIO_CRESET, 0);
    vTaskDelay(pdMS_TO_TICKS(10)); // Wait for reset

    // Initialize SPI if not already initialized
    init_spi();

    // Release FPGA from reset
    gpio_set_level(GPIO_CRESET, 1);
    vTaskDelay(pdMS_TO_TICKS(10)); // Wait for FPGA to be ready

    // Open the bitstream file
    f = fopen("/spiffs/bitstream.bin", "rb");
    if (f == NULL) {
        ESP_LOGE(TAG, "Failed to open bitstream file");
        return;
    }

    // Read and send the bitstream
    while ((read_bytes = fread(buffer, 1, sizeof(buffer), f)) > 0) {
        spi_transaction_t t;
        memset(&t, 0, sizeof(t));
        t.length = read_bytes * 8; // Length in bits
        t.tx_buffer = buffer;
        ret = spi_device_transmit(spi, &t);
        ESP_ERROR_CHECK(ret);
    }

    fclose(f);

    // Check CDONE
    if (gpio_get_level(GPIO_CDONE) == 1) {
        ESP_LOGI(TAG, "FPGA configuration successful");
    } else {
        ESP_LOGE(TAG, "FPGA configuration failed");
    }
}

void app_main(void)
{
    // Initialize peripherals
    init_gpio();
    init_spiffs();

    // Program the FPGA
    program_fpga();

    // Continue with the rest of your application
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}