#include "msg_private.h"
#include "../core_runtime/core_runtime_private.h"
#include "coduo_int32_bits.h"
#include "coduo_native_x87.h"
#include "coduo_x87emu.h" /* defines EMULATE_X87; x87 shim when it is 1 */

#include <stdint.h>
#include <string.h>

enum {
    MSG_ONE_BYTE = 1,
    MSG_SHORT_BYTES = 2,
    MSG_LONG_BYTES = 4,
    MSG_WRITE_BITS_MIN_FREE_BYTES = 4,
    MSG_BITS_PER_BYTE = 8,
    MSG_BIT_INDEX_MASK = MSG_BITS_PER_BYTE - 1,
    MSG_SINGLE_BIT_MASK = 1,
    MSG_BYTE_MASK = 255,
    MSG_SHORT_MASK = 65535,
    MSG_READ_OVERFLOW_VALUE = -1,
    MSG_OVERFLOW_FILL_BYTE = -1,
    MSG_PERCENT_CHAR = 37,
    MSG_PERIOD_CHAR = 46,
    MSG_LINE_BREAK_CHAR = 10,
    MSG_STRING_TERMINATOR_BYTES = 1,
    MSG_MAX_STRING_PAYLOAD = CODUO_MAX_STRING_CHARS - 1,
    MSG_BIG_STRING_PAYLOAD = CODUO_BIG_INFO_STRING - 1
};

void MSG_Init(msg_t *msg, uint8_t *data, int32_t length)
{
    if (msgInit == qfalse) {
        MSG_initHuffman();
    }

    memset(msg, 0, sizeof(*msg));
    msg->data = data;
    msg->maxsize = length;
}

void MSG_BeginReading(msg_t *msg)
{
    msg->readcount = 0;
    msg->bit = 0;
}

void MSG_WriteBits(msg_t *msg, int32_t value,
                   int32_t bits)
{
    int32_t bitRemainder;
    int32_t byteIndex;
    int32_t remaining;
    /* Stock shifts the signed dword with SAR after each emitted low bit. The
     * discarded sign fill cannot affect any later low bit, so an unsigned
     * carrier preserves the exact wire bits without a C signed-shift rule. */
    uint32_t valueBits = coduo_int32_bits(value);

    remaining = msg->maxsize - msg->cursize;
    if (remaining < MSG_WRITE_BITS_MIN_FREE_BYTES) {
        msg->overflowed = qtrue;
        return;
    }

    while (bits != 0) {
        --bits;
        bitRemainder = msg->bit & MSG_BIT_INDEX_MASK;
        if (bitRemainder == 0) {
            msg->bit =
                coduo_int32_from_bits((uint32_t)msg->cursize << 3U);
            msg->data[msg->cursize] = 0;
            ++msg->cursize;
        }

        if ((valueBits & MSG_SINGLE_BIT_MASK) != 0) {
            byteIndex = msg->bit >> 3;
            msg->data[byteIndex] =
                (uint8_t)(msg->data[byteIndex] |
                          (uint8_t)(MSG_SINGLE_BIT_MASK
                                    << bitRemainder));
        }

        ++msg->bit;
        valueBits >>= 1U;
    }
}

void MSG_WriteBit0(msg_t *msg)
{
    int32_t bitRemainder;
    if (msg->cursize >= msg->maxsize) {
        msg->overflowed = qtrue;
        return;
    }

    bitRemainder = msg->bit & MSG_BIT_INDEX_MASK;
    if (bitRemainder == 0) {
        msg->bit = coduo_int32_from_bits((uint32_t)msg->cursize << 3U);
        msg->data[msg->cursize] = 0;
        ++msg->cursize;
    }

    ++msg->bit;
}

void MSG_WriteBit1(msg_t *msg)
{
    int32_t bitRemainder;
    int32_t byteIndex;

    if (msg->cursize >= msg->maxsize) {
        msg->overflowed = qtrue;
        return;
    }

    bitRemainder = msg->bit & MSG_BIT_INDEX_MASK;
    if (bitRemainder == 0) {
        msg->bit = coduo_int32_from_bits((uint32_t)msg->cursize << 3U);
        msg->data[msg->cursize] = 0;
        ++msg->cursize;
    }

    byteIndex = msg->bit >> 3;
    msg->data[byteIndex] =
        (uint8_t)(msg->data[byteIndex] |
                  (uint8_t)(MSG_SINGLE_BIT_MASK << bitRemainder));
    ++msg->bit;
}

int32_t MSG_ReadBits(msg_t *msg,
                                    int32_t bits)
{
    int32_t i;
    int32_t bitRemainder;
    uint32_t valueBits;

    valueBits = 0;
    for (i = 0; i < bits; ++i) {
        bitRemainder = msg->bit & MSG_BIT_INDEX_MASK;
        if (bitRemainder == 0) {
            msg->bit =
                coduo_int32_from_bits((uint32_t)msg->readcount << 3U);
            ++msg->readcount;
        }

        /* ORIGINAL_BINARY_BUG: no readcount/cursize check precedes this byte
         * load.  Preserved from 0x08080362..0x080803b7; see
         * original-bugs/coduomp-message-bit-reader-extent.md. */
        valueBits |= (uint32_t)(
                         (msg->data[msg->bit >> 3] >> bitRemainder) &
                         MSG_SINGLE_BIT_MASK)
                     << ((uint32_t)i & 31U);
        ++msg->bit;
    }

    return coduo_int32_from_bits(valueBits);
}

int32_t MSG_ReadBit(msg_t *msg)
{
    int32_t bitRemainder;
    int32_t value;

    bitRemainder = msg->bit & MSG_BIT_INDEX_MASK;
    if (bitRemainder == 0) {
        msg->bit =
            coduo_int32_from_bits((uint32_t)msg->readcount << 3U);
        ++msg->readcount;
    }

    /* ORIGINAL_BINARY_BUG: no readcount/cursize check precedes this byte
     * load.  Preserved from 0x080803c6..0x0808041e; see
     * original-bugs/coduomp-message-bit-reader-extent.md. */
    value = (msg->data[msg->bit >> 3] >> bitRemainder) &
            MSG_SINGLE_BIT_MASK;
    ++msg->bit;
    return value;
}

void MSG_WriteByte(msg_t *msg, int32_t value)
{
    if (msg->cursize < msg->maxsize) {
        msg->data[msg->cursize] = (uint8_t)value;
        ++msg->cursize;
    }
    else {
        msg->overflowed = qtrue;
    }
}

void MSG_WriteData(msg_t *msg, const void *data,
                   int32_t length)
{
    int32_t end;

    end = msg->cursize + length;
    if (msg->maxsize < end) {
        msg->overflowed = qtrue;
    }
    else {
        /* ORIGINAL_BINARY_BUG: a negative length can make the signed end
         * check pass and becomes a wrapped 32-bit memcpy extent. See
         * original-bugs/coduomp-network-packet-constructor-bounds.md. */
        memcpy(&msg->data[msg->cursize], data,
               (size_t)(uint32_t)length);
        msg->cursize = end;
    }
}

void MSG_WriteShort(msg_t *msg, int32_t value)
{
    int32_t end;
    int16_t littleValue;

    end = msg->cursize + MSG_SHORT_BYTES;
    if (msg->maxsize < end) {
        msg->overflowed = qtrue;
    }
    else {
        littleValue = (int16_t)LittleShort((int16_t)value);
        memcpy(&msg->data[msg->cursize], &littleValue, sizeof(littleValue));
        msg->cursize = end;
    }
}

void MSG_WriteLong(msg_t *msg, int32_t value)
{
    int32_t end;
    int32_t littleValue;

    end = msg->cursize + MSG_LONG_BYTES;
    if (msg->maxsize < end) {
        msg->overflowed = qtrue;
    }
    else {
        littleValue = LittleLong(value);
        memcpy(&msg->data[msg->cursize], &littleValue, sizeof(littleValue));
        msg->cursize = end;
    }
}

void MSG_WriteString(msg_t *msg, const char *text)
{
    char string[CODUO_MAX_STRING_CHARS];
    int32_t i;
    int32_t length;

    if (text == NULL) {
        MSG_WriteData(msg, "", MSG_ONE_BYTE);
        return;
    }

    length = (int32_t)strlen(text);
    if (length <= MSG_MAX_STRING_PAYLOAD) {
        for (i = 0; i < length; ++i) {
            string[i] = (char)Q_CleanCharacter((int8_t)text[i]);
        }
        string[i] = '\0';
        MSG_WriteData(msg, string,
                      length + MSG_STRING_TERMINATOR_BYTES);
    }
    else {
        Com_Printf("MSG_WriteString: MAX_STRING_CHARS");
        MSG_WriteData(msg, "", MSG_ONE_BYTE);
    }
}

void MSG_WriteBigString(msg_t *msg, const char *text)
{
    char string[CODUO_BIG_INFO_STRING];
    int32_t i;
    int32_t length;

    if (text == NULL) {
        MSG_WriteData(msg, "", MSG_ONE_BYTE);
        return;
    }

    length = (int32_t)strlen(text);
    if (length <= MSG_BIG_STRING_PAYLOAD) {
        Q_strncpyz(string, text, CODUO_BIG_INFO_STRING);
        for (i = 0; i < length; ++i) {
            string[i] = (char)Q_CleanCharacter((int8_t)string[i]);
        }
        MSG_WriteData(msg, string,
                      length + MSG_STRING_TERMINATOR_BYTES);
    }
    else {
        Com_Printf("MSG_WriteString: BIG_INFO_STRING");
        MSG_WriteData(msg, "", MSG_ONE_BYTE);
    }
}

void MSG_WriteAngle(msg_t *msg, float angle)
{
#if EMULATE_X87
    /* 0x080807d3..0x080807f6: both DWORD constants remain in the x87
     * multiply/divide chain until the truncating fistp. */
    int32_t packed = x87f_store_i32_trunc(x87f_div(
        x87f_mul(x87f_load_f32(angle), x87f_load_f32(256.0f)),
        x87f_load_f32(360.0f)));
#elif defined(__x86_64__)
    static const float angleByteScale = 256.0f;
    static const float fullTurnDegrees = 360.0f;
    int32_t packed = CODUO_X87_TRUNCATE_I32(
        ((long double)angle * (long double)angleByteScale) /
        (long double)fullTurnDegrees);
#else
    int32_t packed = (int32_t)((angle * 256.0f) / 360.0f);
#endif
    MSG_WriteByte(msg, packed & MSG_BYTE_MASK);
}

void MSG_WriteAngle16(msg_t *msg, float angle)
{
#if EMULATE_X87
    /* 0x0808081b..0x08080838: the product feeds truncating fistp directly. */
    int32_t packed = x87f_store_i32_trunc(x87f_mul(
        x87f_load_f32(angle), x87f_load_f32(182.04445f)));
#elif defined(__x86_64__)
    static const float angleShortScale = 182.04445f;
    int32_t packed = CODUO_X87_TRUNCATE_I32(
        (long double)angle * (long double)angleShortScale);
#else
    int32_t packed = (int32_t)(angle * 182.04445f);
#endif
    MSG_WriteShort(msg, packed & MSG_SHORT_MASK);
}

int32_t MSG_ReadByte(msg_t *msg)
{
    int32_t value;
    if (msg->readcount < msg->cursize) {
        value = msg->data[msg->readcount];
        ++msg->readcount;
    }
    else {
        value = MSG_READ_OVERFLOW_VALUE;
    }

    return value;
}

int32_t MSG_ReadShort(msg_t *msg)
{
    int32_t end;
    int32_t value;

    end = msg->readcount + MSG_SHORT_BYTES;
    if (msg->cursize < end) {
        value = MSG_READ_OVERFLOW_VALUE;
    }
    else {
        int16_t littleValue;

        memcpy(&littleValue, &msg->data[msg->readcount],
               sizeof(littleValue));
        value = (int16_t)LittleShort(littleValue);
        msg->readcount = end;
    }

    return value;
}

int32_t MSG_ReadLong(msg_t *msg)
{
    int32_t end;
    int32_t value;

    end = msg->readcount + MSG_LONG_BYTES;
    if (msg->cursize < end) {
        value = MSG_READ_OVERFLOW_VALUE;
    }
    else {
        int32_t littleValue;

        memcpy(&littleValue, &msg->data[msg->readcount],
               sizeof(littleValue));
        value = LittleLong(littleValue);
        msg->readcount = end;
    }

    return value;
}

char *MSG_ReadString(msg_t *msg)
{
    static char string[CODUO_MAX_STRING_CHARS];
    int32_t c;
    uint32_t length;

    length = 0;
    do {
        c = MSG_ReadByte(msg);
        if (c == MSG_READ_OVERFLOW_VALUE || c == '\0') {
            break;
        }

        string[length] = (char)Q_CleanCharacter((int8_t)c);
        ++length;
        /* ORIGINAL_BINARY_BUG: reaching the result capacity stops without
         * consuming the remaining wire string.  Preserved from
         * 0x08080980..0x0808098c; see
         * original-bugs/coduomp-message-string-truncation-desync.md. */
    } while (length <= MSG_MAX_STRING_PAYLOAD - 1);

    string[length] = '\0';
    return string;
}

char *MSG_ReadBigString(msg_t *msg)
{
    static char string[CODUO_BIG_INFO_STRING];
    int32_t c;
    uint32_t length;

    length = 0;
    do {
        c = MSG_ReadByte(msg);
        if (c == MSG_READ_OVERFLOW_VALUE || c == '\0') {
            break;
        }
        if (c == MSG_PERCENT_CHAR) {
            c = MSG_PERIOD_CHAR;
        }

        string[length] = (char)Q_CleanCharacter((int8_t)c);
        ++length;
        /* ORIGINAL_BINARY_BUG: reaching the result capacity stops without
         * consuming the remaining wire string.  Preserved from
         * 0x080809f2..0x080809fe; see
         * original-bugs/coduomp-message-string-truncation-desync.md. */
    } while (length <= MSG_BIG_STRING_PAYLOAD - 1);

    string[length] = '\0';
    return string;
}

char *MSG_ReadStringLine(msg_t *msg)
{
    static char string[CODUO_MAX_STRING_CHARS];
    int32_t c;
    uint32_t length;

    length = 0;
    do {
        c = MSG_ReadByte(msg);
        if (c == MSG_READ_OVERFLOW_VALUE || c == '\0' ||
            c == MSG_LINE_BREAK_CHAR) {
            break;
        }
        if (c == MSG_PERCENT_CHAR) {
            c = MSG_PERIOD_CHAR;
        }

        string[length] = (char)Q_CleanCharacter((int8_t)c);
        ++length;
        /* ORIGINAL_BINARY_BUG: reaching the result capacity stops without
         * consuming the remaining wire line.  Preserved from
         * 0x08080a68..0x08080a74; see
         * original-bugs/coduomp-message-string-truncation-desync.md. */
    } while (length <= MSG_MAX_STRING_PAYLOAD - 1);

    string[length] = '\0';
    return string;
}

float MSG_ReadAngle16(msg_t *msg)
{
    /* no (float) cast on the short: stock feeds the bare fild into the
     * multiply with no intermediate float store (0x8080a9e..0x8080aab) */
    return MSG_ReadShort(msg) * 0.005493164f;
}

void MSG_ReadData(msg_t *msg, void *data, int32_t length)
{
    int32_t end;
    size_t originalLength;

    end = msg->readcount + length;
    originalLength = (size_t)(uint32_t)length;
    /* ORIGINAL_BINARY_BUG: a negative length can make the wrapped signed end
     * check select either arm, after which the original dword becomes a huge
     * 32-bit library-call extent.  Preserve that width on native 64-bit
     * hosts; see
     * original-bugs/coduomp-message-read-data-negative-length.md. */
    if (msg->cursize < end) {
        memset(data, MSG_OVERFLOW_FILL_BYTE, originalLength);
    }
    else {
        memcpy(data, &msg->data[msg->readcount], originalLength);
        msg->readcount = end;
    }
}
