//----------------------------------------------------------------------------------------
//
//   Zaccaria Gen2 Interface
//
//   This file contains all the functions specific to Zaccaria Gen2
//   - receiving and decoding input events
//   - sync signals, i.e. soundcard sending synchronisation signals to MPU
//
//   (C) 2026 colrhon.org
//   This program is released under the Creative Commons Public License, CC BY-NC-SA 4.0
//


#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "driver/gptimer.h"
#include <esp_system.h>
#include "esp_log.h"
#include "esp_check.h"
#include "soc/gpio_struct.h"
#include "rom/ets_sys.h"

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

// Zaccaria audio board is based on ESP32 S3
#ifdef ESP32_S3

#define ESP_INTR_FLAG_DEFAULT 0
#define UART_PORT_NUM UART_NUM_2
extern StreamBufferHandle_t xpinevt;
extern uint16_t gconf[];
extern const char mount_point[];

//---------------------------------------------------------------------------------------------------
//
// Synchronisation functions
//
// Generate hardware sync signals over the ACTSND and ACTSPK bus lines
// Note: ACTSND und ACTSPK behave the same way, no difference
//

static gptimer_handle_t gptimer = NULL;

// indicate finite sound begin
void ZaccSoundBegin(void) {
    gpio_set_level(ACTSND,1);
    gpio_set_level(ACTSPK,1);
}

// indicate finite sound end
void ZaccSoundEnd(void) {
    // check for active timer to avoid conflicts with looping sounds
    uint64_t count;
    gptimer_get_raw_count(gptimer,&count);
    if (count == 0) {
        gpio_set_level(ACTSND,0);
        gpio_set_level(ACTSPK,0);
    }
}

// indicate begin of looping sound
void ZaccSoundLoop(void) {
    gpio_set_level(ACTSND,1);
    gpio_set_level(ACTSPK,1);
    gptimer_set_raw_count(gptimer,0);
    gptimer_start(gptimer);
}

// timer expiry after 4ms
static bool ActSnd_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx) {
//  TP_SET();
    gptimer_stop(timer);
    gptimer_set_raw_count(gptimer,0);
    gpio_set_level(ACTSND,0);
    gpio_set_level(ACTSPK,0);
//  TP_CLR();
    return false;
}

// switch off sync lines
void ZaccOffSync(void) {

    // output ACTSND and ACTSPK on GPIO
    gpio_config_t io_conf1 = {0};
    io_conf1.mode = GPIO_MODE_OUTPUT;
    io_conf1.intr_type = GPIO_INTR_DISABLE;
    io_conf1.pin_bit_mask =
            (1ULL << ACTSND)
        |   (1ULL << ACTSPK);
    io_conf1.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf1.pull_up_en = GPIO_PULLUP_DISABLE;
    ESP_ERROR_CHECK(gpio_config(&io_conf1));

    // do not pull down lines (open collector)
    gpio_set_level(ACTSND,1);
    gpio_set_level(ACTSPK,1);
}

// setup sync interface
void ZaccInitSync(void) {

    // output ACTSND and ACTSPK on GPIO
    gpio_config_t io_conf1 = {0};
    io_conf1.mode = GPIO_MODE_OUTPUT;
    io_conf1.intr_type = GPIO_INTR_DISABLE;
    io_conf1.pin_bit_mask =
            (1ULL << ACTSND)
        |   (1ULL << ACTSPK);
    io_conf1.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf1.pull_up_en = GPIO_PULLUP_ENABLE;
    ESP_ERROR_CHECK(gpio_config(&io_conf1));
    gpio_set_level(ACTSND,1);
    gpio_set_level(ACTSPK,1);
    
    gptimer_config_t timer_config = {
        .clk_src = GPTIMER_CLK_SRC_DEFAULT,
        .direction = GPTIMER_COUNT_UP,
        .resolution_hz = 1 * 1000 * 1000,   // 1 tick equals 1 microsecond
    };
    ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));

    gptimer_alarm_config_t alarm_config = {
        .alarm_count = 4000,                // 4ms
        .flags.auto_reload_on_alarm = false,
    };
    ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));

    gptimer_event_callbacks_t cbs = {
        .on_alarm = ActSnd_alarm_cb,
    };
    ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));

    // enable the timer
    ESP_ERROR_CHECK(gptimer_enable(gptimer));

}


//----------------------------------------------------------------------------------------
//
// Log on SD card
//

static void Log(char *lstr) {
    char buf[30];
    sprintf(buf,"%s/zacclog.txt",mount_point);
    FILE *stm = fopen(buf,"a");
    if (stm == NULL) printf("open log file failed\n");
    else {
        fprintf(stm,lstr);
        fclose(stm);
        // ets_printf(lstr);
    }
    uart_write_bytes(UART_PORT_NUM,(const char*)lstr,strlen(lstr));
}

//----------------------------------------------------------------------------------------
//
// Fifo buffer
//

#define FF2_SIZE 128 // must be a value 2^n
#define FF2_MASK (FF2_SIZE-1)

typedef struct {
    uint32_t tick;  // OS tick time
    uint8_t cmd;    // MPU => Audio command
} Ccmd;

static struct Ff2 {
    Ccmd cent[FF2_SIZE];
    uint16_t read;   // points to oldest entry
    uint16_t write;  // points to next empty slot
} ff2 = {{}, 0, 0};

// Put value into the buffer, returns 0 if buffer is full
//
static inline uint8_t Ff2In(uint32_t tick, uint8_t cmd) {
    uint16_t next = ((ff2.write + 1) & FF2_MASK);
    if (ff2.read == next) return 0;
    ff2.cent[ff2.write].tick = tick;
    ff2.cent[ff2.write].cmd = cmd;
    ff2.write = next;
    return 1;
}

// Get a value from the buffer, return 0 if buffer empty
//
static inline uint8_t Ff2Out(uint32_t *t, uint8_t *p) {
    if (ff2.read == ff2.write) return 0;
    *t = ff2.cent[ff2.read].tick;
    *p = ff2.cent[ff2.read].cmd;
    ff2.read = (ff2.read+1) & FF2_MASK;
    return 1;
}


//----------------------------------------------------------------------------------------
//
// UART 
//

#define UART_BAUD_RATE 115200
#define UART_RX_PIN RX_SDA
#define UART_TX_PIN TX_SCL
#define BUFSIZE 1024

static void SetupUART() {

    // configure parameters of UART driver, assign pins and install the driver
    uart_config_t uart_config = {
        .baud_rate = UART_BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity    = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
    };
    ESP_ERROR_CHECK(uart_param_config(UART_PORT_NUM, &uart_config));
    ESP_ERROR_CHECK(uart_set_pin(UART_PORT_NUM, UART_TX_PIN, UART_RX_PIN, -1, -1));
    // define level if uart plug is not inserted
    gpio_pullup_en(UART_RX_PIN);
    ESP_ERROR_CHECK(uart_driver_install(UART_PORT_NUM, BUFSIZE, BUFSIZE, 20, NULL, 0));
}


//----------------------------------------------------------------------------------------
//
// Read from Sound Cmd Interface
//

#if 0
static uint8_t ReadSoundCmd() {
// using gpio_get_level() is too slow
    uint8_t sbyte = 0;
    uint32_t gin = GPIO.in;
    if (gin & (1UL << HS6)) sbyte |= 0b01000000;
    if (gin & (1UL << HS5)) sbyte |= 0b00100000;
    if (gin & (1UL << HS4)) sbyte |= 0b00010000;
    if (gin & (1UL << HS3)) sbyte |= 0b00001000;
    if (gin & (1UL << HS2)) sbyte |= 0b00000100;
    if (gin & (1UL << HS1)) sbyte |= 0b00000010;
    if (gin & (1UL << HS0)) sbyte |= 0b00000001; // LSBit S8
    Ff2In((uint32_t)xTaskGetTickCount(),sbyte);
    return sbyte;
}
#endif

static inline void ReadSoundCmd(void) {
    // read the state of MPU => Audio data interface, bit0..bit6
    uint8_t sbyte = 0;
    if (gpio_get_level(HS6)) sbyte |= 0b01000000;
    if (gpio_get_level(HS5)) sbyte |= 0b00100000;
    if (gpio_get_level(HS4)) sbyte |= 0b00010000;
    if (gpio_get_level(HS3)) sbyte |= 0b00001000;
    if (gpio_get_level(HS2)) sbyte |= 0b00000100;
    if (gpio_get_level(HS1)) sbyte |= 0b00000010;
    if (gpio_get_level(HS0)) sbyte |= 0b00000001; // LSBit
    Ff2In((uint32_t)xTaskGetTickCount(),sbyte);
//    ets_printf("%lu %u 0x%X\n",(uint32_t)xTaskGetTickCount(),bp,sbyte);
}

static void SndIsr(void* arg) {
//  TP_SET();
    ets_delay_us(10);   // adjust
    ReadSoundCmd();
//  TP_CLR();
}


//----------------------------------------------------------------------------------------
//
// Zaccaria Interface Events
//

void ZaccIf(void *pvParameters) {

	// input bit0..6 on GPIO (7 data bits)
    gpio_config_t io_conf2 = {0};
    io_conf2.mode = GPIO_MODE_INPUT;
    io_conf2.intr_type = GPIO_INTR_DISABLE;
    io_conf2.pin_bit_mask =
            (1ULL << HS6)
        |   (1ULL << HS5)
        |   (1ULL << HS4)
        |   (1ULL << HS3)
        |   (1ULL << HS2)
        |   (1ULL << HS1)
        |   (1ULL << HS0);
    io_conf2.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf2.pull_up_en = GPIO_PULLUP_DISABLE;
    ESP_ERROR_CHECK(gpio_config(&io_conf2));
    
	// interrupt HS7 from MPU
    gpio_config_t io_conf = {0};
    io_conf.mode = GPIO_MODE_INPUT;
    io_conf.intr_type = GPIO_INTR_NEGEDGE;
    io_conf.pin_bit_mask =
        (1ULL << HS7);
    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    ESP_ERROR_CHECK(gpio_config(&io_conf));

    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
    gpio_isr_handler_add(HS7, SndIsr, (void*)HS7);

    SetupUART();
    // setvbuf(stdout,NULL,_IONBF,0); // xxxxx

    uint32_t log = 0;
    if ((gconf[CONF_LOG] == CONF_LOG_YES) || (gconf[CONF_LOG] == CONF_LOG_ONLY) || (gpio_get_level(DIP_DSB) == 0)) log = 1;

    // startup mark
    if (log) Log("Zaccaria startup\n");
    
    gpio_intr_enable(HS7);
    int first76 = 0;
    while(1) {
        Rxcmd xcmd;
        uint32_t tick;
        uint8_t scmd;
        while (Ff2Out(&tick,&scmd)) {
            if (log) {
                char buf[30];
                sprintf(buf,"%lu %lu 0x%X\n",log,tick,scmd);
                Log(buf);
                log++;
            }
            switch (scmd) {
            case 0x02: // kill all sounds
                xcmd.cmd = 'k'; xcmd.arg = scmd;
                xStreamBufferSend(xpinevt,&xcmd,sizeof(Rxcmd),1);
                break;
            case 0x17: // ignore; at the beginning of (some) speach
                break;
            case 0x71:
                xcmd.cmd = 'n'; xcmd.arg = 0x75; // increase nmp (next member to play) of group 0x75
                xStreamBufferSend(xpinevt,&xcmd,sizeof(Rxcmd),1);
                xcmd.cmd = 'p'; xcmd.arg = 0x71; // play next of group 0x71
                xStreamBufferSend(xpinevt,&xcmd,sizeof(Rxcmd),1);
                break;
            case 0x73: // kill all sounds and reset sound groups
                xcmd.cmd = 't'; xcmd.arg = scmd;
                xStreamBufferSend(xpinevt,&xcmd,sizeof(Rxcmd),1);
                first76 = 1;
                break;
            case 0x75:
                xcmd.cmd = 'n'; xcmd.arg = 0x71; // increase nmp (next member to play) of group 0x71
                xStreamBufferSend(xpinevt,&xcmd,sizeof(Rxcmd),1);
                xcmd.cmd = 'p'; xcmd.arg = 0x75; // play next of group 0x75
                xStreamBufferSend(xpinevt,&xcmd,sizeof(Rxcmd),1);
                break;
            case 0x76: // play only 1 time after 0x73
                if (first76) {
                    xcmd.cmd = 'p'; xcmd.arg = scmd;
                    xStreamBufferSend(xpinevt,&xcmd,sizeof(Rxcmd),1);
                    first76 = 0;
                } else {
                    xcmd.cmd = 'k'; xcmd.arg = scmd;
                    xStreamBufferSend(xpinevt,&xcmd,sizeof(Rxcmd),1);
                }
                break;
            default: // play sound
                xcmd.cmd = 'p'; xcmd.arg = scmd;
                xStreamBufferSend(xpinevt,&xcmd,sizeof(Rxcmd),1);
                break;
            }
        }
        vTaskDelay(1);
    }

}

#endif // ESP32_S3
