/*
 * ComAssistant 工作区「控制控件」文本行下行帧 —— 流式解析示例（ISO C99）
 *
 * 适用范围：PC 主界面未勾选 Unify 协议。勾选 Unify 时控件下发二进制包（dataType 0x20/0x21，
 * 数据段 [指令ID:1][数值:4] 小端），须使用 Unify 包头解析，不能复用本文件。
 *
 * 帧格式（与 PC 文本模式一致）：
 *   '{' workspace '}' '{' command '}' commandData  +  行结束（'\n' 或 "\r\n"）
 * 多条独立控制帧须各占一行：帧与帧之间仍以换行分段，不会在单行内串联多条完整帧（一行仅解析出一条命令）。
 *
 * UTF-8：文件与链路上 workspace/command/commandData 均为 UTF-8 字节（与 QString::toUtf8() 一致），
 * 本示例按字节拷贝，不做 Unicode 校验。
 *
 * 示例假设：USART RX 中断里逐字节 stream_parser_feed_byte；主循环里 stream_parser_dispatch_pending
 * 完成解析与回调（parse 仅发生在 dispatch，不在 line_accum_push 中）。
 *
 * 限制：workspace/command 段内不得含 '}'；仅以 '\n' 断行，'\r' 忽略；解析失败丢弃该行。
 * 未见到本行首个 '{' 前丢弃字节；缓冲从首个 '{' 起计数。
 * 单行正文最多 LINE_CAP-1 字节（从首个 '{' 算起）：更长且无换行时清空累积；合法帧超长须增大 LINE_CAP。
 * 单行槽 completed_line：若主循环未及时 dispatch，下一完整行可覆盖上一行。
 *
 * 一行内错位帧：parse_workspace_fields 从每个 '{' 尝试；commandData 仍以 '{' 开头则换下一 '{' 重试。
 *
 * 接入说明：见 workspace_tutorial_code.h 文末注释块。
 *
 * 调试：WS_TUT_DBG 默认关闭（WS_TUT_DEBUG 默认为 0）；编译定义 WS_TUT_DEBUG 为 1 可开启打印。
 * 开启后 line_accum_push（随 feed_byte）与 dispatch_pending 均可能 printf；USART 中断路径慎用。
 */

#include "workspace_tutorial_code.h"

#include <string.h>

static void line_accum_init(LineAccumulator *a)
{
    a->len = 0u;
    a->awaiting_brace = 1;
    WS_TUT_DBG("line_accum_init\n");
}

static int line_accum_push(LineAccumulator *a, unsigned char byte,
                           char *line_out)
{
    if (byte == '\r')
        return 0;

    if (byte == '\n') {
        if (a->len == 0u) {
            a->awaiting_brace = 1;
            WS_TUT_DBG("accum: LF, empty line (discarded)\n");
            return 0;
        }
        if (a->len >= LINE_CAP)
            a->len = LINE_CAP - 1u;
        a->buf[a->len] = '\0';
        memcpy(line_out, a->buf, a->len + 1u);
        WS_TUT_DBG("accum: line complete len=%zu text=%.96s\n",
                   a->len, line_out);
        a->len = 0u;
        a->awaiting_brace = 1;
        return 1;
    }

    if (a->awaiting_brace) {
        if (byte != '{')
            return 0;
        a->awaiting_brace = 0;
        WS_TUT_DBG("accum: first '{' of line, start capture\n");
    }

    if (a->len < LINE_CAP - 1u) {
        a->buf[a->len++] = byte;
    } else {
        WS_TUT_DBG("accum: buffer full, reset (await next '{')\n");
        a->len = 0u;
        a->awaiting_brace = 1;
    }

    return 0;
}

static int parse_workspace_fields_at(const char *line, WorkspaceFields *out)
{
    memset(out, 0, sizeof(*out));

    const char *p = line;

    if (*p != '{') {
        WS_TUT_DBG("parse_at: reject not starting with '{', head=0x%02x\n",
                   (unsigned)(unsigned char)*p);
        return -1;
    }
    ++p;

    const char *ws = p;
    while (*p != '\0' && *p != '}')
        ++p;
    if (*p != '}') {
        WS_TUT_DBG("parse_at: reject no '}' after workspace field\n");
        return -1;
    }

    size_t ws_len = (size_t)(p - ws);
    if (ws_len >= WS_CAP)
        ws_len = WS_CAP - 1u;
    memcpy(out->workspace, ws, ws_len);
    out->workspace[ws_len] = '\0';

    ++p;
    if (*p != '{') {
        WS_TUT_DBG("parse_at: reject missing second '{' before command\n");
        return -1;
    }
    ++p;

    const char *cmd = p;
    while (*p != '\0' && *p != '}')
        ++p;
    if (*p != '}') {
        WS_TUT_DBG("parse_at: reject no '}' after command field\n");
        return -1;
    }

    size_t cmd_len = (size_t)(p - cmd);
    if (cmd_len >= CMD_CAP)
        cmd_len = CMD_CAP - 1u;
    memcpy(out->command, cmd, cmd_len);
    out->command[cmd_len] = '\0';

    ++p;

    {
        size_t rest = strlen(p);
        if (rest >= DATA_CAP)
            rest = DATA_CAP - 1u;
        memcpy(out->data, p, rest);
        out->data[rest] = '\0';
    }

    out->ok = 1;
    WS_TUT_DBG("parse_at: ok ws=%.32s cmd=%.32s data=%.48s\n",
               out->workspace, out->command, out->data);
    return 0;
}

int parse_workspace_fields(const char *line, WorkspaceFields *out)
{
    memset(out, 0, sizeof(*out));

    if (line == NULL)
        return -1;

    const char *p = line;

    while ((p = strchr(p, '{')) != NULL) {
        WorkspaceFields tmp;

        if (parse_workspace_fields_at(p, &tmp) == 0 && tmp.ok) {
            if (tmp.data[0] == '{') {
                p = strchr(p + 1u, '{');
                continue;
            }
            memcpy(out, &tmp, sizeof(tmp));
            WS_TUT_DBG("parse_fields: ok ws=%.32s cmd=%.32s\n",
                       out->workspace, out->command);
            return 0;
        }

        p = strchr(p + 1u, '{');
    }

    memset(out, 0, sizeof(*out));
    WS_TUT_DBG("parse_fields: fail\n");
    return -1;
}

void stream_parser_init(StreamParser *sp)
{
    line_accum_init(&sp->line_acc);
    sp->completed_ready = 0;
    WS_TUT_DBG("stream_parser_init\n");
}

int stream_parser_feed_byte(StreamParser *sp, unsigned char b)
{
    if (!line_accum_push(&sp->line_acc, b, sp->line_buf))
        return 0;

    {
        size_t n = strlen(sp->line_buf);
        if (n >= LINE_CAP)
            n = LINE_CAP - 1u;
        memcpy(sp->completed_line, sp->line_buf, n + 1u);
    }
    sp->completed_ready = 1;
    WS_TUT_DBG("feed_byte: pending line -> completed_line\n");
    return 1;
}

int stream_parser_dispatch_pending(StreamParser *sp,
                                   WorkspacePacketCallback cb, void *ctx)
{
    if (!sp->completed_ready)
        return 0;

    char line_copy[LINE_CAP];
    memcpy(line_copy, sp->completed_line, sizeof(line_copy));
    line_copy[LINE_CAP - 1u] = '\0';
    sp->completed_ready = 0;

    WorkspaceFields f;
    if (parse_workspace_fields(line_copy, &f) != 0 || !f.ok) {
        WS_TUT_DBG("dispatch: parse failed, line=%.96s\n", line_copy);
        return 0;
    }

    WS_TUT_DBG("dispatch: calling cb ws=%.32s cmd=%.32s\n",
               f.workspace, f.command);
    if (cb != NULL)
        cb(&f, ctx);
    return 1;
}
