#ifndef UTILS_H
#define UTILS_H

#include <vector>
#include <jsi/jsi.h>
#include "TypedArray.hpp"
#include "Platform.h"

#define STRINGIFY(it) #it

#define STRINGIFIED_FUNC_NAME() STRINGIFY(__function)
#define STRINGIFY_TYPE(T) #T

using namespace facebook::jsi;

namespace sharedjsi
{

std::string pointerToHexString(void const *ptr);

template <typename T>
Value cPointerToJSPointerObject(Runtime &runtime, const T *pointer) {
  if (pointer == nullptr) {
    return Value::null();
  }

  Object obj(runtime);
  obj.setProperty(runtime, "type", STRINGIFY_TYPE(T));
  obj.setProperty(runtime, "addr", pointerToHexString(pointer));
  return obj;
}

template<typename T>
T *jsPointerObjectToCPointer(Runtime &runtime, const Value &arg)
{
  if (arg.isNull() || arg.isUndefined()) {
    return nullptr;
  }
  Object obj = arg.getObject(runtime);
  std::string str = obj.getProperty(runtime, "addr").toString(runtime).utf8(runtime);
  T *ptr;
  sscanf(str.c_str(), "%p", &ptr);
  return ptr;
}

template <typename T>
std::vector<T> jsPointerArrayToVector(jsi::Runtime &runtime,
                                    const jsi::Value &arg) {
  if (arg.isUndefined() || arg.isNull()) {
    return {};
  }
  if (!arg.isObject() || !arg.asObject(runtime).isArray(runtime)) {
    throw std::invalid_argument("Expected a JavaScript array");
  }

  jsi::Array jsArray = arg.asObject(runtime).asArray(runtime);
  std::vector<T> res;
  res.reserve(jsArray.length(runtime));

  for (size_t i = 0; i < jsArray.length(runtime); ++i) {
    const jsi::Value &val = jsArray.getValueAtIndex(runtime, i);
    res.push_back(
        jsPointerObjectToCPointer<typename std::remove_pointer<T>::type>(
            runtime, val));
  }
  return res;
}

std::vector<uint8_t> jsTypedArrayToCVector(Runtime &runtime, const Value &arg);
std::string jsTypedArrayToCString(Runtime &runtime, const Value &arg);

template<typename T>
Object cSlicedBoxToJSSlicedBox(Runtime &runtime, T box_c_bytes)
{
  Object obj(runtime);
  obj.setProperty(runtime, "ptr", cPointerToJSPointerObject<uint8_t>(runtime, box_c_bytes.ptr));
  obj.setProperty(runtime, "len", static_cast<double>(box_c_bytes.len));
  return obj;
}

slice_ref_uint8_t borrowVecAsOptRefSlice(std::vector<uint8_t> const& v);
slice_ref_uint8_t borrowVecAsRefSlice(std::vector<uint8_t> const& v);
slice_ref_dittoffi_query_result_item_ptr borrowVecAsRefSlice(std::vector<dittoffi_query_result_item_t *> const& v);

Value int64ToBigIntOrNumber(Runtime &runtime, int64_t value);
Value uint64ToBigIntOrNumber(Runtime &runtime, uint64_t value);
int64_t bigIntOrNumberToInt64(Runtime &runtime, Value const& value);
uint64_t bigIntOrNumberToUint64(Runtime &runtime, Value const& value);

void ditto_tick(Runtime* jsi);
void jsi_enqueue_call(std::function<void(jsi::Runtime&)>);
}

#endif /* UTILS_H */
