/**
 * Decode isomorphic corpus PBW1 / TBW1 framed files using libprotobuf and Apache Thrift C++.
 * Each length-prefixed slice is standard protobuf wire / Thrift TBinary struct wire.
 * Prints one JSON object line to stdout for scripts/run-isomorphic-wire-native-bench.js
 */
#include <chrono>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/transport/TBufferTransports.h>
#include <thrift/transport/TTransport.h>

#include "schema.pb.h"
#include "schema_types.h"

using apache::thrift::protocol::TBinaryProtocol;
using apache::thrift::transport::TMemoryBuffer;
using apache::thrift::transport::TTransport;

static uint32_t read_u32le(const uint8_t *p)
{
    return static_cast<uint32_t>(p[0]) | (static_cast<uint32_t>(p[1]) << 8) |
           (static_cast<uint32_t>(p[2]) << 16) | (static_cast<uint32_t>(p[3]) << 24);
}

static bool load_file(const char *path, std::vector<uint8_t> &out)
{
    std::ifstream f(path, std::ios::binary | std::ios::ate);
    if (!f)
        return false;
    const auto sz = static_cast<size_t>(f.tellg());
    f.seekg(0);
    out.resize(sz);
    if (sz > 0)
        f.read(reinterpret_cast<char *>(out.data()), static_cast<std::streamsize>(sz));
    return static_cast<bool>(f);
}

static bool split_framed(const char *magic4, const std::vector<uint8_t> &buf,
                         std::vector<std::pair<const uint8_t *, size_t>> &slices, std::string &err)
{
    if (buf.size() < 4 || std::memcmp(buf.data(), magic4, 4) != 0)
    {
        err = "bad_magic";
        return false;
    }
    size_t o = 4;
    while (o + 4 <= buf.size())
    {
        const uint32_t len = read_u32le(buf.data() + o);
        o += 4;
        if (o + len > buf.size())
        {
            err = "truncated_frame";
            return false;
        }
        slices.push_back({buf.data() + o, static_cast<size_t>(len)});
        o += len;
    }
    return true;
}

static double bench_protobuf(const std::vector<std::pair<const uint8_t *, size_t>> &slices, uint64_t iterations)
{
    auto t0 = std::chrono::steady_clock::now();
    for (uint64_t i = 0; i < iterations; ++i)
    {
        for (const auto &sl : slices)
        {
            bench_isomorphic::BenchIsoRow msg;
            if (!msg.ParseFromArray(sl.first, static_cast<int>(sl.second)))
            {
                std::cerr << "ParseFromArray failed\n";
                std::exit(2);
            }
            (void)msg.c0();
        }
    }
    auto t1 = std::chrono::steady_clock::now();
    return std::chrono::duration<double, std::milli>(t1 - t0).count();
}

static double bench_thrift(const std::vector<std::pair<const uint8_t *, size_t>> &slices, uint64_t iterations)
{
    auto t0 = std::chrono::steady_clock::now();
    for (uint64_t i = 0; i < iterations; ++i)
    {
        for (const auto &sl : slices)
        {
            std::vector<uint8_t> tmp(sl.first, sl.first + sl.second);
            std::shared_ptr<TMemoryBuffer> memBuf(
                new TMemoryBuffer(tmp.data(), static_cast<uint32_t>(tmp.size())));
            std::shared_ptr<TTransport> trans = std::static_pointer_cast<TTransport>(memBuf);
            TBinaryProtocol proto(trans);
            bench_iso_thrift_cpp::BenchIsoRow row;
            row.read(&proto);
            (void)row.c0;
        }
    }
    auto t1 = std::chrono::steady_clock::now();
    return std::chrono::duration<double, std::milli>(t1 - t0).count();
}

// DPK1 row bytes: same tagged-object LE walk as Node WireDeserializer / .NET IsoWireIoBench (validation only).
static constexpr uint8_t kTagNull = 0, kTagFalse = 1, kTagTrue = 2, kTagInt32 = 3, kTagInt64 = 4, kTagDouble = 5,
                         kTagString = 6, kTagBinary = 7, kTagArray = 8, kTagMap = 9, kTagObject = 10;

static void need_span(const uint8_t *base, size_t len, size_t o, size_t n)
{
    if (o + n > len)
    {
        std::cerr << "pack_decode_eof\n";
        std::exit(3);
    }
}

static int32_t read_i32_le(const uint8_t *base, size_t len, size_t &o)
{
    need_span(base, len, o, 4);
    const uint32_t u = read_u32le(base + o);
    o += 4;
    return static_cast<int32_t>(u);
}

static void skip_wire_value(const uint8_t *base, size_t len, size_t &o);

static void skip_utf8_string(const uint8_t *base, size_t len, size_t &o)
{
    const int32_t slen = read_i32_le(base, len, o);
    if (slen < 0)
    {
        std::cerr << "pack_bad_string_len\n";
        std::exit(3);
    }
    need_span(base, len, o, static_cast<size_t>(slen));
    o += static_cast<size_t>(slen);
}

static void skip_wire_value(const uint8_t *base, size_t len, size_t &o)
{
    need_span(base, len, o, 1);
    const uint8_t tag = base[o++];
    switch (tag)
    {
    case kTagNull:
    case kTagFalse:
    case kTagTrue:
        return;
    case kTagInt32:
        need_span(base, len, o, 4);
        o += 4;
        return;
    case kTagInt64:
    case kTagDouble:
        need_span(base, len, o, 8);
        o += 8;
        return;
    case kTagString:
    case kTagBinary:
        skip_utf8_string(base, len, o);
        return;
    case kTagArray: {
        const int32_t n = read_i32_le(base, len, o);
        if (n < 0)
        {
            std::cerr << "pack_bad_array_len\n";
            std::exit(3);
        }
        for (int32_t i = 0; i < n; ++i)
            skip_wire_value(base, len, o);
        return;
    }
    case kTagMap: {
        const int32_t m = read_i32_le(base, len, o);
        if (m < 0)
        {
            std::cerr << "pack_bad_map_len\n";
            std::exit(3);
        }
        for (int32_t i = 0; i < m; ++i)
        {
            skip_wire_value(base, len, o);
            skip_wire_value(base, len, o);
        }
        return;
    }
    case kTagObject: {
        const int32_t fc = read_i32_le(base, len, o);
        if (fc < 0)
        {
            std::cerr << "pack_bad_field_count\n";
            std::exit(3);
        }
        for (int32_t i = 0; i < fc; ++i)
        {
            skip_utf8_string(base, len, o);
            skip_wire_value(base, len, o);
        }
        return;
    }
    default:
        std::cerr << "pack_unknown_tag\n";
        std::exit(3);
    }
}

static void decode_pack_row_slice(const uint8_t *base, size_t len)
{
    size_t o = 0;
    need_span(base, len, o, 1);
    if (base[o++] != kTagObject)
    {
        std::cerr << "pack_row_not_object\n";
        std::exit(3);
    }
    const int32_t fc = read_i32_le(base, len, o);
    if (fc < 0)
    {
        std::cerr << "pack_bad_object_fc\n";
        std::exit(3);
    }
    for (int32_t i = 0; i < fc; ++i)
    {
        skip_utf8_string(base, len, o);
        skip_wire_value(base, len, o);
    }
}

static double bench_pack_tagged(const std::vector<std::pair<const uint8_t *, size_t>> &slices, uint64_t iterations)
{
    auto t0 = std::chrono::steady_clock::now();
    for (uint64_t i = 0; i < iterations; ++i)
    {
        for (const auto &sl : slices)
            decode_pack_row_slice(sl.first, sl.second);
    }
    auto t1 = std::chrono::steady_clock::now();
    return std::chrono::duration<double, std::milli>(t1 - t0).count();
}

int main(int argc, char **argv)
{
    if (argc < 5)
    {
        std::cerr << "usage: isomorphic_wire_native_bench <payload.pb> <payload.thriftwire> <payload.pack> "
                     "<iterations>\n";
        return 1;
    }
    const char *pb_path = argv[1];
    const char *thrift_path = argv[2];
    const char *pack_path = argv[3];
    uint64_t iterations = std::strtoull(argv[4], nullptr, 10);
    if (iterations == 0)
        iterations = 20;

    std::vector<uint8_t> pb_buf;
    std::vector<uint8_t> thr_buf;
    std::vector<uint8_t> pack_buf;
    if (!load_file(pb_path, pb_buf))
    {
        std::cerr << "cannot_read_pb\n";
        return 1;
    }
    if (!load_file(thrift_path, thr_buf))
    {
        std::cerr << "cannot_read_thrift\n";
        return 1;
    }
    if (!load_file(pack_path, pack_buf))
    {
        std::cerr << "cannot_read_pack\n";
        return 1;
    }

    std::vector<std::pair<const uint8_t *, size_t>> pb_slices;
    std::vector<std::pair<const uint8_t *, size_t>> thr_slices;
    std::vector<std::pair<const uint8_t *, size_t>> pack_slices;
    std::string err;
    if (!split_framed("PBW1", pb_buf, pb_slices, err))
    {
        std::cerr << "pbw1:" << err << "\n";
        return 1;
    }
    if (!split_framed("TBW1", thr_buf, thr_slices, err))
    {
        std::cerr << "tbw1:" << err << "\n";
        return 1;
    }
    if (!split_framed("DPK1", pack_buf, pack_slices, err))
    {
        std::cerr << "dpk1:" << err << "\n";
        return 1;
    }

    const double pb_ms = bench_protobuf(pb_slices, iterations);
    const double thr_ms = bench_thrift(thr_slices, iterations);
    const double pack_ms = bench_pack_tagged(pack_slices, iterations);
    const size_t n_pb = pb_slices.size();
    const size_t n_thr = thr_slices.size();
    const size_t n_pack = pack_slices.size();

    std::cout << "{\"runtime\":\"cpp\",\"measureKind\":\"isomorphic_wire_native\",\"iterations\":" << iterations
              << ",\"rowCountPb\":" << n_pb << ",\"rowCountThrift\":" << n_thr << ",\"rowCountPack\":" << n_pack
              << ",\"totalMsPb\":" << pb_ms << ",\"totalMsThrift\":" << thr_ms << ",\"totalMsPack\":" << pack_ms
              << ",\"avgMsPb\":" << (pb_ms / static_cast<double>(iterations))
              << ",\"avgMsThrift\":" << (thr_ms / static_cast<double>(iterations))
              << ",\"avgMsPack\":" << (pack_ms / static_cast<double>(iterations)) << "}\n";
    return 0;
}
