#include <absl/strings/str_format.h>
#include <flatbuffers/flatbuffer_builder.h>
#include <spdlog/spdlog.h>
#include <sys/uio.h>

#include "WorldState.pb.h"
#include "messages/WorldState_generated.h"

// tw::serial — our custom zero-allocation codec
#include <tw/serial/Serial.hpp>

struct Position {
public:
    float x, y, z;
};

const uint32_t NUM_CLIENTS = 1000;
const uint32_t NUM_ENTITIES = 300;


void protobuf() {
     std::vector<mmo::WorldStateMessage> messages(NUM_CLIENTS * 100); // Simulate 100 frames

     for (int frame = 0; frame < 100; ++frame) {
         for (int i = 0; i < NUM_ENTITIES; ++i) {
             Position pos;
             pos.x = i * 1.5f + 1.0f;
             pos.y = i * 2.0f + 2.0f;
             pos.z = i * 3.0f + 3.0f;

             for (int msg_idx = frame * NUM_CLIENTS; msg_idx < (frame + 1) * NUM_CLIENTS; ++msg_idx) {
                 auto entity = messages[msg_idx].mutable_entities()->Add();
                 entity->set_x(pos.x);
                 entity->set_y(pos.y);
                 entity->set_z(pos.z);
             }
         }
     }
}

void flatbuffers_benchmarks(std::vector<Position> positions) {
    std::vector<flatbuffers::FlatBufferBuilder> builders;
    builders.reserve(NUM_CLIENTS);
    std::vector<std::vector<flatbuffers::Offset<PlayerInfo>>> players(NUM_CLIENTS);

    for (int i = 0; i < NUM_CLIENTS; i++) {
        builders.emplace_back(4096);
        players[i].resize(NUM_ENTITIES);
    }

    // reused per frame
    std::vector<iovec> iovecs(NUM_CLIENTS);

    spdlog::info("Running flatbuffers benchmarks...");

    for (int frame = 0; frame < 100; ++frame) {
        for(int position = 0; position < positions.size(); position++) {
            for(int client = 0; client < NUM_CLIENTS; client++) {
                auto& builder = builders[client];

                auto id = position;

                players[client][position] = (
                    CreatePlayerInfo(builder, id, (Vec3*)&positions[position])
                );
            }
        }

        for(int client = 0; client < NUM_CLIENTS; client++ ) {
            auto& builder = builders[client];
            auto players_vec2 = builder.CreateVector(players[client]);
            auto world = CreateWorldState(builder, players_vec2);

            builder.Finish(world);

            builder.Clear();
        }
    }
}

void ours_benchmark(std::vector<Position>& positions) {
    std::vector<std::vector<std::byte>> buffers(NUM_CLIENTS, std::vector<std::byte>(1024*64));

    for(int frame = 0; frame < 100; frame++) {
        for(int position = 0; position < positions.size(); position++) {
            for(int client = 0; client < NUM_CLIENTS; client++) {
                memcpy(&buffers[client][position * (sizeof(uint32_t) + sizeof(Position))], &position, sizeof(int32_t));
                memcpy(&buffers[client][position * (sizeof(uint32_t) + sizeof(Position)) + sizeof(uint32_t)], &positions[position], sizeof(Position));
            }
        }
    }
}

/**
 * tw::serial benchmark — demonstrates the full WorldStateWriter API.
 *
 * Mimics the exact access pattern of StateReplicator::replicate():
 *   - One BinaryBuffer per client, pre-allocated and reused every frame.
 *   - One WorldStateWriter per client per frame.
 *   - Entities written in the inner loop after header/spawns/despawns.
 *   - entity_count patched at the end.
 */
void tw_serial_benchmark(std::vector<Position>& positions) {
    // Pre-allocate one buffer per client
    const std::size_t capacity = 20 + NUM_ENTITIES * 16;
    std::vector<tw::serial::BinaryBuffer> buffers(NUM_CLIENTS);
    for (auto& buf : buffers) {
        buf.reserve(capacity);
    }

    for (int frame = 0; frame < 100; ++frame) {
        // ── Phase 1: write header + empty spawns/despawns ─────────────────
        std::vector<std::size_t> entity_count_offsets(NUM_CLIENTS);

        for (int client = 0; client < NUM_CLIENTS; ++client) {
            auto& buf = buffers[client];
            buf.reset();

            tw::serial::BinaryWriter w(buf);
            // message_type — any value; only the size matters to the benchmark
            w.encode<uint32_t>(0u);
            // sequence — this message is never a reply to a request
            w.encode<uint32_t>(0u);
            // frame_idx
            w.encode<uint32_t>(static_cast<uint32_t>(frame));
            // entity_count placeholder
            entity_count_offsets[client] = buf.size();
            w.encode<uint32_t>(0u);
            // no spawns/despawns in this benchmark
            w.encode<uint32_t>(0u); // spawn_count
            w.encode<uint32_t>(0u); // despawn_count
        }

        // ── Phase 2: scatter entity positions (hot path) ──────────────────
        for (int p = 0; p < static_cast<int>(positions.size()); ++p) {
            const uint32_t id = static_cast<uint32_t>(p);
            for (int client = 0; client < NUM_CLIENTS; ++client) {
                buffers[client].append(&id,             sizeof(uint32_t));
                buffers[client].append(&positions[p].x, 3 * sizeof(float));
            }
        }

        // ── Phase 3: patch entity_count ───────────────────────────────────
        for (int client = 0; client < NUM_CLIENTS; ++client) {
            buffers[client].patch_u32(entity_count_offsets[client],
                                      static_cast<uint32_t>(positions.size()));
        }
    }
}

int main() {
    // create test data

    spdlog::info("Running benchmarks...");

    std::vector<Position> positions(NUM_ENTITIES);

    // Protobuf Benchmark
    auto ours_start = std::chrono::high_resolution_clock::now();
    ours_benchmark(positions);
    auto ours_end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> ours_elapsed = ours_end - ours_start;
    spdlog::info("Ours elapsed: {} seconds", ours_elapsed.count());

    // Protobuf Benchmark
    auto protobuf_start = std::chrono::high_resolution_clock::now();
    protobuf();
    auto protobuf_end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> protobuf_elapsed = protobuf_end - protobuf_start;
    spdlog::info("Protobuf elapsed: {} seconds", protobuf_elapsed.count());

    // FlatBuffers Benchmark
    auto flatbuffers_start = std::chrono::high_resolution_clock::now();
    flatbuffers_benchmarks(positions);
    auto flatbuffers_end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> flatbuffers_elapsed = flatbuffers_end - flatbuffers_start;
    spdlog::info("FlatBuffers elapsed: {} seconds", flatbuffers_elapsed.count());

    // tw::serial Benchmark
    auto tw_serial_start = std::chrono::high_resolution_clock::now();
    tw_serial_benchmark(positions);
    auto tw_serial_end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> tw_serial_elapsed = tw_serial_end - tw_serial_start;
    spdlog::info("tw::serial elapsed: {} seconds", tw_serial_elapsed.count());

    return 0;
}
