#include "msg_private.h"
#include "coduo_engine_structs.h"
#include "sv_snapshot_private.h"
#include "../core_runtime/core_runtime_private.h"

#include <stdint.h>

void SV_EmitPacketEntities(int oldNumEntities, int oldFirstEntity,
                           int newNumEntities, int newFirstEntity,
                           msg_t *msg)
{
    const coduo_host_entity_state_snapshot_t *oldEntity;
    const coduo_host_entity_state_snapshot_t *newEntity;
    int32_t oldEntityNum;
    int32_t newEntityNum;
    int oldIndex;
    int newIndex;

    newEntity = NULL;
    oldEntity = NULL;
    newIndex = 0;
    oldIndex = 0;

    while (newIndex < newNumEntities || oldIndex < oldNumEntities) {
        if (newIndex < newNumEntities) {
            newEntity =
                &svs.entityStateSnapshots
                     [(newIndex + newFirstEntity) %
                      svs.numEntityStateSnapshots]
                         .entityState;
            newEntityNum = newEntity->s_number;
        } else {
            newEntityNum = 9999;
        }

        if (oldIndex < oldNumEntities) {
            oldEntity =
                &svs.entityStateSnapshots
                     [(oldIndex + oldFirstEntity) %
                      svs.numEntityStateSnapshots]
                         .entityState;
            oldEntityNum = oldEntity->s_number;
        } else {
            oldEntityNum = 9999;
        }

        if (newEntityNum == oldEntityNum) {
            MSG_WriteDeltaEntity(msg, oldEntity, newEntity, 0);
            ++oldIndex;
            ++newIndex;
        } else if (newEntityNum < oldEntityNum) {
            MSG_WriteDeltaEntity(
                msg, &sv_entityLinks[newEntityNum].baseline.entityState,
                newEntity, 1);
            ++newIndex;
        } else if (oldEntityNum < newEntityNum) {
            MSG_WriteDeltaEntity(msg, oldEntity, NULL, 1);
            ++oldIndex;
        }
    }

    MSG_WriteBits(msg, 1023, 10);
}

void SV_EmitPacketClients(int oldNumClients, int oldFirstClient,
                          int newNumClients, int newFirstClient,
                          msg_t *msg)
{
    const coduo_host_client_snapshot_t *oldClient;
    const coduo_host_client_snapshot_t *newClient;
    int32_t oldClientNum;
    int32_t newClientNum;
    int oldIndex;
    int newIndex;

    newClient = NULL;
    oldClient = NULL;
    newIndex = 0;
    oldIndex = 0;

    while (newIndex < newNumClients || oldIndex < oldNumClients) {
        if (newIndex < newNumClients) {
            newClient =
                &svs.clientSnapshots[(newIndex + newFirstClient) %
                                     svs.numClientSnapshots];
            newClientNum = newClient->clientNum;
        } else {
            newClientNum = 9999;
        }

        if (oldIndex < oldNumClients) {
            oldClient =
                &svs.clientSnapshots[(oldIndex + oldFirstClient) %
                                     svs.numClientSnapshots];
            oldClientNum = oldClient->clientNum;
        } else {
            oldClientNum = 9999;
        }

        if (newClientNum == oldClientNum) {
            MSG_WriteDeltaClient(msg, oldClient, newClient, 0);
            ++oldIndex;
            ++newIndex;
        } else if (newClientNum < oldClientNum) {
            MSG_WriteDeltaClient(msg, NULL, newClient, 1);
            ++newIndex;
        } else if (oldClientNum < newClientNum) {
            MSG_WriteDeltaClient(msg, oldClient, NULL, 1);
            ++oldIndex;
        }
    }

    MSG_WriteBit0(msg);
}

void SV_WriteSnapshotToClient(client_t *client, msg_t *msg)
{
    clientSnapshot_t *newFrame;
    clientSnapshot_t *oldFrame;
    int32_t snapFlags;
    int deltaFrame;
    int oldNumEntities;
    int oldFirstEntity;
    int oldNumClients;
    int oldFirstClient;
    int padByte;

    newFrame = &client->snapshotFrames[client->netchan.outgoingSequence &
                                       (CODUO_HOST_CLIENT_SNAPSHOT_FRAME_COUNT -
                                        1)];

    if (client->deltaMessage < 1 || client->state != CODUO_CS_ACTIVE) {
        oldFrame = NULL;
        deltaFrame = 0;
    } else if (client->netchan.outgoingSequence - client->deltaMessage <= 28) {
        oldFrame =
            &client->snapshotFrames
                 [client->deltaMessage &
                  (CODUO_HOST_CLIENT_SNAPSHOT_FRAME_COUNT - 1)];
        deltaFrame = client->netchan.outgoingSequence - client->deltaMessage;

        if (oldFrame->firstEntity <
            svs.nextEntityStateSnapshot - svs.numEntityStateSnapshots) {
            Com_DPrintf("%s: Delta request from out of date entities.\n",
                        client->name);
            oldFrame = NULL;
            deltaFrame = 0;
        }
    } else {
        Com_DPrintf("%s: Delta request from out of date packet.\n",
                    client->name);
        oldFrame = NULL;
        deltaFrame = 0;
    }

    MSG_WriteByte(msg, CODUO_SVC_SNAPSHOT);
    MSG_WriteLong(msg, svs.time);
    MSG_WriteByte(msg, deltaFrame);

    snapFlags = svs.snapFlagServerBit;
    if (client->rateDelayed != 0) {
        snapFlags |= 1;
    }

    if (client->state == CODUO_CS_ACTIVE) {
        client->sendAsActive = 1;
    } else if (client->state != CODUO_CS_ZOMBIE) {
        client->sendAsActive = 0;
    }

    if (client->sendAsActive == 0) {
        snapFlags |= 2;
    }
    MSG_WriteByte(msg, snapFlags);

    if (oldFrame == NULL) {
        MSG_WriteDeltaPlayerstate(msg, NULL, &newFrame->playerState);
        oldNumEntities = 0;
        oldFirstEntity = 0;
        oldNumClients = 0;
        oldFirstClient = 0;
    } else {
        MSG_WriteDeltaPlayerstate(msg, &oldFrame->playerState,
                                  &newFrame->playerState);
        oldNumEntities = oldFrame->numEntities;
        oldFirstEntity = oldFrame->firstEntity;
        oldNumClients = oldFrame->numClients;
        oldFirstClient = oldFrame->firstClient;
    }

    SV_EmitPacketEntities(oldNumEntities, oldFirstEntity,
                          newFrame->numEntities, newFrame->firstEntity, msg);
    SV_EmitPacketClients(oldNumClients, oldFirstClient, newFrame->numClients,
                         newFrame->firstClient, msg);

    if (sv_padPackets->integer != 0) {
        for (padByte = 0; padByte < sv_padPackets->integer; ++padByte) {
            MSG_WriteByte(msg, 1);
        }
    }
}

void SV_PrintServerCommandsForClient(client_t *client)
{
    const client_t *clients;
    int32_t commandSequence;
    const serverReliableCommand_t *command;
    int clientNum;

    clients = svs.clients;
    clientNum = (int)(client - clients);

    Com_Printf("-- Unacknowledged Server Commands for client %i:%s --\n",
               clientNum, client->name);

    commandSequence = client->reliableAcknowledge + 1;
    while (commandSequence <= client->reliableSequence) {
        command = &client->reliableCommands
                       [commandSequence &
                        (CODUO_HOST_CLIENT_RELIABLE_COMMAND_COUNT - 1)];
        Com_Printf("cmd %5d: %8d: %s\n", commandSequence,
                   command->enqueueTime, command->commandText);
        ++commandSequence;
    }

    Com_Printf("----------");
}
