// WebAssembly C++ API

#ifndef __WASM_HH
#define __WASM_HH

#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <limits>
#include <string>


///////////////////////////////////////////////////////////////////////////////
// Auxiliaries

// Machine types

static_assert(sizeof(float) == sizeof(int32_t), "incompatible float type");
static_assert(sizeof(double) == sizeof(int64_t), "incompatible double type");
static_assert(sizeof(intptr_t) == sizeof(int32_t) ||
              sizeof(intptr_t) == sizeof(int64_t), "incompatible pointer type");

using byte_t = char;
using float32_t = float;
using float64_t = double;


namespace wasm {

// Vectors

template<class T>
class vec {
  static const size_t invalid_size = SIZE_MAX;

  size_t size_;
  std::unique_ptr<T[]> data_;

#ifdef WASM_API_DEBUG
  void make_data();
  void free_data();
#else
  void make_data() {}
  void free_data() {}
#endif

  vec(size_t size) : vec(size, size ? new(std::nothrow) T[size] : nullptr) {
    make_data();
  }

  vec(size_t size, T* data) : size_(size), data_(data) {
    assert(!!size_ == !!data_ || size_ == invalid_size);
  }

public:
  using elem_type = T;

  vec(vec<T>&& that) : vec(that.size_, that.data_.release()) {}

  ~vec() {
    free_data();
  }

  operator bool() const {
    return bool(size_ != invalid_size);
  }

  auto size() const -> size_t {
    return size_;
  }

  auto get() const -> const T* {
    return data_.get();
  }

  auto get() -> T* {
    return data_.get();
  }

  auto release() -> T* {
    return data_.release();
  }

  void reset() {
    free_data();
    size_ = invalid_size;
    data_.reset();
  }

  void reset(vec& that) {
    free_data();
    size_ = that.size_;
    data_.reset(that.data_.release());
  }

  auto operator=(vec&& that) -> vec& {
    reset(that);
    return *this;
  }

  auto operator[](size_t i) -> T& {
    assert(i < size_);
    return data_[i];
  }

  auto operator[](size_t i) const -> const T& {
    assert(i < size_);
    return data_[i];
  }

  auto copy() const -> vec {
    auto v = vec(size_);
    if (v) for (size_t i = 0; i < size_; i++) v.data_[i] = data_[i];
    return v;
  }

  // TODO: This can't be used for e.g. vec<Val>
  auto deep_copy() const -> vec {
    auto v = vec(size_);
    if (v) for (size_t i = 0; i < size_; ++i) v.data_[i] = data_[i]->copy();
    return v;
  }

  static auto make_uninitialized(size_t size = 0) -> vec {
    return vec(size);
  }

  static auto make(size_t size, T init[]) -> vec {
    auto v = vec(size);
    if (v) for (size_t i = 0; i < size; ++i) v.data_[i] = std::move(init[i]);
    return v;
  }

  static auto make(std::string s) -> vec<char> {
    auto v = vec(s.length() + 1);
    if (v) std::strcpy(v.get(), s.data());
    return v;
  }

  // TODO(mvsc): MVSC requires this special case:
  static auto make() -> vec {
    return vec(0);
  }

  template<class... Ts>
  static auto make(Ts&&... args) -> vec {
    T data[] = { std::move(args)... };
    return make(sizeof...(Ts), data);
  }

  static auto adopt(size_t size, T data[]) -> vec {
    return vec(size, data);
  }

  static auto invalid() -> vec {
    return vec(invalid_size, nullptr);
  }
};


// Ownership

template<class T> using own = std::unique_ptr<T>;
template<class T> using ownvec = vec<own<T>>;

template<class T>
auto make_own(T* x) -> own<T> { return own<T>(x); }


///////////////////////////////////////////////////////////////////////////////
// Runtime Environment

// Configuration

class Config {
public:
  Config() = delete;
  ~Config();
  void operator delete(void*);

  static auto make() -> own<Config>;

  // Implementations may provide custom methods for manipulating Configs.
};


// Engine

class Engine {
public:
  Engine() = delete;
  ~Engine();
  void operator delete(void*);

  static auto make(own<Config>&& = Config::make()) -> own<Engine>;
};


// Store

class Store {
public:
  Store() = delete;
  ~Store();
  void operator delete(void*);

  static auto make(Engine*) -> own<Store>;
};


///////////////////////////////////////////////////////////////////////////////
// Type Representations

// Type attributes

enum Mutability : uint8_t { CONST, VAR };

struct Limits {
  uint32_t min;
  uint32_t max;

  Limits(uint32_t min, uint32_t max = std::numeric_limits<uint32_t>::max()) :
    min(min), max(max) {}
};


// Value Types

enum ValKind : uint8_t {
  I32, I64, F32, F64,
  ANYREF = 128, FUNCREF,
};

inline bool is_num(ValKind k) { return k < ANYREF; }
inline bool is_ref(ValKind k) { return k >= ANYREF; }


class ValType {
public:
  ValType() = delete;
  ~ValType();
  void operator delete(void*);

  static auto make(ValKind) -> own<ValType>;
  auto copy() const -> own<ValType>;

  auto kind() const -> ValKind;
  auto is_num() const -> bool { return wasm::is_num(kind()); }
  auto is_ref() const -> bool { return wasm::is_ref(kind()); }
};


// External Types

enum ExternKind : uint8_t {
  EXTERN_FUNC, EXTERN_GLOBAL, EXTERN_TABLE, EXTERN_MEMORY
};

class FuncType;
class GlobalType;
class TableType;
class MemoryType;

class ExternType {
public:
  ExternType() = delete;
  ~ExternType();
  void operator delete(void*);

  auto copy() const-> own<ExternType>;

  auto kind() const -> ExternKind;

  auto func() -> FuncType*;
  auto global() -> GlobalType*;
  auto table() -> TableType*;
  auto memory() -> MemoryType*;

  auto func() const -> const FuncType*;
  auto global() const -> const GlobalType*;
  auto table() const -> const TableType*;
  auto memory() const -> const MemoryType*;
};


// Function Types

class FuncType : public ExternType {
public:
  FuncType() = delete;
  ~FuncType();

  static auto make(
    ownvec<ValType>&& params = ownvec<ValType>::make(),
    ownvec<ValType>&& results = ownvec<ValType>::make()
  ) -> own<FuncType>;

  auto copy() const -> own<FuncType>;

  auto params() const -> const ownvec<ValType>&;
  auto results() const -> const ownvec<ValType>&;
};


// Global Types

class GlobalType : public ExternType {
public:
  GlobalType() = delete;
  ~GlobalType();

  static auto make(own<ValType>&&, Mutability) -> own<GlobalType>;
  auto copy() const -> own<GlobalType>;

  auto content() const -> const ValType*;
  auto mutability() const -> Mutability;
};


// Table Types

class TableType : public ExternType {
public:
  TableType() = delete;
  ~TableType();

  static auto make(own<ValType>&&, Limits) -> own<TableType>;
  auto copy() const -> own<TableType>;

  auto element() const -> const ValType*;
  auto limits() const -> const Limits&;
};


// Memory Types

class MemoryType : public ExternType {
public:
  MemoryType() = delete;
  ~MemoryType();

  static auto make(Limits) -> own<MemoryType>;
  auto copy() const -> own<MemoryType>;

  auto limits() const -> const Limits&;
};


// Import Types

using Name = vec<byte_t>;

class ImportType {
public:
  ImportType() = delete;
  ~ImportType();
  void operator delete(void*);

  static auto make(Name&& module, Name&& name, own<ExternType>&&) ->
    own<ImportType>;
  auto copy() const -> own<ImportType>;

  auto module() const -> const Name&;
  auto name() const -> const Name&;
  auto type() const -> const ExternType*;
};


// Export Types

class ExportType {
public:
  ExportType() = delete;
  ~ExportType();
  void operator delete(void*);

  static auto make(Name&&, own<ExternType>&&) -> own<ExportType>;
  auto copy() const -> own<ExportType>;

  auto name() const -> const Name&;
  auto type() const -> const ExternType*;
};


///////////////////////////////////////////////////////////////////////////////
// Runtime Objects

// References

class Ref {
public:
  Ref() = delete;
  ~Ref();
  void operator delete(void*);

  auto copy() const -> own<Ref>;
  auto same(const Ref*) const -> bool;

  auto get_host_info() const -> void*;
  void set_host_info(void* info, void (*finalizer)(void*) = nullptr);
};


// Values

class Val {
  ValKind kind_;
  union impl {
    int32_t i32;
    int64_t i64;
    float32_t f32;
    float64_t f64;
    Ref* ref;
  } impl_;

  Val(ValKind kind, impl impl) : kind_(kind), impl_(impl) {}

public:
  Val() : kind_(ANYREF) { impl_.ref = nullptr; }
  Val(int32_t i) : kind_(I32) { impl_.i32 = i; }
  Val(int64_t i) : kind_(I64) { impl_.i64 = i; }
  Val(float32_t z) : kind_(F32) { impl_.f32 = z; }
  Val(float64_t z) : kind_(F64) { impl_.f64 = z; }
  Val(own<Ref>&& r) : kind_(ANYREF) { impl_.ref = r.release(); }

  Val(Val&& that) : kind_(that.kind_), impl_(that.impl_) {
    if (is_ref()) that.impl_.ref = nullptr;
  }

  ~Val() {
    reset();
  }

  auto is_num() const -> bool { return wasm::is_num(kind_); }
  auto is_ref() const -> bool { return wasm::is_ref(kind_); }

  static auto i32(int32_t x) -> Val { return Val(x); }
  static auto i64(int64_t x) -> Val { return Val(x); }
  static auto f32(float32_t x) -> Val { return Val(x); }
  static auto f64(float64_t x) -> Val { return Val(x); }
  static auto ref(own<Ref>&& x) -> Val { return Val(std::move(x)); }
  template<class T> inline static auto make(T x) -> Val;
  template<class T> inline static auto make(own<T>&& x) -> Val;

  void reset() {
    if (is_ref() && impl_.ref) {
      delete impl_.ref;
      impl_.ref = nullptr;
    }
  }

  void reset(Val& that) {
    reset();
    kind_ = that.kind_;
    impl_ = that.impl_;
    if (is_ref()) that.impl_.ref = nullptr;
  }

  auto operator=(Val&& that) -> Val& {
    reset(that);
    return *this;
  }

  auto kind() const -> ValKind { return kind_; }
  auto i32() const -> int32_t { assert(kind_ == I32); return impl_.i32; }
  auto i64() const -> int64_t { assert(kind_ == I64); return impl_.i64; }
  auto f32() const -> float32_t { assert(kind_ == F32); return impl_.f32; }
  auto f64() const -> float64_t { assert(kind_ == F64); return impl_.f64; }
  auto ref() const -> Ref* { assert(is_ref()); return impl_.ref; }
  template<class T> inline auto get() const -> T;

  auto release_ref() -> own<Ref> {
    assert(is_ref());
    auto ref = impl_.ref;
    impl_.ref = nullptr;
    return own<Ref>(ref);
  }

  auto copy() const -> Val {
    if (is_ref() && impl_.ref != nullptr) {
      // TODO(mvsc): MVSC cannot handle this:
      // impl impl = {.ref = impl_.ref->copy().release()};
      impl impl;
      impl.ref = impl_.ref->copy().release();
      return Val(kind_, impl);
    } else {
      return Val(kind_, impl_);
    }
  }
};


template<> inline auto Val::make<int32_t>(int32_t x) -> Val { return Val(x); }
template<> inline auto Val::make<int64_t>(int64_t x) -> Val { return Val(x); }
template<> inline auto Val::make<float32_t>(float32_t x) -> Val { return Val(x); }
template<> inline auto Val::make<float64_t>(float64_t x) -> Val { return Val(x); }
template<> inline auto Val::make<Ref>(own<Ref>&& x) -> Val {
  return Val(std::move(x));
}

template<> inline auto Val::make<uint32_t>(uint32_t x) -> Val {
  return Val(static_cast<int32_t>(x));
}
template<> inline auto Val::make<uint64_t>(uint64_t x) -> Val {
  return Val(static_cast<int64_t>(x));
}

template<> inline auto Val::get<int32_t>() const -> int32_t { return i32(); }
template<> inline auto Val::get<int64_t>() const -> int64_t { return i64(); }
template<> inline auto Val::get<float32_t>() const -> float32_t { return f32(); }
template<> inline auto Val::get<float64_t>() const -> float64_t { return f64(); }
template<> inline auto Val::get<Ref*>() const -> Ref* { return ref(); }

template<> inline auto Val::get<uint32_t>() const -> uint32_t {
  return static_cast<uint32_t>(i32());
}
template<> inline auto Val::get<uint64_t>() const -> uint64_t {
  return static_cast<uint64_t>(i64());
}


// Traps

using Message = vec<byte_t>;  // null terminated

class Instance;

class Frame {
public:
  Frame() = delete;
  ~Frame();
  void operator delete(void*);

  auto copy() const -> own<Frame>;

  auto instance() const -> Instance*;
  auto func_index() const -> uint32_t;
  auto func_offset() const -> size_t;
  auto module_offset() const -> size_t;
};

class Trap : public Ref {
public:
  Trap() = delete;
  ~Trap();

  static auto make(Store*, const Message& msg) -> own<Trap>;
  auto copy() const -> own<Trap>;

  auto message() const -> Message;
  auto origin() const -> own<Frame>;  // may be null
  auto trace() const -> ownvec<Frame>;  // may be empty, origin first
};


// Shared objects

template<class T>
class Shared {
public:
  Shared() = delete;
  ~Shared();
  void operator delete(void*);
};


// Modules

class Module : public Ref {
public:
  Module() = delete;
  ~Module();

  static auto validate(Store*, const vec<byte_t>& binary) -> bool;
  static auto make(Store*, const vec<byte_t>& binary) -> own<Module>;
  auto copy() const -> own<Module>;

  auto imports() const -> ownvec<ImportType>;
  auto exports() const -> ownvec<ExportType>;

  auto share() const -> own<Shared<Module>>;
  static auto obtain(Store*, const Shared<Module>*) -> own<Module>;

  auto serialize() const -> vec<byte_t>;
  static auto deserialize(Store*, const vec<byte_t>&) -> own<Module>;
};


// Foreign Objects

class Foreign : public Ref {
public:
  Foreign() = delete;
  ~Foreign();

  static auto make(Store*) -> own<Foreign>;
  auto copy() const -> own<Foreign>;
};


// Externals

class Func;
class Global;
class Table;
class Memory;

class Extern : public Ref {
public:
  Extern() = delete;
  ~Extern();

  auto copy() const -> own<Extern>;

  auto kind() const -> ExternKind;
  auto type() const -> own<ExternType>;

  auto func() -> Func*;
  auto global() -> Global*;
  auto table() -> Table*;
  auto memory() -> Memory*;

  auto func() const -> const Func*;
  auto global() const -> const Global*;
  auto table() const -> const Table*;
  auto memory() const -> const Memory*;
};


// Function Instances

class Func : public Extern {
public:
  Func() = delete;
  ~Func();

  using callback = auto (*)(const Val[], Val[]) -> own<Trap>;
  using callback_with_env = auto (*)(void*, const Val[], Val[]) -> own<Trap>;

  static auto make(Store*, const FuncType*, callback) -> own<Func>;
  static auto make(Store*, const FuncType*, callback_with_env,
    void*, void (*finalizer)(void*) = nullptr) -> own<Func>;
  auto copy() const -> own<Func>;

  auto type() const -> own<FuncType>;
  auto param_arity() const -> size_t;
  auto result_arity() const -> size_t;

  auto call(const Val[] = nullptr, Val[] = nullptr) const -> own<Trap>;
};


// Global Instances

class Global : public Extern {
public:
  Global() = delete;
  ~Global();

  static auto make(Store*, const GlobalType*, const Val&) -> own<Global>;
  auto copy() const -> own<Global>;

  auto type() const -> own<GlobalType>;
  auto get() const -> Val;
  void set(const Val&);
};


// Table Instances

class Table : public Extern {
public:
  Table() = delete;
  ~Table();

  using size_t = uint32_t;

  static auto make(
    Store*, const TableType*, const Ref* init = nullptr) -> own<Table>;
  auto copy() const -> own<Table>;

  auto type() const -> own<TableType>;
  auto get(size_t index) const -> own<Ref>;
  auto set(size_t index, const Ref*) -> bool;
  auto size() const -> size_t;
  auto grow(size_t delta, const Ref* init = nullptr) -> bool;
};


// Memory Instances

class Memory : public Extern {
public:
  Memory() = delete;
  ~Memory();

  static auto make(Store*, const MemoryType*) -> own<Memory>;
  auto copy() const -> own<Memory>;

  using pages_t = uint32_t;

  static const size_t page_size = 0x10000;

  auto type() const -> own<MemoryType>;
  auto data() const -> byte_t*;
  auto data_size() const -> size_t;
  auto size() const -> pages_t;
  auto grow(pages_t delta) -> bool;
};


// Module Instances

class Instance : public Ref {
public:
  Instance() = delete;
  ~Instance();

  static auto make(
    Store*, const Module*, const Extern* const[], own<Trap>* = nullptr
  ) -> own<Instance>;
  auto copy() const -> own<Instance>;

  auto exports() const -> ownvec<Extern>;
};


///////////////////////////////////////////////////////////////////////////////

}  // namespace wasm

#endif  // #ifdef __WASM_HH
