#include "macros.h"
#include <opencv2/core.hpp>

#ifndef __FF_COREUTILS_H__
#define __FF_COREUTILS_H__

#define FF_ASSERT_INDEX_RANGE(idx, max, what) \
	if (idx < 0 || max < idx) {   \
		return tryCatch.throwError("Index out of bounds: " + std::string(what) + " at index " + std::to_string(idx));	\
	} 

#define FF_APPLY_FUNC(func, arg0, arg1, ret) func(arg0, arg1, ret);
#define FF_APPLY_CLASS_FUNC(func, arg0, arg1, ret) ret = ((arg0).*(func))(arg1);
#define FF_APPLY_OPERATOR(op, arg0, arg1, ret) ret = arg0 op arg1;

/* TODO fix non scalar matrix multiplication, division */

#define FF_SELF_OPERATOR(func) \
	v8::Local<v8::Object> jsObj = FF::newInstance(Nan::New(constructor));	\
	func(unwrapSelf(info), unwrapClassPtrUnchecked(jsObj)->self);			\
	return info.GetReturnValue().Set(jsObj); 

#define FF_SCALAR_OPERATOR(func, applyFunc, clazz, methodName) \
	FF::TryCatch tryCatch(methodName); \
	if (!info[0]->IsNumber()) {	\
		return tryCatch.throwError("expected arg to be a Scalar"); \
	} \
	v8::Local<v8::Object> jsObj = FF::newInstance(Nan::New(constructor)); \
	applyFunc( \
		func,  \
		unwrapSelf(info), \
		info[0]->ToNumber(Nan::GetCurrentContext()).ToLocalChecked()->Value(), \
		unwrapClassPtrUnchecked(jsObj)->self \
	);  \
	return info.GetReturnValue().Set(jsObj);  

#define FF_OPERATOR(func, applyFunc, clazz, methodName) \
	FF::TryCatch tryCatch(methodName); \
	if (!Nan::New(constructor)->HasInstance(info[0])) { \
			return tryCatch.throwError("expected arg to be an instance of " + std::string(#clazz)); \
	} \
	v8::Local<v8::Object> jsObj = FF::newInstance(Nan::New(constructor)); \
	applyFunc( \
		func, \
		unwrapSelf(info), \
		unwrapClassPtrUnchecked(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked())->self, \
		unwrapClassPtrUnchecked(jsObj)->self  \
	); \
	return info.GetReturnValue().Set(jsObj);

#define FF_OPERATOR_RET_SCALAR(func, applyFunc, clazz, methodName) \
	FF::TryCatch tryCatch(methodName); \
	if (!Nan::New(constructor)->HasInstance(info[0])) { \
			return tryCatch.throwError("expected arg to be an instance of " + std::string(#clazz)); \
	} \
	double ret; \
	applyFunc( \
		func, \
		unwrapSelf(info), \
		unwrapClassPtrUnchecked(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked())->self, \
		ret 	\
	); \
	return info.GetReturnValue().Set(ret);

#define FF_PROTO_SET_ARITHMETIC_OPERATIONS(ctor) \
	Nan::SetPrototypeMethod(ctor, "add", Add); \
	Nan::SetPrototypeMethod(ctor, "sub", Sub); \
	Nan::SetPrototypeMethod(ctor, "mul", Mul); \
	Nan::SetPrototypeMethod(ctor, "div", Div); \

#define FF_PROTO_SET_MATRIX_OPERATIONS(ctor) \
	FF_PROTO_SET_ARITHMETIC_OPERATIONS(ctor) \
	Nan::SetPrototypeMethod(ctor, "hMul", HMul); \
	Nan::SetPrototypeMethod(ctor, "hDiv", HDiv); \
	Nan::SetPrototypeMethod(ctor, "absdiff", Absdiff); \
	Nan::SetPrototypeMethod(ctor, "exp", Exp); \
	Nan::SetPrototypeMethod(ctor, "log", Log); \
	Nan::SetPrototypeMethod(ctor, "sqrt", Sqrt); \
	Nan::SetPrototypeMethod(ctor, "dot", Dot);

#define FF_PROTO_SET_MAT_OPERATIONS(ctor)  \
	FF_PROTO_SET_MATRIX_OPERATIONS(ctor) \
	Nan::SetPrototypeMethod(ctor, "and", And); \
	Nan::SetPrototypeMethod(ctor, "or", Or); \
	Nan::SetPrototypeMethod(ctor, "bitwiseAnd", BitwiseAnd); \
	Nan::SetPrototypeMethod(ctor, "bitwiseNot", BitwiseNot); \
	Nan::SetPrototypeMethod(ctor, "bitwiseOr", BitwiseOr); \
	Nan::SetPrototypeMethod(ctor, "bitwiseXor", BitwiseXor); \
	Nan::SetPrototypeMethod(ctor, "abs", Abs); \
	Nan::SetPrototypeMethod(ctor, "transpose", Transpose); \
	Nan::SetPrototypeMethod(ctor, "inv", Inv); \
	Nan::SetPrototypeMethod(ctor, "determinant", Determinant); \
	Nan::SetPrototypeMethod(ctor, "matMul", MatMul);

#define FF_INIT_ARITHMETIC_OPERATIONS(clazz) \
	static NAN_METHOD(Add) { \
		FF_OPERATOR(+, FF_APPLY_OPERATOR, clazz, "Add"); \
	} \
	static NAN_METHOD(Sub) { \
		FF_OPERATOR(-, FF_APPLY_OPERATOR, clazz, "Sub"); \
	} \
	static NAN_METHOD(Mul) { \
		FF_SCALAR_OPERATOR(*, FF_APPLY_OPERATOR, clazz, "Mul");	\
	} \
	static NAN_METHOD(Div) { \
		FF_SCALAR_OPERATOR(/, FF_APPLY_OPERATOR, clazz, "Div");	\
	} \

#define FF_INIT_MATRIX_OPERATIONS(clazz) \
	FF_INIT_ARITHMETIC_OPERATIONS(clazz) \
	static NAN_METHOD(HMul) { 	\
		FF_OPERATOR(cv::multiply, FF_APPLY_FUNC, clazz, "HMul"); \
	} \
	static NAN_METHOD(HDiv) { 	\
		FF_OPERATOR(cv::divide, FF_APPLY_FUNC, clazz, "HDiv"); 	\
	} \
	static NAN_METHOD(Absdiff) { \
		FF_OPERATOR(cv::absdiff, FF_APPLY_FUNC, clazz, "Absdiff"); \
	} \
	static NAN_METHOD(Exp) { 	\
		FF_SELF_OPERATOR(cv::exp); \
	} \
	static NAN_METHOD(Log) { 	\
		FF_SELF_OPERATOR(cv::log); \
	}   \
	static NAN_METHOD(Sqrt) { 	\
		FF_SELF_OPERATOR(cv::sqrt); \
	} \

#define FF_INIT_MAT_OPERATIONS() \
	FF_INIT_MATRIX_OPERATIONS(Mat);  \
	static NAN_METHOD(And) { \
		FF_OPERATOR(&, FF_APPLY_OPERATOR, Mat, "And"); \
	} \
	static NAN_METHOD(Or) { \
		FF_OPERATOR(|, FF_APPLY_OPERATOR, Mat, "Or"); \
	} \
	static NAN_METHOD(BitwiseAnd) { \
		FF_OPERATOR(cv::bitwise_and, FF_APPLY_FUNC, Mat, "BitwiseAnd");\
	} \
	static NAN_METHOD(BitwiseNot) { \
		FF_SELF_OPERATOR(cv::bitwise_not); \
	} \
	static NAN_METHOD(BitwiseOr) { \
		FF_OPERATOR(cv::bitwise_or, FF_APPLY_FUNC, Mat, "BitwiseOr");	\
	} \
	static NAN_METHOD(BitwiseXor) { \
		FF_OPERATOR(cv::bitwise_xor, FF_APPLY_FUNC, Mat, "BitwiseXor");\
	} \
	static NAN_METHOD(Abs) { 	\
		return info.GetReturnValue().Set(Converter::wrap(cv::abs(unwrapSelf(info)))); \
	} \
	static NAN_METHOD(Determinant) { \
		return info.GetReturnValue().Set(cv::determinant(Mat::unwrapSelf(info))); \
	} \
	static NAN_METHOD(Transpose) { \
		FF_SELF_OPERATOR(cv::transpose); \
	} \
	static NAN_METHOD(Inv) { \
		FF_SELF_OPERATOR(cv::invert); \
	} \
	static NAN_METHOD(MatMul) { \
		FF_OPERATOR(*, FF_APPLY_OPERATOR, Mat, "MatMul"); \
	} 

#define FF_INIT_VEC2_OPERATIONS()	FF_INIT_MATRIX_OPERATIONS(Vec2);
#define FF_INIT_VEC3_OPERATIONS()	FF_INIT_MATRIX_OPERATIONS(Vec3);
#define FF_INIT_VEC4_OPERATIONS()	FF_INIT_MATRIX_OPERATIONS(Vec4);
#define FF_INIT_VEC6_OPERATIONS()	FF_INIT_MATRIX_OPERATIONS(Vec6);

namespace FF {
	template<int cn>
	static v8::Local<v8::Array> vecToJsArr(cv::Vec<double, cn> vec) {
		v8::Local<v8::Array> jsVec = Nan::New<v8::Array>(cn);
		for (int i = 0; i < cn; i++) Nan::Set(jsVec, i, Nan::New(vec[i]));
		return jsVec;
	}
}

#endif