// #define DEBUG

#define BAUD_RATE 115200

#include "common.h"

#include "fpga.h"
#include "wifi.h"
#include "config.h"
#include "led.h"
#include "tty.h"
#include "boardinfo_esp.h"

#include <freertos/task_snapshot.h>
#include <esp_heap_caps.h>
#include <esp_task_wdt.h>
#include <esp_mac.h>

#include <USB.h>
#include <HardwareSerial.h>
#include <core_version.h>

#define PIN_USB_PWR_EN		7
#define PIN_USB_PWR_SINK	8

volatile bool do_log_config_status;

uint8_t efuse_default_mac[6];
char serial_number[16];		// Canonical board serial number

void setup_usb_ids()
{
    uint8_t * const mac = efuse_default_mac;

    esp_efuse_mac_get_default(mac);
    snprintf(serial_number, sizeof serial_number, "%02X%02X%02X-%02X%02X%02X",
	     mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

    USB.VID(0x4680);
    USB.PID(0x0882);
    USB.firmwareVersion(1);	// Do something smarter here
    USB.productName("MAX80 Network Controller");
    USB.manufacturerName("Peter & Per");
    USB.serialNumber(serial_number);
}

bool _spiram_broken;

void heap_info()
{
    size_t il = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
    size_t ia = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
    size_t it = heap_caps_get_total_size(MALLOC_CAP_INTERNAL);
    size_t sl = heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM);
    size_t sa = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
    size_t st = heap_caps_get_total_size(MALLOC_CAP_SPIRAM);

    char msg_buffer[128];
    snprintf(msg_buffer, sizeof msg_buffer,
	     "Heap: sram %zu/%zu/%zu, spiram %zu/%zu/%zu\r\n",
	     il, ia, it, sl, sa, st);

    fputs(msg_buffer, stdout);
    Serial.print(msg_buffer);

    setvar_uint(status_heap_sram_max, il);
    setvar_uint(status_heap_sram_free, ia);
    setvar_uint(status_heap_sram_size, it);
    setvar_uint(status_heap_spiram_max, sl);
    setvar_uint(status_heap_spiram_free, sa);
    setvar_uint(status_heap_spiram_size, st);
    _spiram_broken = st == 0;
}

#if 0
static void dump_config()
{
    printf("--- Configuration:\n");
    write_sysvars(stdout, false);
    printf("--- Status:\n");
    write_sysvars(stdout, true);
    printf("--- End configuration and status\n");
}
#endif

static void init_hw()
{
    // Start out with disabled shared I/O pins
    for (int i = 1; i <= 18; i++)
	pinMode(i, INPUT);

    // Query board info
    board_info_init();

    // Make sure FPGA nCE input (board 2.1+) is not accidentally pulled high
    fpga_enable_nce();

    // Configure USB power control. Try to detect 36k pulldown
    // resistor on USB_PWR_EN first, to determine board version 2
    // (on board v1, this pin in N/C.)
    //
    // This detection algorithm is sketchy at best. In the end the
    // better option probably would be to use the ADC mode of the input
    // pin...
    pinMode(PIN_USB_PWR_SINK, OUTPUT);		// IO8: USB_PWR_SINK
    digitalWrite(PIN_USB_PWR_SINK, 0);		// This is a power sink

    pinMode(PIN_USB_PWR_EN, OUTPUT);
    digitalWrite(PIN_USB_PWR_EN, 0);
    delayMicroseconds(50);

    // Configure LEDs
    led_init();
    led_set(LED_BLUE, LED_FLASH);	// ESP32 software initializing
}

#define _tostring(x) #x
#define tostring(x) _tostring(x)

static const char fwdate[] = __DATE__ " " __TIME__;
static const char arduino_esp32_ver[] = tostring(ARDUINO_ESP32_GIT_DESC);

static void set_build_status_info()
{
    setvar_str(status_max80_fw_date, fwdate);
    setvar_str(status_max80_fw_esp32_arduino, arduino_esp32_ver);
    setvar_str(status_max80_fw_esp32_idf, IDF_VER);
}

void setup() {
    init_hw();

    // Enable external PSRAM for heap
    heap_caps_malloc_extmem_enable(3000); // >= 3K allocations in PSRAM

    TTY::init();
    Serial.print("\r\n*** Hello, World! ***\r\n");
    sysvar_init();
    heap_info();

    printf("[FW]   MAX80 firmware compiled on %s\n", fwdate);
    printf("[PCB]  MAX80 board version: %s\n", board_info.version_str);
    setvar_str(status_max80_hw_ver, board_info.version_str);
    printf("[PCB]  MAX80 serial number: %s\n", serial_number);
    setvar_str(status_max80_hw_serial, serial_number);
    Serial.println("MAX80 start");

    init_config();
    set_build_status_info();
    fpga_service_init();
    fpga_service_enable(true);
    if (spiram_broken()) {
	const char spiram_broken_msg[] =
	    "WARNING: SPIRAM broken, not starting network\r\n";
	fputs(spiram_broken_msg, stdout);
	Serial.print(spiram_broken_msg);
    } else {
	SetupWiFi();
    }
    Serial.println("[RDY]");
    sysvar_print_updates = true;
    do_log_config_status = true; // Print configuration from main loop
    led_set(LED_BLUE, LED_ON);	// Software ready
}

static inline char task_state(eTaskState state)
{
    switch (state) {
    case eInvalid:
	return 'X';
    case eReady:
    case eRunning:
	return 'R';
    case eBlocked:
	return 'D';
    case eSuspended:
	return 'S';
    case eDeleted:
	return 'Z';
    default:
	return '?';
    }
}

static void dump_tasks(void)
{
    TaskHandle_t task = NULL;

    while (1) {
	task = pxTaskGetNext(task);
	if (!task)
	    break;

	printf("%-16s %c %2u\n",
	       pcTaskGetName(task),
	       task_state(eTaskGetState(task)),
	       uxTaskPriorityGet(task));
    }
}

void loop() {
    if (0) {
	printf("loop task: %s\n", pcTaskGetName(xTaskGetCurrentTaskHandle()));
	printf("idle task: %s\n", pcTaskGetName(xTaskGetIdleTaskHandle()));
	dump_tasks();
	heap_info();
	putchar('\n');
    }

    if (do_log_config_status) {
	do_log_config_status = false;
	heap_info();
	log_config_status();
    }

    fflush(stdout);

    TTY::ping();
    vTaskDelay(5 * configTICK_RATE_HZ);
}
