#include <string>
#include <vector>
#include <map>
#include <complex>
#include <cstdio>
#include <iostream>
#include <optional>

#include "glaze/api/impl.hpp"
#include "glaze/beve/read.hpp"
#include "glaze/beve/write.hpp"
#include "glaze/beve/beve_to_json.hpp"

// Sample enum
enum class Color { Red, Green, Blue };

template <> struct glz::meta<Color> {
  static constexpr std::string_view name = "Color";
  static constexpr auto value = enumerate(
      "Red", Color::Red,
      "Green", Color::Green,
      "Blue", Color::Blue);
};

// Sample struct
struct Basic {
  int i{};
  double d{};
  std::string name{};
  std::vector<double> vs{};
  std::vector<bool> vb{};
  std::map<std::string,int> m{};
  std::map<int,double> mi{};
  Color color{Color::Green};
};

template <> struct glz::meta<Basic> {
  using T = Basic;
  static constexpr std::string_view name = "Basic";
  static constexpr auto value = object(
      "i", &T::i,
      "d", &T::d,
      "name", &T::name,
      "vs", &T::vs,
      "vb", &T::vb,
      "m", &T::m,
      "mi", &T::mi,
      "color", &T::color
  );
};

// Sample values
static auto sample_vec_f64() { return std::vector<double>{1.1, -2.25, 3.5}; }
static auto sample_vec_u32() { return std::vector<uint32_t>{1, 2, 1000}; }
static auto sample_vec_bool() { return std::vector<bool>{true,false,true,true,false}; }
static auto sample_vec_string() { return std::vector<std::string>{"a","bb","ccc"}; }
static auto sample_color() { return Color::Green; }
static auto sample_basic() {
  Basic b{};
  b.i = 42; b.d = -3.125; b.name = "hello";
  b.vs = sample_vec_f64();
  b.vb = sample_vec_bool();
  b.m = {{"a",1},{"bb",2}};
  b.mi = {{5, 3.14},{7, 7.42}};
  b.color = Color::Green;
  return b;
}

static bool eq(const Basic& a, const Basic& b) {
  return a.i==b.i && a.d==b.d && a.name==b.name && a.vs==b.vs && a.vb==b.vb && a.m==b.m && a.mi==b.mi && a.color==b.color;
}

// Nested structs for interop
struct Inner {
  int x{};
  std::string y{};
};

template <> struct glz::meta<Inner> {
  using T = Inner;
  static constexpr std::string_view name = "Inner";
  static constexpr auto value = object(
    "x", &T::x,
    "y", &T::y
  );
};

struct Outer {
  std::string title{};
  Inner inner{};
  std::vector<Inner> list{};
  std::optional<Inner> maybe{};
};

template <> struct glz::meta<Outer> {
  using T = Outer;
  static constexpr std::string_view name = "Outer";
  static constexpr auto value = object(
    "title", &T::title,
    "inner", &T::inner,
    "list", &T::list,
    "maybe", &T::maybe
  );
};

static bool eq(const Inner& a, const Inner& b) {
  return a.x == b.x && a.y == b.y;
}

static bool eq(const Outer& a, const Outer& b) {
  if (a.title != b.title) return false;
  if (!eq(a.inner, b.inner)) return false;
  if (a.list.size() != b.list.size()) return false;
  for (size_t i = 0; i < a.list.size(); ++i) if (!eq(a.list[i], b.list[i])) return false;
  if (a.maybe.has_value() != b.maybe.has_value()) return false;
  if (a.maybe && b.maybe && !eq(*a.maybe, *b.maybe)) return false;
  return true;
}

static Outer sample_nested() {
  Inner in{.x = 7, .y = std::string{"inner"}};
  Outer o{};
  o.title = "outer";
  o.inner = in;
  o.list = { Inner{.x = 1, .y = std::string{"a"}}, Inner{.x = 2, .y = std::string{"b"}} };
  o.maybe = Inner{.x = -5, .y = std::string{"maybe"}};
  return o;
}

static std::string read_stdin_all() {
  std::string s; std::string chunk;
  char buf[4096];
  while (true) {
    std::cin.read(buf, sizeof(buf));
    std::streamsize n = std::cin.gcount();
    if (n > 0) s.append(buf, buf + n);
    if (n < (std::streamsize)sizeof(buf)) break;
  }
  return s;
}

int main(int argc, char** argv) {
  if (argc < 3) {
    std::cerr << "usage: glaze_interop <write|read> <case>\n";
    return 2;
  }
  std::string mode = argv[1];
  std::string cas = argv[2];

  if (mode == "write") {
    std::string out;
    if (cas == "vec_f64") { auto v=sample_vec_f64(); glz::write_beve(v, out); }
    else if (cas == "vec_u32") { auto v=sample_vec_u32(); glz::write_beve(v, out); }
    else if (cas == "vec_bool") { auto v=sample_vec_bool(); glz::write_beve(v, out); }
    else if (cas == "vec_string") { auto v=sample_vec_string(); glz::write_beve(v, out); }
    else if (cas == "color") { auto v=sample_color(); glz::write_beve(v, out); }
    else if (cas == "basic") { auto v=sample_basic(); glz::write_beve(v, out); }
    else if (cas == "nested") { auto v=sample_nested(); glz::write_beve(v, out); }
    else if (cas == "cplx64") { std::complex<double> v{1.5, -2.25}; glz::write_beve(v, out); }
    else if (cas == "vcplx64") { std::vector<std::complex<double>> v{{1.0,2.0},{-3.0,4.5}}; glz::write_beve(v, out); }
    else if (cas == "mapi8") { std::map<int8_t,int> m{{-1,10},{2,20}}; glz::write_beve(m, out); }
    else if (cas == "mapu16") { std::map<uint16_t,int> m{{1,10},{65535,20}}; glz::write_beve(m, out); }
    else if (cas == "mapu64") { std::map<uint64_t,int> m{{1,10},{(uint64_t)1<<40,20}}; glz::write_beve(m, out); }
    else { std::cerr << "unknown case\n"; return 3; }
    std::cout.write(out.data(), (std::streamsize)out.size());
    return 0;
  }
  else if (mode == "read") {
    auto bytes = read_stdin_all();
    if (cas == "vec_f64") { auto expect=sample_vec_f64(); decltype(expect) got; if (glz::read_beve(got, bytes)) { std::cerr<<"parse error\n"; return 4; } if (got==expect) { std::cout<<"OK\n"; return 0; } }
    else if (cas == "vec_u32") { auto expect=sample_vec_u32(); decltype(expect) got; if (glz::read_beve(got, bytes)) { std::cerr<<"parse error\n"; return 4; } if (got==expect) { std::cout<<"OK\n"; return 0; } }
    else if (cas == "vec_bool") { auto expect=sample_vec_bool(); decltype(expect) got; if (glz::read_beve(got, bytes)) { std::cerr<<"parse error\n"; return 4; } if (got==expect) { std::cout<<"OK\n"; return 0; } }
    else if (cas == "vec_string") { auto expect=sample_vec_string(); decltype(expect) got; if (glz::read_beve(got, bytes)) { std::cerr<<"parse error\n"; return 4; } if (got==expect) { std::cout<<"OK\n"; return 0; } }
    else if (cas == "color") { auto expect=sample_color(); decltype(expect) got; if (glz::read_beve(got, bytes)) { std::cerr<<"parse error\n"; return 4; } if (got==expect) { std::cout<<"OK\n"; return 0; } }
    else if (cas == "basic") { auto expect=sample_basic(); decltype(expect) got; if (glz::read_beve(got, bytes)) { std::cerr<<"parse error\n"; return 4; } if (eq(got, expect)) { std::cout<<"OK\n"; return 0; } }
    else if (cas == "nested") { auto expect=sample_nested(); decltype(expect) got; if (glz::read_beve(got, bytes)) { std::cerr<<"parse error\n"; return 4; } if (eq(got, expect)) { std::cout<<"OK\n"; return 0; } }
    else if (cas == "cplx64") { std::complex<double> expect{1.5,-2.25}; decltype(expect) got; if (glz::read_beve(got, bytes)) { std::cerr<<"parse error\n"; return 4; } if (got==expect) { std::cout<<"OK\n"; return 0; } }
    else if (cas == "vcplx64") { std::vector<std::complex<double>> expect{{1.0,2.0},{-3.0,4.5}}; decltype(expect) got; if (glz::read_beve(got, bytes)) { std::cerr<<"parse error\n"; return 4; } if (got==expect) { std::cout<<"OK\n"; return 0; } }
    else if (cas == "mapi8") { std::map<int8_t,int> expect{{-1,10},{2,20}}; decltype(expect) got; if (glz::read_beve(got, bytes)) { std::cerr<<"parse error\n"; return 4; } if (got==expect) { std::cout<<"OK\n"; return 0; } }
    else if (cas == "mapu16") { std::map<uint16_t,int> expect{{1,10},{65535,20}}; decltype(expect) got; if (glz::read_beve(got, bytes)) { std::cerr<<"parse error\n"; return 4; } if (got==expect) { std::cout<<"OK\n"; return 0; } }
    else if (cas == "mapu64") { std::map<uint64_t,int> expect{{1,10},{(uint64_t)1<<40,20}}; decltype(expect) got; if (glz::read_beve(got, bytes)) { std::cerr<<"parse error\n"; return 4; } if (got==expect) { std::cout<<"OK\n"; return 0; } }
    else { std::cerr << "unknown case\n"; return 3; }
    std::cout<<"MISMATCH\n"; return 5;
  }
  else if (mode == "tojson") {
    auto bytes = read_stdin_all();
    std::string json;
    auto ec = glz::beve_to_json(bytes, json);
    if (bool(ec)) { std::cerr << "tojson error\n"; return 6; }
    std::cout << json;
    return 0;
  }
  else {
    std::cerr << "unknown mode\n";
    return 2;
  }
}
