#include "coduo_engine_structs.h"
#include "../core_runtime/core_runtime_private.h"
#include "../networking/msg_private.h"
#include "../networking/netchan_private.h"
#include "server_private.h"
#include "sv_globals_private.h"

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

enum {
    SV_SERVER_COMMAND_PRINT_BUFFER_SIZE = 1024,
    SV_SERVER_COMMAND_PRINT_COPY_LIMIT = 1020,
    SV_SERVER_COMMAND_FORMAT_BUFFER_SIZE = 32768,
    SV_SERVER_COMMAND_UNRELIABLE_BACKLOG_LIMIT = 32,
    SV_SERVER_COMMAND_OVERFLOW_BACKLOG = CODUO_MAX_RELIABLE_COMMANDS + 1,
    SV_SERVER_COMMAND_RING_MASK = CODUO_MAX_RELIABLE_COMMANDS - 1
};

static char sv_serverCommandPrintBuffer[SV_SERVER_COMMAND_PRINT_BUFFER_SIZE];

static const char *SV_ExpandNewlines(const char *command)
{
    int out = 0;

    while (*command != '\0' && out <= SV_SERVER_COMMAND_PRINT_COPY_LIMIT) {
        if (*command == '\n') {
            sv_serverCommandPrintBuffer[out++] = '\\';
            sv_serverCommandPrintBuffer[out++] = 'n';
            ++command;
        } else if (*command == '\x14' || *command == '\x15') {
            ++command;
        } else {
            sv_serverCommandPrintBuffer[out++] = *command++;
        }
    }

    sv_serverCommandPrintBuffer[out] = '\0';
    return sv_serverCommandPrintBuffer;
}

static qboolean SV_IsFirstTokenEqual(const char *left, const char *right)
{
    while (*left != '\0' && *right != '\0' && *left != ' ' &&
           *right != ' ') {
        if (*left != *right) {
            return qfalse;
        }
        ++left;
        ++right;
    }

    return (*left == '\0' || *left == ' ') &&
           (*right == '\0' || *right == ' ');
}

static int32_t
SV_CanReplaceServerCommand(client_t *client,
                           const char *command)
{
    int32_t sequence;

    for (sequence = client->reliableSent + 1;
         sequence <= client->reliableSequence;
         ++sequence) {
        serverReliableCommand_t *slot =
            &client->reliableCommands[sequence & SV_SERVER_COMMAND_RING_MASK];
        const signed char commandType = (signed char)command[0];

        if (slot->reliable == 0 ||
            commandType != (signed char)slot->commandText[0] ||
            (commandType >= 'x' && commandType <= 'z')) {
            continue;
        }

        if (strcmp(command + 1, slot->commandText + 1) == 0) {
            return sequence;
        }

        switch (commandType) {
        case 'a':
        case 'b':
        case 'o':
        case 'p':
        case 'q':
        case 'r':
        case 't':
            return sequence;

        case 'd':
        case 'v':
            if (SV_IsFirstTokenEqual(command + 2,
                                     slot->commandText + 2)) {
                return sequence;
            }
            break;

        default:
            break;
        }
    }

    return -1;
}

static void SV_CullIgnorableServerCommands(client_t *client)
{
    int32_t compactSequence =
        client->reliableSent + 1;
    int32_t readSequence;

    for (readSequence = compactSequence;
         readSequence <= client->reliableSequence;
         ++readSequence) {
        serverReliableCommand_t *readSlot =
            &client->reliableCommands[readSequence &
                                      SV_SERVER_COMMAND_RING_MASK];

        if (readSlot->reliable != 0) {
            serverReliableCommand_t *compactSlot =
                &client->reliableCommands[compactSequence &
                                          SV_SERVER_COMMAND_RING_MASK];

            if (compactSlot != readSlot) {
                *compactSlot = *readSlot;
            }
            ++compactSequence;
        }
    }

    client->reliableSequence = compactSequence - 1;
}

/* NOT_FROM_ORIGINAL_SOURCE: local factoring of SV_AddServerCommand's shift loop. */
static void coduomp_sv_remove_duplicated_server_command(
    client_t *client,
    int32_t duplicatedSequence)
{
    int32_t readSequence;

    for (readSequence = duplicatedSequence + 1;
         readSequence <= client->reliableSequence;
         ++readSequence, ++duplicatedSequence) {
        client->reliableCommands[duplicatedSequence &
                                 SV_SERVER_COMMAND_RING_MASK] =
            client->reliableCommands[readSequence &
                                     SV_SERVER_COMMAND_RING_MASK];
    }
}

void SV_AddServerCommand(client_t *client,
                         qboolean reliable,
                         const char *command)
{
    int32_t duplicatedSequence;
    int32_t sequence;
    serverReliableCommand_t *slot;

    if (client->bIsTestClient != 0) {
        return;
    }

    if (!((client->reliableSequence - client->reliableAcknowledge <
               SV_SERVER_COMMAND_UNRELIABLE_BACKLOG_LIMIT &&
           client->state == CODUO_CS_ACTIVE) ||
          (SV_CullIgnorableServerCommands(client), reliable != qfalse))) {
        return;
    }

    duplicatedSequence = SV_CanReplaceServerCommand(client, command);
    if (duplicatedSequence < 0) {
        /* ORIGINAL_BINARY_BUG: the signed dword sequence is allowed to wrap.
         * A pending window that straddles INT32_MAX makes the signed send
         * loops appear empty. See original-bugs/
         * coduomp-server-command-sequence-wrap.md. */
        ++client->reliableSequence;
    } else {
        coduomp_sv_remove_duplicated_server_command(client,
                                                    duplicatedSequence);
    }

    if (client->reliableSequence - client->reliableAcknowledge ==
        SV_SERVER_COMMAND_OVERFLOW_BACKLOG) {
        Com_Printf("===== pending server commands =====\n");
        for (sequence = client->reliableAcknowledge + 1;
             sequence <= client->reliableSequence;
             ++sequence) {
            slot = &client->reliableCommands[sequence &
                                             SV_SERVER_COMMAND_RING_MASK];
            Com_Printf("cmd %5d: %8d: %s\n", sequence, slot->enqueueTime,
                       slot->commandText);
        }
        Com_Printf("cmd %5d: %8d: %s\n", sequence, svs.timeSecondary,
                   command);
        NET_OutOfBandPrint(NS_SERVER, client->netchan.remoteAddress,
                           "disconnect");
        SV_SetClientDeferredDropReason(client, "EXE_SERVERCOMMANDOVERFLOW");
        reliable = qtrue;
        command = "w \"EXE_SERVERCOMMANDOVERFLOW\"";
    }

    slot = &client->reliableCommands[client->reliableSequence &
                                     SV_SERVER_COMMAND_RING_MASK];
    MSG_WriteReliableCommandToBuffer(command, slot->commandText,
                                     sizeof(slot->commandText));
    slot->enqueueTime = svs.timeSecondary;
    slot->reliable = reliable;
}

void SV_SendServerCommand(client_t *client,
                          qboolean reliable,
                          const char *format, ...)
{
    char command[SV_SERVER_COMMAND_FORMAT_BUFFER_SIZE];
    va_list argptr;

    va_start(argptr, format);
    /* ORIGINAL_BINARY_BUG: stock supplies no destination capacity to
     * vsprintf. See original-bugs/
     * coduomp-server-command-formatting-overflow.md. */
    vsprintf(command, format, argptr);
    va_end(argptr);

    if (client != NULL) {
        SV_AddServerCommand(client, reliable, command);
        return;
    }

    if (dedicated->integer != 0 &&
        strncmp(command, "print", 5) == 0) {
        Com_Printf("broadcast: %s\n", SV_ExpandNewlines(command));
    }

    for (int clientNum = 0;
         clientNum < host_cvar_sv_maxclients_pointer_slot->integer;
         ++clientNum) {
        client_t *broadcastClient = &svs.clients[clientNum];

        if (broadcastClient->state > CODUO_CS_CONNECTED) {
            SV_AddServerCommand(broadcastClient, reliable, command);
        }
    }
}
