// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

// termdemo.c  -- SecLink terminal emulator demo
//
// A standalone DVX GUI application (NOT a DXE app  -- this has its own main())
// that combines the ANSI terminal widget with the SecLink encrypted serial
// stack to create a BBS terminal client.
//
// Unlike the DXE apps (progman, notepad, clock, dvxdemo) which run inside
// the DVX Shell, this is a freestanding program that initializes the GUI
// directly and manages its own event loop. It demonstrates how to use the
// DVX widget system outside the shell framework.
//
// Data flow:
//   User keystrokes -> terminal widget -> commWrite -> secLink (encrypt) -> UART
//   UART -> secLink (decrypt) -> onRecv -> ring buffer -> commRead -> terminal widget
//
// The ring buffer decouples the secLink receive callback (which fires during
// secLinkPoll) from the terminal widget's read cycle (which fires during
// dvxUpdate). This is necessary because the callback can fire at any time
// during polling, but the terminal widget expects to read data synchronously
// during its paint cycle.
//
// Usage: termdemo [com_port] [baud_rate]
//   com_port   -- 1-4 (default 1)
//   baud_rate  -- baud rate (default 115200)

#include "dvxApp.h"
#include "dvxWgt.h"
#include "secLink.h"
#include "security.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// ============================================================
// Constants
// ============================================================

#define CMD_FILE_QUIT   100

#define RECV_BUF_SIZE   4096

#define CHANNEL_DATA    0

// ============================================================
// Types
// ============================================================

// Bridges the secLink receive callback (asynchronous, fires during poll)
// with the terminal widget's comm interface (synchronous, called during
// widget paint). The ring buffer allows these two timing domains to
// communicate without blocking.
typedef struct {
    SecLinkT    *link;
    AppContextT *app;
    WidgetT     *term;
    uint8_t      recvBuf[RECV_BUF_SIZE];
    int32_t      recvHead;  // written by onRecv callback
    int32_t      recvTail;  // read by commRead during widget paint
} TermContextT;

// ============================================================
// Prototypes
// ============================================================

static int32_t commRead(void *ctx, uint8_t *buf, int32_t maxLen);
static int32_t commWrite(void *ctx, const uint8_t *data, int32_t len);
static void idlePoll(void *ctx);
static void onCloseCb(WindowT *win);
static void onMenuCb(WindowT *win, int32_t menuId);
static void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel);


// ============================================================
// commRead  -- drain receive ring buffer for terminal widget
// ============================================================

static int32_t commRead(void *ctx, uint8_t *buf, int32_t maxLen) {
    TermContextT *tc = (TermContextT *)ctx;
    int32_t       count = 0;

    while (count < maxLen && tc->recvTail != tc->recvHead) {
        buf[count++]  = tc->recvBuf[tc->recvTail];
        tc->recvTail = (tc->recvTail + 1) % RECV_BUF_SIZE;
    }

    return count;
}


// ============================================================
// commWrite  -- send keystrokes via secLink (encrypted, channel 0)
// ============================================================

static int32_t commWrite(void *ctx, const uint8_t *data, int32_t len) {
    TermContextT *tc = (TermContextT *)ctx;
    int           rc = secLinkSend(tc->link, data, len, CHANNEL_DATA, true, false);

    return (rc == SECLINK_SUCCESS) ? len : 0;
}


// ============================================================
// idlePoll  -- poll serial link instead of yielding CPU
// ============================================================

static void idlePoll(void *ctx) {
    TermContextT *tc = (TermContextT *)ctx;
    secLinkPoll(tc->link);
}


// ============================================================
// onCloseCb  -- quit when the terminal window is closed
// ============================================================

static void onCloseCb(WindowT *win) {
    AppContextT *ctx = (AppContextT *)win->userData;

    if (ctx) {
        dvxQuit(ctx);
    }
}


// ============================================================
// onMenuCb  -- handle menu commands
// ============================================================

static void onMenuCb(WindowT *win, int32_t menuId) {
    AppContextT *ctx = (AppContextT *)win->userData;

    switch (menuId) {
        case CMD_FILE_QUIT:
            if (ctx) {
                dvxQuit(ctx);
            }
            break;
    }
}


// ============================================================
// onRecv  -- secLink receive callback, fills ring buffer
// ============================================================

static void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel) {
    (void)channel;
    TermContextT *tc = (TermContextT *)ctx;

    for (int i = 0; i < len; i++) {
        int32_t next = (tc->recvHead + 1) % RECV_BUF_SIZE;

        if (next == tc->recvTail) {
            break;
        }

        tc->recvBuf[tc->recvHead] = data[i];
        tc->recvHead              = next;
    }
}


// ============================================================
// main
// ============================================================

int main(int argc, char *argv[]) {
    int     comPort  = 0;
    int32_t baudRate = 115200;

    // Parse command line
    if (argc >= 2) {
        if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
            printf("Usage: termdemo [com_port] [baud_rate]\n");
            printf("  com_port  -- 1-4 (default 1)\n");
            printf("  baud_rate -- baud rate (default 115200)\n");
            return 0;
        }

        comPort = atoi(argv[1]) - 1;

        if (comPort < 0 || comPort > 3) {
            fprintf(stderr, "Invalid COM port (must be 1-4)\n");
            return 1;
        }
    }

    if (argc >= 3) {
        baudRate = atol(argv[2]);

        if (baudRate <= 0) {
            fprintf(stderr, "Invalid baud rate\n");
            return 1;
        }
    }

    printf("SecLink Terminal Demo\n");
    printf("COM%d at %ld 8N1\n\n", comPort + 1, (long)baudRate);

    // Seed the RNG with hardware entropy
    printf("Gathering entropy...\n");
    uint8_t entropy[16];
    int     got = secRngGatherEntropy(entropy, sizeof(entropy));
    secRngSeed(entropy, got);

    // Initialize terminal context
    TermContextT tc;
    memset(&tc, 0, sizeof(tc));

    // Open secLink on the specified COM port
    printf("Opening SecLink on COM%d...\n", comPort + 1);
    tc.link = secLinkOpen(comPort, baudRate, 8, 'N', 1, 0, onRecv, &tc);

    if (!tc.link) {
        fprintf(stderr, "Failed to open SecLink\n");
        return 1;
    }

    // Perform DH key exchange (blocks until both sides complete)
    printf("Performing key exchange (waiting for remote side)...\n");
    int rc = secLinkHandshake(tc.link);

    if (rc != SECLINK_SUCCESS) {
        fprintf(stderr, "Handshake failed (%d)\n", rc);
        secLinkClose(tc.link);
        return 1;
    }

    printf("Secure link established.\n\n");

    // Initialize DVX GUI
    AppContextT ctx;

    printf("Initializing video...\n");

    if (dvxInit(&ctx, 1024, 768, 16) != 0) {
        fprintf(stderr, "Failed to initialize DVX GUI\n");
        secLinkClose(tc.link);
        return 1;
    }

    tc.app = &ctx;

    // Create the terminal window
    WindowT *win = dvxCreateWindow(&ctx, "SecLink Terminal", 40, 30, 680, 460, false);

    if (!win) {
        dvxShutdown(&ctx);
        secLinkClose(tc.link);
        return 1;
    }

    win->userData = &ctx;
    win->onClose  = onCloseCb;
    win->onMenu   = onMenuCb;

    // File menu
    MenuBarT *bar = wmAddMenuBar(win);

    if (bar) {
        MenuT *fileMenu = wmAddMenu(bar, "File");

        if (fileMenu) {
            wmAddMenuItem(fileMenu, "Quit", CMD_FILE_QUIT);
        }
    }

    // Widget tree: terminal + status bar
    WidgetT *root = wgtInitWindow(&ctx, win);
    WidgetT *term = wgtAnsiTerm(root, 80, 25);

    term->weight = 100;
    wgtAnsiTermSetScrollback(term, 1000);
    tc.term = term;

    // The comm interface is the glue between the terminal widget and the
    // secLink transport. The widget calls commRead during its paint cycle
    // to get new data to display, and commWrite when the user types to send
    // keystrokes over the encrypted link.
    wgtAnsiTermSetComm(term, &tc, commRead, commWrite);

    // Status bar showing connection info
    WidgetT *sb = wgtStatusBar(root);
    char     statusText[64];
    snprintf(statusText, sizeof(statusText), "COM%d %ld 8N1 [Encrypted]", comPort + 1, (long)baudRate);
    wgtLabel(sb, statusText);

    // Fit window to widget tree
    dvxFitWindow(&ctx, win);

    // Register an idle callback so the GUI event loop polls secLink during
    // idle time (when there are no mouse/keyboard events to process). This
    // ensures incoming data is processed even when the user isn't interacting
    // with the terminal. Without this, data would only arrive during the
    // fixed-rate dvxUpdate calls.
    ctx.idleCallback = idlePoll;
    ctx.idleCtx      = &tc;

    // Main loop: poll secLink for incoming data, then dvxUpdate processes
    // input events, repaints dirty widgets (including the terminal, which
    // calls commRead to consume the ring buffer), and flushes to the LFB.
    while (ctx.running) {
        secLinkPoll(tc.link);

        if (!dvxUpdate(&ctx)) {
            break;
        }
    }

    // Cleanup
    dvxShutdown(&ctx);
    secLinkClose(tc.link);

    printf("SecLink Terminal Demo ended.\n");

    return 0;
}
