#pragma once

#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>

#include "weaseljson.h"

inline WeaselJsonCallbacks printCallbacks() {
  WeaselJsonCallbacks result;
  result.on_begin_object = +[](void *) { puts("on_begin_object"); };
  result.on_end_object = +[](void *) { puts("on_end_object"); };
  result.on_begin_string = +[](void *) { puts("on_begin_string"); };
  result.on_string_data = +[](void *, const char *buf, int len) {
    printf("on_string_data `%.*s`\n", len, buf);
  };
  result.on_end_string = +[](void *) { puts("on_end_string"); };
  result.on_begin_array = +[](void *) { puts("on_begin_array"); };
  result.on_end_array = +[](void *) { puts("on_end_array"); };
  result.on_begin_number = +[](void *) { puts("on_begin_number"); };
  result.on_number_data = +[](void *, const char *buf, int len) {
    printf("on_number_data `%.*s`\n", len, buf);
  };
  result.on_end_number = +[](void *) { puts("on_end_number"); };
  result.on_true_literal = +[](void *) { puts("on_true_literal"); };
  result.on_false_literal = +[](void *) { puts("on_false_literal"); };
  result.on_null_literal = +[](void *) { puts("on_null_literal"); };
  return result;
}

inline WeaselJsonCallbacks noopCallbacks() {
  WeaselJsonCallbacks result;
  result.on_begin_object = +[](void *) {};
  result.on_end_object = +[](void *) {};
  result.on_begin_string = +[](void *) {};
  result.on_string_data = +[](void *, const char *buf, int len) {};
  result.on_end_string = +[](void *) {};
  result.on_begin_array = +[](void *) {};
  result.on_end_array = +[](void *) {};
  result.on_begin_number = +[](void *) {};
  result.on_number_data = +[](void *, const char *buf, int len) {};
  result.on_end_number = +[](void *) {};
  result.on_true_literal = +[](void *) {};
  result.on_false_literal = +[](void *) {};
  result.on_null_literal = +[](void *) {};
  return result;
}

struct SerializeState {
  bool isKey = false;
  struct Cursor {
    int64_t index;
    bool isObject;
  };
  std::string result;
  void on_begin_value() {
    if (!stack.empty()) {
      auto &back = stack.back();
      if (back.isObject && back.index % 2 == 0 && back.index > 0) {
        result.append(",");
      }
      if (back.isObject && back.index % 2 == 1 && back.index > 0) {
        result.append(":");
      }
      if (!back.isObject && back.index > 0) {
        result.append(",");
      }
      ++back.index;
    }
  }
  std::vector<Cursor> stack;
};

inline WeaselJsonCallbacks serializeCallbacks() {
  WeaselJsonCallbacks result;
  result.on_begin_object = +[](void *p) {
    auto *state = (SerializeState *)p;
    state->on_begin_value();
    state->stack.push_back({0, true});
    state->result.append("{");
  };
  result.on_end_object = +[](void *p) {
    auto *state = (SerializeState *)p;
    state->stack.pop_back();
    state->result.append("}");
  };
  result.on_begin_string = +[](void *p) {
    auto *state = (SerializeState *)p;
    state->on_begin_value();
    state->result.append("<");
  };
  result.on_string_data = +[](void *p, const char *buf, int len) {
    auto *state = (SerializeState *)p;
    state->result.append(std::string(buf, len));
  };
  result.on_end_string = +[](void *p) {
    auto *state = (SerializeState *)p;
    state->result.append(">");
  };
  result.on_begin_array = +[](void *p) {
    auto *state = (SerializeState *)p;
    state->on_begin_value();
    state->stack.push_back({0, false});
    state->result.append("[");
  };
  result.on_end_array = +[](void *p) {
    auto *state = (SerializeState *)p;
    state->stack.pop_back();
    state->result.append("]");
  };
  result.on_begin_number = +[](void *p) {
    auto *state = (SerializeState *)p;
    state->on_begin_value();
    state->result.append("(");
  };
  result.on_number_data = +[](void *p, const char *buf, int len) {
    auto *state = (SerializeState *)p;
    state->result.append(std::string(buf, len));
  };
  result.on_end_number = +[](void *p) {
    auto *state = (SerializeState *)p;
    state->result.append(")");
  };
  result.on_true_literal = +[](void *p) {
    auto *state = (SerializeState *)p;
    state->on_begin_value();
    state->result.append("true");
  };
  result.on_false_literal = +[](void *p) {
    auto *state = (SerializeState *)p;
    state->on_begin_value();
    state->result.append("false");
  };
  result.on_null_literal = +[](void *p) {
    auto *state = (SerializeState *)p;
    state->on_begin_value();
    state->result.append("null");
  };
  return result;
}
