/**
 * @file Protocol.cpp
 * @brief StreamHub wire protocol implementation.
 */

#include "Protocol.h"

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <sstream>

namespace StreamHubClient {

/*---------------------------------------------------------------------------*/
/* Binary frame parser                                                        */
/*---------------------------------------------------------------------------*/

/* Read a uint32 LE from buf+offset; advance offset by 4. */
static uint32_t readU32(const uint8_t* buf, size_t& off, size_t len) {
    if (off + 4 > len) { return 0u; }
    uint32_t v = static_cast<uint32_t>(buf[off])
               | (static_cast<uint32_t>(buf[off+1]) << 8)
               | (static_cast<uint32_t>(buf[off+2]) << 16)
               | (static_cast<uint32_t>(buf[off+3]) << 24);
    off += 4;
    return v;
}

/* Read a uint16 LE from buf+offset; advance offset by 2. */
static uint16_t readU16(const uint8_t* buf, size_t& off, size_t len) {
    if (off + 2 > len) { return 0u; }
    uint16_t v = static_cast<uint16_t>(buf[off])
               | (static_cast<uint16_t>(buf[off+1]) << 8);
    off += 2;
    return v;
}

/* Read a float64 LE from buf+offset; advance offset by 8. */
static double readF64(const uint8_t* buf, size_t& off, size_t len) {
    if (off + 8 > len) { return 0.0; }
    double v;
    memcpy(&v, buf + off, 8);
    off += 8;
    return v;
}

bool ParseBinaryFrame(const uint8_t* buf, size_t len, DataFrame& out) {
    out.sourceId.clear();
    out.signals.clear();

    size_t off = 0;
    if (off >= len) { return false; }

    uint8_t version = buf[off++];
    if (version != 1u) { return false; }

    if (off >= len) { return false; }
    uint8_t idLen = buf[off++];

    if (off + idLen + 4 > len) { return false; }
    out.sourceId.assign(reinterpret_cast<const char*>(buf + off), idLen);
    off += idLen;

    uint32_t numSignals = readU32(buf, off, len);

    for (uint32_t s = 0; s < numSignals; s++) {
        uint16_t keyLen = readU16(buf, off, len);
        if (off + keyLen + 4 > len) { return false; }

        FrameSignal sig;
        sig.name.assign(reinterpret_cast<const char*>(buf + off), keyLen);
        off += keyLen;

        uint32_t pairCount = readU32(buf, off, len);
        if (off + static_cast<size_t>(pairCount) * 16u > len) { return false; }

        sig.t.resize(pairCount);
        sig.v.resize(pairCount);
        for (uint32_t i = 0; i < pairCount; i++) {
            sig.t[i] = readF64(buf, off, len);
        }
        for (uint32_t i = 0; i < pairCount; i++) {
            sig.v[i] = readF64(buf, off, len);
        }
        out.signals.push_back(std::move(sig));
    }
    return true;
}

bool ParseCaptureFrame(const uint8_t* buf, size_t len, CaptureFrame& out) {
    out.signals.clear();

    size_t off = 0;
    if (off >= len) { return false; }

    uint8_t version = buf[off++];
    if (version != 2u) { return false; }

    if (off + 8u * 3u + 4u > len) { return false; }
    out.trigTime = readF64(buf, off, len);
    out.preSec   = readF64(buf, off, len);
    out.postSec  = readF64(buf, off, len);

    uint32_t numSignals = readU32(buf, off, len);

    for (uint32_t s = 0; s < numSignals; s++) {
        uint16_t keyLen = readU16(buf, off, len);
        if (off + keyLen + 4 > len) { return false; }

        CaptureSignal sig;
        sig.key.assign(reinterpret_cast<const char*>(buf + off), keyLen);
        off += keyLen;

        uint32_t pairCount = readU32(buf, off, len);
        if (off + static_cast<size_t>(pairCount) * 16u > len) { return false; }

        sig.t.resize(pairCount);
        sig.v.resize(pairCount);
        for (uint32_t i = 0; i < pairCount; i++) {
            sig.t[i] = readF64(buf, off, len);
        }
        for (uint32_t i = 0; i < pairCount; i++) {
            sig.v[i] = readF64(buf, off, len);
        }
        out.signals.push_back(std::move(sig));
    }
    return true;
}

/*---------------------------------------------------------------------------*/
/* JSON helpers                                                               */
/*---------------------------------------------------------------------------*/

static bool jsonGetStr(const char* json, const char* key, char* out, size_t outSz) {
    char pat[128];
    snprintf(pat, sizeof(pat), "\"%s\":\"", key);
    const char* p = strstr(json, pat);
    if (!p) { return false; }
    p += strlen(pat);
    size_t i = 0;
    while (*p && *p != '"' && i < outSz - 1) { out[i++] = *p++; }
    out[i] = '\0';
    return true;
}

static bool jsonGetDouble(const char* json, const char* key, double& out) {
    char pat[128];
    snprintf(pat, sizeof(pat), "\"%s\":", key);
    const char* p = strstr(json, pat);
    if (!p) { return false; }
    p += strlen(pat);
    while (*p == ' ') { p++; }
    if (!*p) { return false; }
    out = strtod(p, nullptr);
    return true;
}

static bool jsonGetUint64(const char* json, const char* key, uint64_t& out) {
    double v = 0.0;
    if (!jsonGetDouble(json, key, v)) { return false; }
    out = static_cast<uint64_t>(v);
    return true;
}

static bool jsonGetInt(const char* json, const char* key, int& out) {
    double v = 0.0;
    if (!jsonGetDouble(json, key, v)) { return false; }
    out = static_cast<int>(v);
    return true;
}

/*---------------------------------------------------------------------------*/
/* JSON command builders                                                      */
/*---------------------------------------------------------------------------*/

std::string BuildPing()       { return "{\"type\":\"ping\"}"; }
std::string BuildGetSources() { return "{\"type\":\"getSources\"}"; }
std::string BuildGetStats()   { return "{\"type\":\"getStats\"}"; }

std::string BuildGetConfig(const std::string& sourceId) {
    char buf[256];
    snprintf(buf, sizeof(buf), "{\"type\":\"getConfig\",\"sourceId\":\"%s\"}",
             sourceId.c_str());
    return buf;
}

std::string BuildAddSource(const std::string& label, const std::string& addr,
                            const std::string& multicastGroup, uint16_t dataPort) {
    char buf[512];
    int n = snprintf(buf, sizeof(buf),
             "{\"type\":\"addSource\",\"label\":\"%s\",\"addr\":\"%s\"",
             label.c_str(), addr.c_str());
    if (!multicastGroup.empty() && n > 0 && n < (int)sizeof(buf)) {
        n += snprintf(buf + n, sizeof(buf) - n,
                      ",\"multicastGroup\":\"%s\"", multicastGroup.c_str());
    }
    if (dataPort != 0 && n > 0 && n < (int)sizeof(buf)) {
        n += snprintf(buf + n, sizeof(buf) - n,
                      ",\"dataPort\":%u", static_cast<unsigned>(dataPort));
    }
    if (n > 0 && n < (int)sizeof(buf)) {
        snprintf(buf + n, sizeof(buf) - n, "}");
    }
    return buf;
}

std::string BuildRemoveSource(const std::string& id) {
    char buf[256];
    snprintf(buf, sizeof(buf), "{\"type\":\"removeSource\",\"id\":\"%s\"}",
             id.c_str());
    return buf;
}

std::string BuildArm()    { return "{\"type\":\"arm\"}"; }
std::string BuildDisarm() { return "{\"type\":\"disarm\"}"; }
std::string BuildRearm()  { return "{\"type\":\"rearm\"}"; }

std::string BuildTrigStop(bool stopped) {
    char buf[64];
    snprintf(buf, sizeof(buf), "{\"type\":\"trigStop\",\"stopped\":%s}",
             stopped ? "true" : "false");
    return buf;
}

std::string BuildSaveSources() { return "{\"type\":\"saveSources\"}"; }

std::string BuildSetTrigger(const std::string& signalKey, const std::string& edge,
                             double threshold, double windowSec,
                             double prePercent, const std::string& mode) {
    char buf[512];
    snprintf(buf, sizeof(buf),
             "{\"type\":\"setTrigger\","
             "\"signal\":\"%s\",\"edge\":\"%s\","
             "\"threshold\":%.9g,\"windowSec\":%.9g,"
             "\"prePercent\":%.9g,\"mode\":\"%s\"}",
             signalKey.c_str(), edge.c_str(),
             threshold, windowSec, prePercent, mode.c_str());
    return buf;
}

std::string BuildZoom(uint32_t reqId, double t0, double t1, int n,
                       const std::string& signalsCsv) {
    char head[256];
    snprintf(head, sizeof(head),
             "{\"type\":\"zoom\",\"reqId\":%u,"
             "\"t0\":%.17g,\"t1\":%.17g,\"n\":%d,\"signals\":\"",
             static_cast<unsigned>(reqId), t0, t1, n);
    std::string out(head);
    out += signalsCsv;
    out += "\"}";
    return out;
}

std::string BuildHistoryZoom(uint32_t reqId, double t0, double t1, int n,
                              const std::string& signalsCsv) {
    char head[256];
    snprintf(head, sizeof(head),
             "{\"type\":\"historyZoom\",\"reqId\":%u,"
             "\"t0\":%.17g,\"t1\":%.17g,\"n\":%d,\"signals\":\"",
             static_cast<unsigned>(reqId), t0, t1, n);
    std::string out(head);
    out += signalsCsv;
    out += "\"}";
    return out;
}

std::string BuildHistoryInfo() {
    return "{\"type\":\"historyInfo\"}";
}

std::string BuildSetMaxPoints(uint32_t n) {
    char buf[128];
    snprintf(buf, sizeof(buf), "{\"type\":\"setMaxPoints\",\"maxPoints\":%u}",
             static_cast<unsigned>(n));
    return buf;
}

/*---------------------------------------------------------------------------*/
/* JSON event parsers                                                         */
/*---------------------------------------------------------------------------*/

std::string ParseType(const std::string& json) {
    char val[64] = "";
    jsonGetStr(json.c_str(), "type", val, sizeof(val));
    return val;
}

/* ---- Sources ------------------------------------------------------------ */

bool ParseSources(const std::string& json, std::vector<SourceInfo>& out) {
    out.clear();
    /* Iterate over source objects in the array */
    const char* p = json.c_str();
    while ((p = strstr(p, "\"id\":\"")) != nullptr) {
        SourceInfo info;
        char tmp[256] = "";

        jsonGetStr(p - 1, "id",    tmp, sizeof(tmp)); info.id    = tmp;
        jsonGetStr(p - 1, "label", tmp, sizeof(tmp)); info.label = tmp;
        jsonGetStr(p - 1, "addr",  tmp, sizeof(tmp)); info.addr  = tmp;
        jsonGetStr(p - 1, "state", tmp, sizeof(tmp)); info.state = tmp;
        double portF = 0.0;
        jsonGetDouble(p - 1, "port", portF);
        info.port = static_cast<uint32_t>(portF);

        out.push_back(info);
        p++; /* advance past current match */
    }
    return !out.empty();
}

/* ---- Config ------------------------------------------------------------ */

bool ParseConfig(const std::string& json, std::string& sourceId,
                 int& publishMode, std::vector<SignalMeta>& signals) {
    signals.clear();
    char tmp[256] = "";
    jsonGetStr(json.c_str(), "sourceId", tmp, sizeof(tmp));
    sourceId = tmp;

    double pm = 0.0;
    jsonGetDouble(json.c_str(), "publishMode", pm);
    publishMode = static_cast<int>(pm);

    /* Iterate over signal objects in the "signals" array */
    const char* p = json.c_str();
    while ((p = strstr(p, "\"name\":\"")) != nullptr) {
        SignalMeta m;

        jsonGetStr(p - 1, "name", tmp, sizeof(tmp)); m.name = tmp;
        jsonGetStr(p - 1, "unit", tmp, sizeof(tmp)); m.unit = tmp;

        int iv = 0;
        jsonGetInt(p - 1, "typeCode", iv); m.typeCode   = iv;
        jsonGetInt(p - 1, "quantType",iv); m.quantType  = iv;
        jsonGetInt(p - 1, "timeMode", iv); m.timeMode   = iv;
        iv = -1;
        jsonGetInt(p - 1, "timeSignalIdx", iv); m.timeSignalIdx = iv;

        double dv = 0.0;
        jsonGetDouble(p - 1, "samplingRate", dv); m.samplingRate = static_cast<float>(dv);
        jsonGetDouble(p - 1, "rangeMin",     dv); m.rangeMin     = dv;
        jsonGetDouble(p - 1, "rangeMax",     dv); m.rangeMax     = dv;

        double nr = 1.0, nc = 1.0;
        jsonGetDouble(p - 1, "numRows", nr);
        jsonGetDouble(p - 1, "numCols", nc);
        if (nr < 1.0) { nr = 1.0; }
        if (nc < 1.0) { nc = 1.0; }
        m.numRows     = static_cast<uint32_t>(nr);
        m.numCols     = static_cast<uint32_t>(nc);
        m.numElements = m.numRows * m.numCols;

        signals.push_back(m);
        p++;
    }
    return !sourceId.empty();
}

/* ---- Stats ------------------------------------------------------------- */

bool ParseStats(const std::string& json,
                std::vector<std::pair<std::string, SourceStats>>& out) {
    out.clear();
    /* Stats JSON: {"type":"stats","sources":{"id":{ ... },...}} */
    /* Parse by finding each nested object key */
    const char* sourcesBlock = strstr(json.c_str(), "\"sources\":{");
    if (!sourcesBlock) { return false; }
    sourcesBlock += strlen("\"sources\":{");

    const char* p = sourcesBlock;
    while (*p && *p != '}') {
        /* Skip to next key */
        while (*p && *p != '"') { p++; }
        if (!*p || *p != '"') { break; }
        p++; /* skip opening quote */

        /* Read source id */
        char id[128] = "";
        size_t i = 0;
        while (*p && *p != '"' && i < sizeof(id)-1) { id[i++] = *p++; }
        id[i] = '\0';
        if (*p == '"') { p++; } /* skip closing quote */
        if (*p == ':') { p++; } /* skip colon */
        if (*p != '{') { break; }

        /* Find end of this source object */
        const char* objStart = p;
        int depth = 0;
        while (*p) {
            if (*p == '{') { depth++; }
            else if (*p == '}') {
                depth--;
                if (depth == 0) { p++; break; }
            }
            p++;
        }

        /* Parse fields within [objStart, p) */
        std::string objStr(objStart, p - objStart);
        const char* o = objStr.c_str();

        SourceStats st;
        char tmp[64] = "";
        jsonGetStr(o, "state", tmp, sizeof(tmp)); st.state = tmp;

        uint64_t u64 = 0;
        jsonGetUint64(o, "totalReceived", u64); st.totalReceived = u64;
        jsonGetUint64(o, "totalLost",     u64); st.totalLost     = u64;

        double dv = 0.0;
        jsonGetDouble(o, "rateHz",        dv); st.rateHz        = dv;
        jsonGetDouble(o, "rateStdHz",     dv); st.rateStdHz     = dv;
        jsonGetDouble(o, "fragsPerCycle", dv); st.fragsPerCycle = dv;
        jsonGetDouble(o, "bytesPerCycle", dv); st.bytesPerCycle = dv;
        jsonGetDouble(o, "cycleAvgMs",    dv); st.cycleAvgMs    = dv;
        jsonGetDouble(o, "cycleStdMs",    dv); st.cycleStdMs    = dv;
        jsonGetDouble(o, "cycleMinMs",    dv); st.cycleMinMs    = dv;
        jsonGetDouble(o, "cycleMaxMs",    dv); st.cycleMaxMs    = dv;
        jsonGetDouble(o, "cycleHistMin",  dv); st.cycleHistMin  = dv;
        jsonGetDouble(o, "cycleHistMax",  dv); st.cycleHistMax  = dv;

        /* cycleHist: array of 20 numbers */
        const char* hArr = strstr(o, "\"cycleHist\":[");
        if (hArr) {
            hArr += strlen("\"cycleHist\":[");
            for (int hb = 0; hb < 20 && *hArr && *hArr != ']'; hb++) {
                while (*hArr == ',' || *hArr == ' ') { hArr++; }
                if (*hArr == ']') { break; }
                char* end = nullptr;
                st.cycleHist[hb] = strtod(hArr, &end);
                if (end == hArr) { break; }
                hArr = end;
            }
        }

        out.emplace_back(std::string(id), st);

        /* Skip comma between source entries */
        while (*p == ',' || *p == ' ') { p++; }
    }
    return !out.empty();
}

/* ---- Trigger state ---------------------------------------------------- */

bool ParseTriggerState(const std::string& json, TriggerStateMsg& out) {
    char tmp[64] = "";
    if (!jsonGetStr(json.c_str(), "state", tmp, sizeof(tmp))) { return false; }
    out.state = tmp;

    if (jsonGetStr(json.c_str(), "mode", tmp, sizeof(tmp))) { out.mode = tmp; }

    out.stopped = (strstr(json.c_str(), "\"stopped\":true") != nullptr);

    double tt = 0.0;
    out.hasTrigTime = jsonGetDouble(json.c_str(), "trigTime", tt);
    out.trigTime    = tt;
    return true;
}

/* ---- Zoom response ---------------------------------------------------- */

bool ParseZoom(const std::string& json, ZoomResponse& out) {
    out.signals.clear();
    out.reqId = 0;
    double reqIdF = 0.0;
    if (jsonGetDouble(json.c_str(), "reqId", reqIdF)) {
        out.reqId = static_cast<uint32_t>(reqIdF);
    }

    /* Parse signals object: {"src:sig":{"t":[...],"v":[...]},...} */
    const char* sigsBlock = strstr(json.c_str(), "\"signals\":{");
    if (!sigsBlock) { return false; }
    sigsBlock += strlen("\"signals\":{");

    const char* p = sigsBlock;
    while (*p && *p != '}') {
        while (*p && *p != '"') { p++; }
        if (!*p) { break; }
        p++;
        char sigName[128] = "";
        size_t i = 0;
        while (*p && *p != '"' && i < sizeof(sigName)-1) { sigName[i++] = *p++; }
        sigName[i] = '\0';
        if (*p == '"') { p++; }
        if (*p == ':') { p++; }

        ZoomSignal sig;
        sig.name = sigName;

        /* Parse t array */
        const char* tArr = strstr(p, "\"t\":[");
        if (tArr) {
            tArr += 5;
            while (*tArr && *tArr != ']') {
                while (*tArr == ',' || *tArr == ' ') { tArr++; }
                if (*tArr == ']') { break; }
                char* end = nullptr;
                double val = strtod(tArr, &end);
                if (end == tArr) { break; }
                sig.t.push_back(val);
                tArr = end;
            }
        }

        /* Parse v array */
        const char* vArr = strstr(p, "\"v\":[");
        if (vArr) {
            vArr += 5;
            while (*vArr && *vArr != ']') {
                while (*vArr == ',' || *vArr == ' ') { vArr++; }
                if (*vArr == ']') { break; }
                char* end = nullptr;
                double val = strtod(vArr, &end);
                if (end == vArr) { break; }
                sig.v.push_back(val);
                vArr = end;
            }
        }

        out.signals.push_back(std::move(sig));

        /* Advance past this signal's object */
        int depth = 0;
        while (*p) {
            if (*p == '{') { depth++; }
            else if (*p == '}') {
                depth--;
                if (depth == 0) { p++; break; }
            }
            p++;
        }
        while (*p == ',' || *p == ' ') { p++; }
    }
    return true;
}

/* ---- maxPointsUpdated ------------------------------------------------- */

bool ParseMaxPointsUpdated(const std::string& json, uint32_t& maxPoints) {
    double v = 0.0;
    if (!jsonGetDouble(json.c_str(), "maxPoints", v)) { return false; }
    maxPoints = static_cast<uint32_t>(v);
    return true;
}

/* ---- historyInfo -------------------------------------------------------- */

bool ParseHistoryInfo(const std::string& json, HistoryInfoMsg& out) {
    out.signals.clear();
    out.enabled = false;

    /* "enabled" is a JSON boolean (unquoted true/false), not a string */
    out.enabled = (strstr(json.c_str(), "\"enabled\":true") != nullptr);

    double df = 0.0;
    jsonGetDouble(json.c_str(), "durationHours", df);
    out.durationHours = df;

    double decF = 0.0;
    jsonGetDouble(json.c_str(), "decimation", decF);
    out.decimation = static_cast<uint32_t>(decF);

    /* Parse "signals":{...} block — same key iteration as zoom */
    const char *sig = strstr(json.c_str(), "\"signals\":{");
    if (!sig) { return out.enabled; }
    sig += 11; /* past '{"signals":{' */

    while (*sig) {
        /* Find next "key":{ */
        const char *q = strchr(sig, '"');
        if (!q) { break; }
        q++;
        const char *qEnd = strchr(q, '"');
        if (!qEnd) { break; }

        HistorySignalRange hr;
        hr.key = std::string(q, qEnd);

        /* Find the nested object */
        const char *brace = strchr(qEnd, '{');
        if (!brace) { break; }

        jsonGetDouble(brace, "t0", hr.t0);
        jsonGetDouble(brace, "t1", hr.t1);
        double cntF = 0.0, capF = 0.0;
        jsonGetDouble(brace, "count", cntF);
        jsonGetDouble(brace, "capacity", capF);
        hr.count    = static_cast<uint32_t>(cntF);
        hr.capacity = static_cast<uint32_t>(capF);

        out.signals.push_back(hr);

        /* Advance past the closing brace */
        const char *end = strchr(brace, '}');
        if (!end) { break; }
        sig = end + 1;
    }
    return true;
}

} /* namespace StreamHubClient */
