#include "netchan_private.h"
#include "sv_snapshot_private.h"
#include "../core_runtime/core_runtime_private.h"

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

#define SV_NETCHAN_TOTAL_FRAGMENT_PERCENT(send_, receive_)                   \
    (((send_).sampleCount + (receive_).sampleCount < 1 ||                    \
      (send_).fragmentSampleCount + (receive_).fragmentSampleCount < 1)      \
         ? 0                                                                 \
         : (((send_).fragmentSampleCount + (receive_).fragmentSampleCount) * \
            100) /                                                           \
               ((send_).sampleCount + (receive_).sampleCount))
#define SV_NETCHAN_MAX(left_, right_) ((left_) < (right_) ? (right_) : (left_))
#define SV_NETCHAN_MIN(left_, right_) ((left_) < (right_) ? (left_) : (right_))

void SV_Netchan_Encode(client_t *client, uint8_t *data,
                       int32_t length)
{
    int32_t i;
    int commandIndex;
    uint8_t key;

    commandIndex = 0;
    key = (uint8_t)client->netchan.outgoingSequence ^
          (uint8_t)client->challenge;

    for (i = 0; i < length; ++i) {
        if (client->lastClientCommandString[commandIndex] == '\0') {
            commandIndex = 0;
        }

        key = (uint8_t)(key ^
                        (uint8_t)(((uint8_t)client->lastClientCommandString
                                        [commandIndex])
                                   << (i & 1)));
        ++commandIndex;
        *data = (uint8_t)(key ^ *data);
        ++data;
    }
}

void SV_Netchan_Decode(client_t *client, uint8_t *data,
                       int32_t length)
{
    char *command;
    int32_t i;
    int commandIndex;
    uint8_t key;

    command =
        client
            ->reliableCommands[client->reliableAcknowledge &
                               (CODUO_HOST_CLIENT_RELIABLE_COMMAND_COUNT - 1)]
            .commandText;
    commandIndex = 0;
    key = (uint8_t)client->challenge ^ (uint8_t)client->serverId ^
          (uint8_t)client->messageAcknowledge;

    for (i = 0; i < length; ++i) {
        if (command[commandIndex] == '\0') {
            commandIndex = 0;
        }

        key = (uint8_t)(key ^
                        (uint8_t)(((uint8_t)command[commandIndex])
                                  << (i & 1)));
        ++commandIndex;
        *data = (uint8_t)(key ^ *data);
        ++data;
    }
}

void SV_Netchan_TransmitNextFragment(netchan_t *chan)
{
    Netchan_TransmitNextFragment(chan);
}

void SV_Netchan_Transmit(client_t *client, uint8_t *data,
                         int32_t length)
{
    SV_Netchan_Encode(client, data + 4, length - 4);
    Netchan_Transmit(&client->netchan, length, data);
}

void SV_Netchan_AddOOBProfilePacket(int32_t length)
{
    if (net_profile->integer != 0) {
        NetProf_PrepProfiling(&svs_oobNetProfile);
        NetProf_AddPacket(&svs_oobNetProfile->send, length, qfalse);
    }
}

void SV_Netchan_SendOOBPacket(int32_t length,
                              const void *data, netadr_t to)
{
    int32_t marker;

    /* Stock i386 tests the first four packet bytes with one dword load. */
    memcpy(&marker, data, sizeof(marker));
    if (marker != -1) {
        Com_Printf("SV_Netchan_SendOOBPacket used to send non-OOB packet.\n");
    }

    NetProf_PrepProfiling(&svs_oobNetProfile);
    NET_SendPacket(NS_SERVER, length, data, to);
    SV_Netchan_AddOOBProfilePacket(length);
}

void SV_Netchan_UpdateProfileStats(void)
{
    client_t *client;
    int i;

    if (svs.clients == NULL) {
        return;
    }

    if (svs_oobNetProfile != NULL) {
        NetProf_UpdateStatistics(&svs_oobNetProfile->send);
        NetProf_UpdateStatistics(&svs_oobNetProfile->receive);
    }

    client = svs.clients;
    for (i = 0; i < sv_maxclients->integer; ++i) {
        netProfileInfo_t *profile;

        profile = client->netchan.profile;
        if (client->state != 0 && profile != NULL) {
            NetProf_UpdateStatistics(&profile->send);
            NetProf_UpdateStatistics(&profile->receive);
        }
        ++client;
    }
}

void SV_Netchan_PrintProfileStats(qboolean printHeader)
{
    client_t *client;
    netProfileInfo_t *profile;
    char line[1024];
    char clientName[17];
    int totalSendBps;
    int totalReceiveBps;
    int totalSendSamples;
    int totalSendFragments;
    int totalReceiveSamples;
    int totalReceiveFragments;
    int totalSendMax;
    int totalSendMin;
    int totalReceiveMax;
    int totalReceiveMin;
    int i;
    int totalFragmentPercent;
    int combinedMax;
    int combinedMin;
    int sendFragmentPercent;
    int receiveFragmentPercent;
    int combinedBps;

    totalSendBps = 0;
    totalReceiveBps = 0;
    totalSendSamples = 0;
    totalSendFragments = 0;
    totalReceiveSamples = 0;
    totalReceiveFragments = 0;
    totalSendMax = 0;
    totalSendMin = 9999;
    totalReceiveMax = 0;
    totalReceiveMin = 9999;

    if (svs.clients == NULL) {
        return;
    }

    SV_Netchan_UpdateProfileStats();

    if (printHeader != 0) {
        Com_Printf("\n\n");
    }

    Com_sprintf(line, sizeof(line), "====================");
    if (printHeader != 0) {
        Com_Printf("%s\n", line);
    }

    Com_sprintf(line, sizeof(line), "Server Network Profile:");
    if (printHeader != 0) {
        Com_Printf("%s\n\n", line);
    }

    Com_sprintf(line, sizeof(line),
                "                    | Sent To                | Recieved From          | Total Source Traffic   |");
    if (printHeader != 0) {
        Com_Printf("%s\n", line);
    }

    Com_sprintf(line, sizeof(line),
                "              Source|   bps|  max|  min|frag%%|   bps|  max|  min|frag%%|   bps|  max|  min|frag%%|");
    if (printHeader != 0) {
        Com_Printf("%s\n", line);
    }

    if (svs_oobNetProfile != NULL) {
        profile = svs_oobNetProfile;
        totalSendBps += profile->send.bytesPerSecond;
        totalSendSamples += profile->send.sampleCount;
        totalSendFragments += profile->send.fragmentSampleCount;
        totalReceiveBps += profile->receive.bytesPerSecond;
        totalReceiveSamples += profile->receive.sampleCount;
        totalReceiveFragments += profile->receive.fragmentSampleCount;
        totalSendMax =
            SV_NETCHAN_MAX(totalSendMax, profile->send.maxBytes);
        totalSendMin =
            SV_NETCHAN_MIN(profile->send.minBytes, totalSendMin);
        totalReceiveMax =
            SV_NETCHAN_MAX(totalReceiveMax, profile->receive.maxBytes);
        totalReceiveMin =
            SV_NETCHAN_MIN(profile->receive.minBytes, totalReceiveMin);
    }

    client = svs.clients;
    for (i = 0; i < sv_maxclients->integer; ++i) {
        profile = client->netchan.profile;
        if (client->state != 0 && profile != NULL) {
            totalSendBps += profile->send.bytesPerSecond;
            totalSendSamples += profile->send.sampleCount;
            totalSendFragments += profile->send.fragmentSampleCount;
            totalReceiveBps += profile->receive.bytesPerSecond;
            totalReceiveSamples += profile->receive.sampleCount;
            totalReceiveFragments += profile->receive.fragmentSampleCount;
            totalSendMax =
                SV_NETCHAN_MAX(totalSendMax, profile->send.maxBytes);
            totalSendMin =
                SV_NETCHAN_MIN(profile->send.minBytes, totalSendMin);
            totalReceiveMax =
                SV_NETCHAN_MAX(totalReceiveMax, profile->receive.maxBytes);
            totalReceiveMin =
                SV_NETCHAN_MIN(profile->receive.minBytes, totalReceiveMin);
        }
        ++client;
    }

    if (totalReceiveSamples + totalSendSamples < 1 ||
        totalReceiveFragments + totalSendFragments < 1) {
        totalFragmentPercent = 0;
    } else {
        totalFragmentPercent =
            ((totalSendFragments + totalReceiveFragments) * 100) /
            (totalSendSamples + totalReceiveSamples);
    }

    combinedMin = SV_NETCHAN_MIN(totalSendMin, totalReceiveMin);
    combinedMax = SV_NETCHAN_MAX(totalSendMax, totalReceiveMax);
    combinedBps = totalSendBps + totalReceiveBps;

    if (totalReceiveSamples == 0) {
        receiveFragmentPercent = 0;
    } else {
        receiveFragmentPercent =
            (totalReceiveFragments * 100) / totalReceiveSamples;
    }
    if (totalSendSamples == 0) {
        sendFragmentPercent = 0;
    } else {
        sendFragmentPercent = (totalSendFragments * 100) / totalSendSamples;
    }

    Com_sprintf(line, sizeof(line),
                "              Totals:%6i|%5i|%5i| %3i%%|%6i|%5i|%5i| %3i%%|%6i|%5i|%5i| %3i%%|",
                totalSendBps, totalSendMax, totalSendMin,
                sendFragmentPercent, totalReceiveBps, totalReceiveMax,
                totalReceiveMin, receiveFragmentPercent, combinedBps,
                combinedMax, combinedMin, totalFragmentPercent);
    if (printHeader != 0) {
        Com_Printf("%s\n", line);
    }

    if (svs_oobNetProfile == NULL) {
        Com_sprintf(line, sizeof(line),
                    "  OutOfBand Messages:     0|    0|    0|   - |     0|    0|    0|   - |     0|    0|    0|   - |");
        if (printHeader != 0) {
            Com_Printf("%s\n", line);
        }
    } else {
        profile = svs_oobNetProfile;
        totalFragmentPercent =
            SV_NETCHAN_TOTAL_FRAGMENT_PERCENT(profile->send, profile->receive);
        combinedMin =
            SV_NETCHAN_MIN(profile->send.minBytes, profile->receive.minBytes);
        combinedMax =
            SV_NETCHAN_MAX(profile->send.maxBytes, profile->receive.maxBytes);
        combinedBps =
            profile->send.bytesPerSecond + profile->receive.bytesPerSecond;

        Com_sprintf(line, sizeof(line),
                    "  OutOfBand Messages: %5i|%5i|%5i| %3i%%| %5i|%5i|%5i| %3i%%| %5i|%5i|%5i| %3i%%|",
                    profile->send.bytesPerSecond, profile->send.maxBytes,
                    profile->send.minBytes, profile->send.fragmentPercent,
                    profile->receive.bytesPerSecond,
                    profile->receive.maxBytes, profile->receive.minBytes,
                    profile->receive.fragmentPercent, combinedBps,
                    combinedMax, combinedMin, totalFragmentPercent);
        if (printHeader != 0) {
            Com_Printf("%s\n", line);
        }
    }

    client = svs.clients;
    for (i = 0; i < sv_maxclients->integer; ++i) {
        if (client->state != 0) {
            strncpy(clientName, client->name, sizeof(clientName));
            clientName[16] = '\0';

            profile = client->netchan.profile;
            if (profile == NULL) {
                Com_sprintf(line, sizeof(line),
                            "#%2i-%16s:     0|    0|    0|   0%%|     0|    0|    0|   0%%|     0|    0|    0|   0%%|",
                            i, clientName);
                if (printHeader != 0) {
                    Com_Printf("%s\n", line);
                }
            } else {
                totalFragmentPercent = SV_NETCHAN_TOTAL_FRAGMENT_PERCENT(
                    profile->send, profile->receive);
                combinedMin = SV_NETCHAN_MIN(profile->send.minBytes,
                                             profile->receive.minBytes);
                combinedMax = SV_NETCHAN_MAX(profile->send.maxBytes,
                                             profile->receive.maxBytes);
                combinedBps = profile->send.bytesPerSecond +
                              profile->receive.bytesPerSecond;

                Com_sprintf(line, sizeof(line),
                            "#%2i-%16s: %5i|%5i|%5i| %3i%%| %5i|%5i|%5i| %3i%%| %5i|%5i|%5i| %3i%%|",
                            i, clientName, profile->send.bytesPerSecond,
                            profile->send.maxBytes, profile->send.minBytes,
                            profile->send.fragmentPercent,
                            profile->receive.bytesPerSecond,
                            profile->receive.maxBytes,
                            profile->receive.minBytes,
                            profile->receive.fragmentPercent, combinedBps,
                            combinedMax, combinedMin, totalFragmentPercent);
                if (printHeader != 0) {
                    Com_Printf("%s\n", line);
                }
            }
        }
        ++client;
    }
}
