//----------------------------------------------------------------------------------------
//
//   "All In One" Firmware for PWAVplayer
//
//   Supports the following audio boards:
//   - Original prototype, flat input 1..10, based on ESP32_WROVER
//   - I2C bus variant of original prototype, based on ESP32_WROVER
//   - Gottlieb Sys80 GOSOWAV, based on ESP32_WROVER
//   - Williams System11 a/b/c, based on ESP32_S3 (work in progress)
//   - Zaccaria Gen2, production quality, based on ESP32_S3
//
//   (C) 2026 colrhon.org
//   This program is released under the Creative Commons Public License, CC BY-NC-SA 4.0
//

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/stream_buffer.h"
#include <esp_system.h>
#include "driver/sdmmc_host.h"
#include "driver/sdmmc_defs.h"
#include "driver/gptimer.h"
#include "driver/gpio.h"
#include "driver/sdspi_host.h"
#include "sdmmc_cmd.h"
#include "esp_vfs_fat.h"
#include "esp_check.h"
#include "esp_log.h"


// adjust
//#define DEBUG
#include "platform.h"
#include "pgpio.h"
#include "pwav.h"

// firmware file name
#define FIRMWARE_NAME "update.bin"

// config file name
#define CONFIG_NAME "config.txt"

// default sound theme
#define DEF_SNDDIR "orgsnd"

// ESP32 Cores
#define CORE_0 0
#define CORE_1 1

#define LOG "MAIN"

extern void CheckFWUpdate(char *fname);
extern void WAVPlayer(void *pvParameters);
extern void WAVDummy(void *pvParameters);
extern void RegSyncSig(uint16_t ind, void (*funp)(void));
extern StreamBufferHandle_t xpinevt;

//----------------------------------------------------------------------------------------
//
// Configuration data
//

uint16_t gconf[CONF_MAX];
char *gconfsd; // special case, sound directory

// read config file and override default values
//
static void ReadConfig(char *fname) {
    FILE *fp;
    if ((fp = fopen(fname,"r")) == NULL) {
        ESP_LOGI(LOG,"No config found, using defaults");
        return;
    }
#   define LBUF_SZ 100 
    char lbuf[LBUF_SZ];
    while (1) {
        if (fgets(lbuf,LBUF_SZ,fp) == NULL) break;
        char key[30];
        char val[30];
        int k = sscanf(lbuf," %[a-zA-Z0-9] = %[a-zA-Z0-9]",key,val);
        if (k == 2) {
            if (strcmp(key,"dac") == 0) { // DAC number of bits
                if (strcmp(val,"12") == 0) gconf[CONF_DAC] = CONF_DAC_12;
                if (strcmp(val,"16") == 0) gconf[CONF_DAC] = CONF_DAC_16;
            }
            if (strcmp(key,"mix") == 0) { // Mixer formula
                if (strcmp(val,"sum") == 0) gconf[CONF_MIX] = CONF_MIX_SUM;
                if (strcmp(val,"div2") == 0) gconf[CONF_MIX] = CONF_MIX_DIV2;
                if (strcmp(val,"sqrt") == 0) gconf[CONF_MIX] = CONF_MIX_SQRT;
            }
            if (strcmp(key,"evt") == 0) { // Event interface
                if (strcmp(val,"none") == 0) gconf[CONF_EVT] = CONF_EVT_NONE;
                if (strcmp(val,"flat") == 0) gconf[CONF_EVT] = CONF_EVT_FLAT;
                if (strcmp(val,"flat0") == 0) gconf[CONF_EVT] = CONF_EVT_FLAT0;
                if (strcmp(val,"bw11") == 0) gconf[CONF_EVT] = CONF_EVT_BW11;
                if (strcmp(val,"bg80") == 0) gconf[CONF_EVT] = CONF_EVT_BG80;
                if (strcmp(val,"zacc") == 0) gconf[CONF_EVT] = CONF_EVT_ZACC;
            }
            if (strcmp(key,"deb") == 0) { // Debounce
                gconf[CONF_DEB] = atoi(val);
            }
            if (strcmp(key,"rpd") == 0) { // Rest period (part of debouncing)
                gconf[CONF_RESTPD] = atoi(val);
            }                     
            if (strcmp(key,"ser") == 0) { // Serial interface
                if (strcmp(val,"none") == 0) gconf[CONF_SER] = CONF_SER_NONE;
                if (strcmp(val,"i2c") == 0) gconf[CONF_SER] = CONF_SER_I2C;
                if (strcmp(val,"uart") == 0) gconf[CONF_SER] = CONF_SER_UART;
            }
            if (strcmp(key,"addr") == 0) { // I2C address
                gconf[CONF_I2C_ADDR] = (uint16_t)strtol(val,NULL,16);
            }
            if (strcmp(key,"log") == 0) { // Log interface events
                if (strcmp(val,"no") == 0) gconf[CONF_LOG] = CONF_LOG_NO;
                if (strcmp(val,"yes") == 0) gconf[CONF_LOG] = CONF_LOG_YES;
                if (strcmp(val,"only") == 0) gconf[CONF_LOG] = CONF_LOG_ONLY;
            }
            if (strcmp(key,"volv") == 0) { // Adjust voice volume
                gconf[CONF_VOLV] = atoi(val);
            }
            if (strcmp(key,"vols") == 0) { // Adjust sound volume
                gconf[CONF_VOLS] = atoi(val);
            }
            if (strcmp(key,"stheme") == 0) { // Sound theme/directory
                gconfsd = (char *)malloc(sizeof(val));
                strcpy(gconfsd,val);
            }
        }
    }
    fclose(fp);
}

static void InitConfig(void) {
    // set all defaults
    gconf[CONF_DAC] = CONF_DAC_12;
    gconf[CONF_MIX] = CONF_MIX_DIV2;
    gconf[CONF_EVT] = CONF_EVT_FLAT;
    gconf[CONF_DEB] = 5; // 5ms
    gconf[CONF_RESTPD] = 60; // 60ms
    gconf[CONF_SER] = CONF_SER_NONE;
    gconf[CONF_I2C_ADDR] = 0x66;
    gconf[CONF_LOG] = CONF_LOG_NO;
    gconf[CONF_VOLV] = 100;
    gconf[CONF_VOLS] = 100;
    gconfsd = DEF_SNDDIR;
}

//----------------------------------------------------------------------------------------
//
// SDMMC
// mount SD card
//

// VFS FAT32
#define FAT_MAX_FILES 12  // max number of open files, see mount configuration for FatFS
const char mount_point[] = "/sdcard";
static sdmmc_card_t *card;

static esp_err_t MountSDCard(void) {

    sdmmc_host_t host = SDMMC_HOST_DEFAULT();
    host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; // 40MHz; disable to reduce to 20MHz

    sdmmc_slot_config_t slot = SDMMC_SLOT_CONFIG_DEFAULT();
    slot.width = 4;
#ifdef ESP32_S3
    slot.clk = SD_CLK;
    slot.cmd = SD_CMD;
    slot.d0 = SD_DAT0;
    slot.d1 = SD_DAT1;
    slot.d2 = SD_DAT2;
    slot.d3 = SD_DAT3;    
#endif
#ifdef ESP32_WROVER
    slot.d1 = SD_DAT1;
    slot.d2 = SD_DAT2;
    slot.d3 = SD_DAT3;    
    slot.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
#endif
    esp_vfs_fat_sdmmc_mount_config_t mount = {
        .format_if_mount_failed = false,
        .max_files = FAT_MAX_FILES,
    };

    esp_err_t err = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot, &mount, &card);
    if (err != ESP_OK) {
        ESP_LOGE(LOG,"SD card, mount failed: %s", esp_err_to_name(err));
        ESP_LOGE(LOG,"Check whether SD card is inserted");
        return err;
        }

    // print card properties
    sdmmc_card_print_info(stdout, card);
    
    return ESP_OK;
}

//----------------------------------------------------------------------------------------
//
// Main
//
//----------------------------------------------------------------------------------------

static void IndFatalError(void) {
#   ifdef ESP32_S3
    // for ESP32 S3 cards, indicate fatal error by turning on red LED
    extern void RepStatus(void *pvParameters);
    extern StreamBufferHandle_t xledevt;
    xledevt = xStreamBufferCreate(IP_BUF_SZ,1); // led event messages
    xTaskCreatePinnedToCore(&RepStatus, "RepStatus", 4096, NULL, (tskIDLE_PRIORITY + 0), NULL, CORE_1);
    Rxcmd xcmd;
    xcmd.cmd = 'f'; xcmd.arg = 0;
    xStreamBufferSend(xledevt,&xcmd,sizeof(Rxcmd),1);
#   endif
    }

void app_main(void) {

    // configure testpin, same for all cards
    gpio_config_t io_conf = {0};
    io_conf.intr_type = GPIO_INTR_DISABLE;
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pin_bit_mask = (1ULL << TESTPIN);
    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    ESP_ERROR_CHECK(gpio_config(&io_conf));
    
    if (MountSDCard() != ESP_OK) {
        IndFatalError();
        return;
        }

    char fpath0[30];
    sprintf(fpath0, "%s/%s",mount_point,FIRMWARE_NAME);
    CheckFWUpdate(fpath0);
    
    InitConfig();
    sprintf(fpath0, "%s/%s",mount_point,CONFIG_NAME);
    ReadConfig(fpath0);

    // sound event messages
    xpinevt = xStreamBufferCreate(IP_BUF_SZ,1);

    // reset sync signal functions
    RegSyncSig(SNDFB_RESET,NULL);

    // enforce single wav player instance
    int swp = 0;
    

/// Audiocards based on ESP32 S3
#ifdef ESP32_S3

    //===============================================================================================================
    //
    // Audiocard for Zaccaria Gen2
    //
    //===============================================================================================================
    // see https://www.flippermarkt.de/forum/threads/projekt-ersatz-fuer-die-tms5200-basierte-soundkarte-zaccaria-gen-2.289597/#post-2323217
    //
    if (gconf[CONF_EVT] == CONF_EVT_ZACC) {

        // from wavplayer.c
        extern void LedSrcActivate(uint16_t state);
        
        // from if_zacc.c
        extern void ZaccSoundBegin(void);
        extern void ZaccSoundEnd(void);
        extern void ZaccSoundLoop(void);
        extern void ZaccOffSync(void);
        extern void ZaccInitSync(void);
        extern void ZaccIf(void *pvParameters);

        // from status.c
        extern void RepStatus(void *pvParameters);
        extern StreamBufferHandle_t xledevt;
        
        // DIP switches
        gpio_config_t io_conf1 = {0};
        io_conf1.mode = GPIO_MODE_INPUT;
        io_conf1.intr_type = GPIO_INTR_DISABLE;
        io_conf1.pin_bit_mask = (1ULL << DIP_DSA) | (1ULL << DIP_DSB) | (1ULL << DIP_DSC);
        io_conf1.pull_down_en = GPIO_PULLDOWN_DISABLE;
        io_conf1.pull_up_en = GPIO_PULLUP_ENABLE;
        ESP_ERROR_CHECK(gpio_config(&io_conf1));

        // status LED controller
        xledevt = xStreamBufferCreate(IP_BUF_SZ,1); // led event messages
        xTaskCreatePinnedToCore(&RepStatus, "RepStatus", 4096, NULL, (tskIDLE_PRIORITY + 0), NULL, CORE_1);
        LedSrcActivate(1);

        // assign functions for sync singals, see if_zacc.c
        RegSyncSig(SNDFB_BEGIN,&ZaccSoundBegin);
        RegSyncSig(SNDFB_END,&ZaccSoundEnd);
        RegSyncSig(SNDFB_LOOP,&ZaccSoundLoop);
        RegSyncSig(SNDFB_OFF,&ZaccOffSync);
        RegSyncSig(SNDFB_INIT,&ZaccInitSync);

        // force original sound theme if DIP switch 3 is set
        if (gpio_get_level(DIP_DSC) == 1) gconfsd = DEF_SNDDIR;
        
        // start wav player
        if ((gconf[CONF_LOG] == CONF_LOG_ONLY) || (gpio_get_level(DIP_DSB) == 0)) {
            // use sound card as a log device only
            if (!swp) xTaskCreatePinnedToCore(&WAVDummy, "WAVDummy", 4096, NULL, tskIDLE_PRIORITY+10, NULL, CORE_0), swp++;
        } else {
            // normal operation
            if (!swp) xTaskCreatePinnedToCore(&WAVPlayer, "WAVplayer", 8192, NULL, tskIDLE_PRIORITY+10, NULL, CORE_0), swp++;
        }
        // run event interface task
        xTaskCreatePinnedToCore(&ZaccIf, "ZaccIf", 4096, NULL, (tskIDLE_PRIORITY + 3), NULL, CORE_1);
    }
    
    //===============================================================================================================
    //
    // Audiocard for Williams System11 a/b/c
    //
    //===============================================================================================================
    // see https://www.flippermarkt.de/forum/threads/universale-soundkarte-zwischenstand.284234/page-6#post-2301650
    //
    if (gconf[CONF_EVT] == CONF_EVT_BW11) {

        // from if_sys11.c
        extern void Sys11If(void *pvParameters);

        // work in progress
        //
        // tbd:
        // - generate sync signals
        // - decode events
        // - log
        //
        
        if (!swp) xTaskCreatePinnedToCore(&WAVPlayer, "WAVplayer", 8192, NULL, tskIDLE_PRIORITY+10, NULL, CORE_0), swp++;
        // run event interface task
        xTaskCreatePinnedToCore(&Sys11If, "Sys11If", 4096, NULL, (tskIDLE_PRIORITY + 3), NULL, CORE_1);
    }

#endif // ESP32_S3


/// Audiocards based on ESP32 WROVER
#ifdef ESP32_WROVER

    //===============================================================================================================
    //
    // Flat interface, input 1..10
    //
    //===============================================================================================================
    // see https://www.flippermarkt.de/forum/threads/universale-soundkarte-zwischenstand.284234/page-4#post-2284854
    //
    if (gconf[CONF_EVT] == CONF_EVT_FLAT) {
        // from if_flat.c
        extern void FlatIf(void *pvParameters);

        if (!swp) xTaskCreatePinnedToCore(&WAVPlayer, "WAVplayer", 8192, NULL, tskIDLE_PRIORITY+10, NULL, CORE_0), swp++;
        xTaskCreatePinnedToCore(&FlatIf, "FlatIf", 4096, NULL, (tskIDLE_PRIORITY + 3), NULL, CORE_1);
    }

    //===============================================================================================================
    //
    // Flat interface (old version), input 1..10
    //
    //===============================================================================================================
    // see https://www.flippermarkt.de/forum/threads/universale-soundkarte-zwischenstand.284234/page-4#post-2284854
    //
    if (gconf[CONF_EVT] == CONF_EVT_FLAT0) {
        // from if_flat.c
        extern void FlatIf0(void *pvParameters);

        if (!swp) xTaskCreatePinnedToCore(&WAVPlayer, "WAVplayer", 8192, NULL, tskIDLE_PRIORITY+10, NULL, CORE_0), swp++;
        xTaskCreatePinnedToCore(&FlatIf0, "FlatIf0", 4096, NULL, (tskIDLE_PRIORITY + 3), NULL, CORE_1);
    }

    //===============================================================================================================
    //
    // GOSOWAV Audiocard for Gottlieb Sys 80
    //
    //===============================================================================================================
    // see https://lisy.dev/gosowav.html
    //
    if (gconf[CONF_EVT] == CONF_EVT_BG80) {
        // from if_G80.c
        extern void G80If(void *pvParameters);

        if (!swp) xTaskCreatePinnedToCore(&WAVPlayer, "WAVplayer", 8192, NULL, tskIDLE_PRIORITY+10, NULL, CORE_0), swp++;
        xTaskCreatePinnedToCore(&G80If, "G80If", 4096, NULL, (tskIDLE_PRIORITY + 3), NULL, CORE_1);
    }

#endif // ESP32_WROVER

    //===============================================================================================================
    //
    // Serial interface I2C bus
    //
    //===============================================================================================================
    // see https://www.flippermarkt.de/forum/threads/universale-soundkarte-zwischenstand.284234/page-4#post-2282495
    //
    if (gconf[CONF_SER] == CONF_SER_I2C) {
        // from serial.c
        extern void SerialI2C(void *pvParameters);

        if (!swp) xTaskCreatePinnedToCore(&WAVPlayer, "WAVplayer", 8192, NULL, tskIDLE_PRIORITY+10, NULL, CORE_0), swp++;
        xTaskCreatePinnedToCore(&SerialI2C, "SerialI2C", 4096, NULL, (tskIDLE_PRIORITY + 2), NULL, CORE_1);
    }

    //===============================================================================================================
    //
    // Serial interface UART Rx/Tx
    //
    //===============================================================================================================
    //
    if (gconf[CONF_SER] == CONF_SER_UART) {
        // from serial.c
        extern void SerialUART(void *pvParameters);

        if (!swp) xTaskCreatePinnedToCore(&WAVPlayer, "WAVplayer", 8192, NULL, tskIDLE_PRIORITY+10, NULL, CORE_0), swp++;
        xTaskCreatePinnedToCore(&SerialUART, "SerialUART", 4096, NULL, (tskIDLE_PRIORITY + 2), NULL, CORE_1);
    }
}
