// Composite key: pack two fields into one ordered key
// Textbook section: Volume II, chapter 8, "Secondary indexes", section "Composite keys".
// What it demonstrates: packing two unsigned fields into a composite key
//   of fixed length (8 bytes, big-endian) so that the lexicographic order
//   of keys matches the numeric one; a range query "all keys with the given first
//   field" via SET_RANGE. No custom comparator is required: byte-order encoding
//   is sufficient.
// Build: g++ -std=c++17 -I<libmdbx-include> -I../common 10-composite-key.c++ -lmdbx
// Run: ./10-composite-key
// Expected output:
//   entries: (0,1) (0,2) (1,0) (1,1) (7,3) (7,7) (7,42) (9,0)
//   range x=7: (7,3) (7,7) (7,42)
//   ok: composite key packed and range-scanned
// On failure — message to stderr and exit 1.
//
// SPDX-License-Identifier: Apache-2.0

#include <cstdint>
#include <iostream>
#include <string>

#include "common.h++"
#include <mdbx.h++>

#if defined(_MSC_VER)
#include <intrin.h>
#endif

namespace {

// Big-endian (network-order) representation of a 64-bit value: the byte order
// used for fixed-width packed keys, so lexicographic order matches numeric.
uint64_t be64(uint64_t value) {
#if defined(_MSC_VER)
  return _byteswap_uint64(value);
#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
  return __builtin_bswap64(value);
#else
  return value;
#endif
}

// Packing (x, y) into uint64_t so that the per-byte (lexicographic) order
// of the key matches the numeric order of the pair. For this, a big-endian
// representation of fixed-width unsigned fields is used.
uint64_t pack(uint32_t x, uint32_t y) {
  const uint64_t value = (uint64_t(x) << 32) | uint64_t(y);
  return be64(value);
}

// Decoding the composite key back into (x, y).
std::string key_to_string(const mdbx::slice &key) {
  const auto *p = static_cast<const uint8_t *>(key.data());
  const uint32_t hi = (uint32_t(p[0]) << 24) | (uint32_t(p[1]) << 16) | (uint32_t(p[2]) << 8) | uint32_t(p[3]);
  const uint32_t lo = (uint32_t(p[4]) << 24) | (uint32_t(p[5]) << 16) | (uint32_t(p[6]) << 8) | uint32_t(p[7]);
  return "(" + std::to_string(hi) + "," + std::to_string(lo) + ")";
}

// The first field of the composite key (x), decoded from big-endian.
uint32_t key_first_field(const mdbx::slice &key) {
  const auto *p = static_cast<const uint8_t *>(key.data());
  return (uint32_t(p[0]) << 24) | (uint32_t(p[1]) << 16) | (uint32_t(p[2]) << 8) | uint32_t(p[3]);
}

} // namespace

int main() {
  try {
    const std::string path = std::string(example::tmpdir()) + "/mdbx-10-composite-key.mdbx";
    mdbx::env::remove(path);
    mdbx::env_managed env = example::env_open(path);

    {
      auto txn = env.start_write();
      auto table = txn.open_map(nullptr);
      static const uint32_t xs[] = {0, 0, 1, 1, 7, 7, 7, 9};
      static const uint32_t ys[] = {1, 2, 0, 1, 3, 42, 7, 0};
      for (size_t i = 0; i < sizeof(xs) / sizeof(xs[0]); ++i) {
        const uint64_t packed = pack(xs[i], ys[i]);
        txn.insert(table, mdbx::slice::wrap(packed), mdbx::slice("v"));
      }
      txn.commit();
    }

    auto rtxn = env.start_read();
    auto table = rtxn.open_map(nullptr);

    // Full traversal: keys are sorted by the value of the composite key.
    std::string all;
    auto cur = rtxn.open_cursor(table);
    for (auto r = cur.to_first(); r; r = cur.to_next(false))
      all += key_to_string(r.key) + " ";
    std::cout << "entries: " << all << "\n";

    // The range x=7: from (7,0); we stop when the first field != 7.
    std::string range;
    const uint64_t lo = pack(7, 0);
    for (auto r = cur.to_key_greater_or_equal(mdbx::slice::wrap(lo), false); r && key_first_field(r.key) == 7;
         r = cur.to_next(false))
      range += key_to_string(r.key) + " ";
    std::cout << "range x=7: " << range << "\n";

    rtxn.abort();

    std::cout << "ok: composite key packed and range-scanned\n";
    return EXIT_SUCCESS;
  } catch (const std::exception &ex) {
    std::cerr << "error: " << ex.what() << "\n";
    return EXIT_FAILURE;
  }
}