/**
 * @file WSFrame_client.h
 * @brief WebSocket frame helpers for the ImGui client (no MARTe2 dependency).
 *
 * Mirrors WSFrame.h from Source/Applications/StreamHub/ using standard C++ types.
 */

#pragma once

#include <cstdint>
#include <cstring>

namespace StreamHubClient {

static const uint8_t WS_OPCODE_CONTINUATION = 0x00u;
static const uint8_t WS_OPCODE_TEXT         = 0x01u;
static const uint8_t WS_OPCODE_BINARY       = 0x02u;
static const uint8_t WS_OPCODE_CLOSE        = 0x08u;
static const uint8_t WS_OPCODE_PING         = 0x09u;
static const uint8_t WS_OPCODE_PONG         = 0x0Au;

static const uint8_t WS_FIN_BIT  = 0x80u;
static const uint8_t WS_MASK_BIT = 0x80u;

/** @brief Encode a WebSocket frame header (server→client, no masking).
 * @return Number of header bytes written (2, 4, or 10). */
inline uint32_t WSEncodeHeader(uint8_t* buf, uint8_t opcode, uint64_t payloadLen) {
    buf[0] = WS_FIN_BIT | opcode;
    if (payloadLen < 126u) {
        buf[1] = static_cast<uint8_t>(payloadLen);
        return 2u;
    } else if (payloadLen < 65536u) {
        buf[1] = 126u;
        buf[2] = static_cast<uint8_t>((payloadLen >> 8u) & 0xFFu);
        buf[3] = static_cast<uint8_t>(payloadLen & 0xFFu);
        return 4u;
    } else {
        buf[1] = 127u;
        for (int i = 7; i >= 0; i--) {
            buf[2 + i] = static_cast<uint8_t>(payloadLen & 0xFFu);
            payloadLen >>= 8u;
        }
        return 10u;
    }
}

struct WSFrameHeader {
    bool     fin;
    uint8_t  opcode;
    bool     masked;
    uint64_t payloadLen;
    uint8_t  maskKey[4];
    uint32_t headerSize;
};

inline bool WSParseHeader(const uint8_t* buf, uint32_t bufLen, WSFrameHeader& hdr) {
    if (bufLen < 2u) { return false; }

    hdr.fin    = (buf[0] & WS_FIN_BIT) != 0u;
    hdr.opcode = buf[0] & 0x0Fu;
    hdr.masked = (buf[1] & WS_MASK_BIT) != 0u;

    uint32_t hdrSize = 2u;
    uint64_t plen    = static_cast<uint64_t>(buf[1] & 0x7Fu);

    if (plen == 126u) {
        if (bufLen < 4u) { return false; }
        plen = (static_cast<uint64_t>(buf[2]) << 8u) | static_cast<uint64_t>(buf[3]);
        hdrSize += 2u;
    } else if (plen == 127u) {
        if (bufLen < 10u) { return false; }
        plen = 0u;
        for (int i = 0; i < 8; i++) {
            plen = (plen << 8u) | static_cast<uint64_t>(buf[2 + i]);
        }
        hdrSize += 8u;
    }

    if (hdr.masked) {
        if (bufLen < hdrSize + 4u) { return false; }
        hdr.maskKey[0] = buf[hdrSize];
        hdr.maskKey[1] = buf[hdrSize + 1u];
        hdr.maskKey[2] = buf[hdrSize + 2u];
        hdr.maskKey[3] = buf[hdrSize + 3u];
        hdrSize += 4u;
    } else {
        hdr.maskKey[0] = hdr.maskKey[1] = hdr.maskKey[2] = hdr.maskKey[3] = 0u;
    }

    hdr.payloadLen = plen;
    hdr.headerSize = hdrSize;
    return true;
}

inline void WSUnmask(uint8_t* payload, uint32_t len, const uint8_t mask[4]) {
    for (uint32_t i = 0u; i < len; i++) {
        payload[i] ^= mask[i % 4u];
    }
}

/* ── SHA-1 (for WebSocket handshake) ──────────────────────────────────────── */

inline void WS_SHA1(const uint8_t* data, uint32_t len, uint8_t digest[20]) {
    /* FIPS 180-4 SHA-1 — sufficient for a 60-byte input (key+GUID) */
    uint32_t h0 = 0x67452301u, h1 = 0xEFCDAB89u, h2 = 0x98BADCFEu,
             h3 = 0x10325476u, h4 = 0xC3D2E1F0u;

    auto rot32 = [](uint32_t v, int n) -> uint32_t {
        return (v << n) | (v >> (32 - n));
    };

    /* Build padded message */
    uint32_t bitLen = len * 8u;
    uint32_t padLen = (len % 64u < 56u) ? (56u - len % 64u) : (120u - len % 64u);
    uint32_t msgLen = len + padLen + 8u;
    uint8_t* msg = new uint8_t[msgLen]();
    memcpy(msg, data, len);
    msg[len] = 0x80u;
    /* Big-endian bit-length at end */
    msg[msgLen - 4] = static_cast<uint8_t>((bitLen >> 24) & 0xFF);
    msg[msgLen - 3] = static_cast<uint8_t>((bitLen >> 16) & 0xFF);
    msg[msgLen - 2] = static_cast<uint8_t>((bitLen >>  8) & 0xFF);
    msg[msgLen - 1] = static_cast<uint8_t>( bitLen        & 0xFF);

    for (uint32_t blk = 0u; blk < msgLen; blk += 64u) {
        uint32_t w[80];
        for (int i = 0; i < 16; i++) {
            w[i] = (static_cast<uint32_t>(msg[blk + i*4])   << 24)
                 | (static_cast<uint32_t>(msg[blk + i*4+1]) << 16)
                 | (static_cast<uint32_t>(msg[blk + i*4+2]) << 8)
                 |  static_cast<uint32_t>(msg[blk + i*4+3]);
        }
        for (int i = 16; i < 80; i++) {
            w[i] = rot32(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1);
        }
        uint32_t a=h0,b=h1,c=h2,d=h3,e=h4,f,k,t;
        for (int i = 0; i < 80; i++) {
            if      (i < 20) { f=( b & c)|((~b)& d); k=0x5A827999u; }
            else if (i < 40) { f=  b ^ c ^ d;         k=0x6ED9EBA1u; }
            else if (i < 60) { f=(b&c)|(b&d)|(c&d);   k=0x8F1BBCDCu; }
            else              { f=  b ^ c ^ d;         k=0xCA62C1D6u; }
            t = rot32(a,5) + f + e + k + w[i];
            e=d; d=c; c=rot32(b,30); b=a; a=t;
        }
        h0+=a; h1+=b; h2+=c; h3+=d; h4+=e;
    }
    delete[] msg;

    auto put32 = [&](uint8_t* p, uint32_t v) {
        p[0]=v>>24; p[1]=(v>>16)&0xFF; p[2]=(v>>8)&0xFF; p[3]=v&0xFF;
    };
    put32(digest,    h0); put32(digest+4,  h1);
    put32(digest+8,  h2); put32(digest+12, h3);
    put32(digest+16, h4);
}

/* ── Base64 encode (for WebSocket handshake) ─────────────────────────────── */

inline uint32_t WS_Base64Encode(const uint8_t* in, uint32_t inLen, char* out) {
    static const char tbl[] =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    uint32_t o = 0;
    for (uint32_t i = 0; i < inLen; ) {
        uint32_t b  = static_cast<uint32_t>(in[i++]) << 16;
        int have = 1;
        if (i < inLen) { b |= static_cast<uint32_t>(in[i++]) << 8; have++; }
        if (i < inLen) { b |= static_cast<uint32_t>(in[i++]);       have++; }
        out[o++] = tbl[(b >> 18) & 0x3F];
        out[o++] = tbl[(b >> 12) & 0x3F];
        out[o++] = (have >= 2) ? tbl[(b >> 6) & 0x3F] : '=';
        out[o++] = (have >= 3) ? tbl[ b       & 0x3F] : '=';
    }
    out[o] = '\0';
    return o;
}

} /* namespace StreamHubClient */
