#include <vector>
#include <napi.h>

#define THROW_BAD_ARGS(FAIL_MSG) throw Napi::TypeError::New(env, FAIL_MSG);
#define THROW_ERROR(FAIL_MSG) throw Napi::Error::New(env, FAIL_MSG);

#pragma GCC diagnostic ignored "-Wtype-limits"
#define CHECK_N_ARGS(MIN_ARGS) if ((MIN_ARGS) > 0 && info.Length() < (MIN_ARGS)) { THROW_BAD_ARGS("Expected " #MIN_ARGS " arguments") }

const napi_property_attributes CONST_PROP = static_cast<napi_property_attributes>(napi_enumerable | napi_configurable);

#define ENTER_CONSTRUCTOR(MIN_ARGS) \
	Napi::HandleScope scope(env);              \
	if (!info.IsConstructCall()) throw Napi::Error::New(env, "Must be called with `new`!"); \
	CHECK_N_ARGS(MIN_ARGS);

#define ENTER_CONSTRUCTOR_POINTER(CLASS, MIN_ARGS) \
	ENTER_CONSTRUCTOR(MIN_ARGS)                    \
	if (!info.Length() || !info[0].IsExternal()){ \
		throw Napi::Error::New(env, "This type cannot be created directly!"); \
	}                                                \
	auto self = this;

#define ENTER_METHOD(CLASS, MIN_ARGS) \
	Napi::Env env = info.Env(); \
	Napi::HandleScope scope(env);                \
	CHECK_N_ARGS(MIN_ARGS);           \
	auto self = this;

#define ENTER_ACCESSOR(CLASS) \
		Napi::HandleScope scope(env);                \
		auto self = info.Holder().Unwrap<CLASS>();

#define UNWRAP_ARG(CLASS, NAME, ARGNO)     \
	if (!info[ARGNO].IsObject())          \
		THROW_BAD_ARGS("Parameter " #NAME " is not an object"); \
	auto NAME = Napi::ObjectWrap<CLASS>::Unwrap(info[ARGNO].As<Napi::Object>()); \
	if (!NAME)                             \
		THROW_BAD_ARGS("Parameter " #NAME " (" #ARGNO ") is of incorrect type");

#define STRING_ARG(NAME, N) \
	if (info.Length() > N){ \
		if (!info[N].IsString()) \
			THROW_BAD_ARGS("Parameter " #NAME " (" #N ") should be string"); \
		NAME = *String::Utf8Value(info[N].ToString()); \
	}

#define DOUBLE_ARG(NAME, N) \
	if (!info[N].IsNumber()) \
		THROW_BAD_ARGS("Parameter " #NAME " (" #N ") should be number"); \
	NAME = info[N].ToNumber()->Value();

#define INT_ARG(NAME, N) \
	if (!info[N].IsNumber()) \
		THROW_BAD_ARGS("Parameter " #NAME " (" #N ") should be number"); \
	NAME = info[N].As<Napi::Number>().Int32Value();

#define BOOL_ARG(NAME, N) \
	NAME = false;    \
	if (info.Length() > N){ \
		if (!info[N].IsBoolean()) \
			THROW_BAD_ARGS("Parameter " #NAME " (" #N ") should be bool"); \
		NAME = info[N].ToBoolean()->Value(); \
	}
