#ifndef JNIWRAPPER_H_
#define JNIWRAPPER_H_

#include <assert.h>
#include <jni.h>

// TODO: 同じ名前のFieldとMethod, オーバーロードのあるMethodに対応できていない

// JNI補助クラス
template <size_t N>
struct JValues {
	jvalue vals[N];

	JValues() {
		memset(vals, 0, sizeof(jvalue) * N);
	}
	JValues(const JValues<N>& src) {
		memcpy(vals, src.vals, sizeof(jvalue) * N);
	}
	template <class T1>
	explicit JValues(T1 arg1) {
		assert(N == 1);
		memset(vals, 0, sizeof(jvalue) * N);
		setJValue(vals[0], arg1);
	}
	template <class T1, class T2>
	explicit JValues(T1 arg1, T2 arg2) {
		assert(N == 2);
		memset(vals, 0, sizeof(jvalue) * N);
		setJValue(vals[0], arg1);
		setJValue(vals[1], arg2);
	}
	template <class T1, class T2, class T3>
	explicit JValues(T1 arg1, T2 arg2, T3 arg3) {
		assert(N == 3);
		memset(vals, 0, sizeof(jvalue) * N);
		setJValue(vals[0], arg1);
		setJValue(vals[1], arg2);
		setJValue(vals[2], arg3);
	}
	template <class T1, class T2, class T3, class T4>
	explicit JValues(T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
		assert(N == 4);
		memset(vals, 0, sizeof(jvalue) * N);
		setJValue(vals[0], arg1);
		setJValue(vals[1], arg2);
		setJValue(vals[2], arg3);
		setJValue(vals[3], arg4);
	}
	~JValues() {}
	operator jvalue*() {
		return vals;
	}
};

template <class TA, class T>
class JArray {
public:
	typedef TA Array;
	typedef T Element;
	typedef T* Pointer;

	JArray(JNIEnv* env, Array jArray, jint modeRelease) {
		initialize(env, jArray, modeRelease);
	}
	JArray(JNIEnv* env, Array jArray) {
		initialize(env, jArray, 0);
	}
	JArray(JNIEnv* env, jobject jArray, jint modeRelease) {
		initialize(env, static_cast<Array>(jArray), modeRelease);
	}
	JArray(JNIEnv* env, jobject jArray) {
		initialize(env, static_cast<Array>(jArray), 0);
	}
	~JArray() {
		release();
	}
	void release() {
		release(mModeRelease);
	}
	void release(int mode) {
		if (mjpElements != NULL) {
			releaseArrayElements(mjArray, mode);
			mjpElements = NULL;
		}
	}
	Pointer getElements() {
		return mjpElements;
	}
	jsize getArrayLength() {
		return mEnv->GetArrayLength(mjArray);
	}

private:
	JNIEnv* mEnv;
	Array mjArray;
	Pointer mjpElements;
	jint mModeRelease;

	void initialize(JNIEnv* env, Array jArray, jint modeRelease) {
		mEnv = env;
		mjArray = jArray;
		mModeRelease = modeRelease;
		mjpElements = getArrayElements(jArray);
	}

	Pointer getArrayElements(jbyteArray jArray) {
		return mEnv->GetByteArrayElements(jArray, NULL);
	}
	Pointer getArrayElements(jshortArray jArray) {
		return mEnv->GetShortArrayElements(jArray, NULL);
	}
	Pointer getArrayElements(jintArray jArray) {
		return mEnv->GetIntArrayElements(jArray, NULL);
	}
	void releaseArrayElements(jbyteArray jArray, int mode) {
		mEnv->ReleaseByteArrayElements(jArray, mjpElements, mode);
	}
	void releaseArrayElements(jshortArray jArray, int mode) {
		mEnv->ReleaseShortArrayElements(jArray, mjpElements, mode);
	}
	void releaseArrayElements(jintArray jArray, int mode) {
		mEnv->ReleaseIntArrayElements(jArray, mjpElements, mode);
	}
};

typedef JArray<jbyteArray, jbyte> JByteArray;
typedef JArray<jshortArray, jshort> JShortArray;
typedef JArray<jintArray, jint> JIntArray;

class JClass {
public:
	JClass() : mClass(NULL) {}
	~JClass() {
		// メモリ初期化しておく
		mClass = NULL;
	}

	jclass getObjectClass(JNIEnv* env, jobject obj) {
		if (mClass == NULL) {
			mClass = env->GetObjectClass(obj);
		}
		return mClass;
	}
	jclass findClass(JNIEnv* env, const char* name) {
		if (mClass == NULL) {
			mClass = env->FindClass(name);
		}
		return mClass;
	}
private:
	jclass mClass;
};

#define GET_MEMBER_JCLASS_NAME(_name) mjcc##_name
#define MEMBER_JCLASS_CLASS(_name) JClass GET_MEMBER_JCLASS_NAME(_name);
#define GET_MEMBER_JCLASS(_tbl, _name) ((_tbl).GET_MEMBER_JCLASS_NAME(_name))

template <class T, const char* Name, const char* Sig>
class JField {
public:
	typedef T FieldType;

	JField() : mFieldID(NULL), mField(NULL) {}
	~JField() {
		// メモリ初期化しておく
		mFieldID = NULL;
		mField = NULL;
	}

	jfieldID getFieldID(JNIEnv* env, jclass clazz) {
		if (mFieldID == NULL) {
			mFieldID = env->GetFieldID(clazz, Name, Sig);
		}
		return mFieldID;
	}

	FieldType getField(JNIEnv* env, jobject obj) {
		return getField(env, obj, static_cast<FieldType>(0));
	}

	void setField(JNIEnv* env, jobject obj, FieldType value) {
		return setFieldImpl(env, obj, value);
	}
private:
	jfieldID mFieldID;
	FieldType mField;

	FieldType getField(JNIEnv* env, jobject obj, jobject) {
		if (mField == NULL) {
			mField = static_cast<FieldType>(env->GetObjectField(obj, mFieldID));
		}
		return mField;
	}

	jint getField(JNIEnv* env, jobject obj, jint) {
		return env->GetIntField(obj, mFieldID);
	}

	void setFieldImpl(JNIEnv* env, jobject obj, jint value) {
		env->SetIntField(obj, mFieldID, value);
	}
};

#define DEF_JFIELD_CLASS(_type, _name) \
	extern const char g_jfn##_name[]; \
	extern const char g_jfs##_name[]; \
	typedef JField<_type, g_jfn##_name, g_jfs##_name> JField##_name;
#define IMPL_JFIELD_CLASS_SIG(_name, _sig) \
	extern const char g_jfn##_name[] = #_name; \
	extern const char g_jfs##_name[] = _sig;
#define GET_JFIELD_CLASS(_name) JField##_name
#define GET_MEMBER_JFIELD_NAME(_name) mjfc##_name
#define MEMBER_JFIELD_CLASS(_name) GET_JFIELD_CLASS(_name) GET_MEMBER_JFIELD_NAME(_name);

#define GET_MEMBER_JFIELD(_tbl, _name) ((_tbl).GET_MEMBER_JFIELD_NAME(_name))

template <class T, const char* Name, const char* Sig>
class JMethod {
public:
	typedef T ReturnType;

	JMethod() : mMethodID(NULL) {}
	~JMethod() {
		// メモリ初期化しておく
		mMethodID = NULL;
	}

	jmethodID getMethodID(JNIEnv* env, jclass clazz) {
		if (mMethodID == NULL) {
			mMethodID = env->GetMethodID(clazz, Name, Sig);
		}
		return mMethodID;
	}

	ReturnType callReturnMethod(JNIEnv* env, jobject obj, jvalue* args) {
		return callMethod(env, obj, args, static_cast<JMethod::ReturnType>(0));
	}
	void callVoidMethod(JNIEnv* env, jobject obj, jvalue* args) {
		env->CallVoidMethodA(obj, mMethodID, args);
	}

private:
	jmethodID mMethodID;

	jint callMethod(JNIEnv* env, jobject obj, jvalue* args, jint) {
		return env->CallIntMethodA(obj, mMethodID, args);
	}
	jlong callMethod(JNIEnv* env, jobject obj, jvalue* args, jlong) {
		return env->CallLongMethodA(obj, mMethodID, args);
	}
};

#define DEF_JMETHOD_CLASS(_returnType, _name) \
	extern const char g_jmn##_name[]; \
	extern const char g_jms##_name[]; \
	typedef JMethod<_returnType, g_jmn##_name, g_jms##_name> JMethod##_name;
#define IMPL_JMETHOD_CLASS_SIG(_name, _sig) \
	extern const char g_jmn##_name[] = #_name; \
	extern const char g_jms##_name[] = _sig;
#define GET_JMETHOD_CLASS(_name) JMethod##_name
#define GET_MEMBER_JMETHOD_NAME(_name) mjmc##_name
#define MEMBER_JMETHOD_CLASS(_name) GET_JMETHOD_CLASS(_name) GET_MEMBER_JMETHOD_NAME(_name);

#define GET_MEMBER_JMETHOD(_tbl, _name) ((_tbl).GET_MEMBER_JMETHOD_NAME(_name))

class JAutoSynchronized {
public:
	JAutoSynchronized(JNIEnv* env, jobject obj)
	: mEnv(env), mObj(obj) {
		mRetMonitorEnter = env->MonitorEnter(obj);
		assert(mRetMonitorEnter == JNI_OK);
	}
	~JAutoSynchronized() {
		if (mRetMonitorEnter == JNI_OK) {
			jint ret = mEnv->MonitorExit(mObj);
			assert(ret == JNI_OK);
		}
	}
private:
	JNIEnv* mEnv;
	jobject mObj;
	jint mRetMonitorEnter;
};

class JAutoPtrUTFChars {
public:
	JAutoPtrUTFChars(JNIEnv* env, jstring strj)
	: mEnv(env), mStrj(strj), mUtf8(env->GetStringUTFChars(strj, NULL)) {
	}
	~JAutoPtrUTFChars() {
		if (mUtf8 != NULL) {
			mEnv->ReleaseStringUTFChars(mStrj, mUtf8);
		}
	}
	const char* get() const {
		return mUtf8;
	}
private:
	JNIEnv* mEnv;
	jstring mStrj;
	const char* mUtf8;
};

// JNI補助関数
inline jvalue& setJValue(jvalue& jval, jboolean v) {
	jval.z = v;
	return jval;
}
inline jvalue& setJValue(jvalue& jval, jbyte v) {
	jval.b = v;
	return jval;
}
inline jvalue& setJValue(jvalue& jval, jchar v) {
	jval.c = v;
	return jval;
}
inline jvalue& setJValue(jvalue& jval, jshort v) {
	jval.s = v;
	return jval;
}
inline jvalue& setJValue(jvalue& jval, jint v) {
	jval.i = v;
	return jval;
}
inline jvalue& setJValue(jvalue& jval, jlong v) {
	jval.j = v;
	return jval;
}
inline jvalue& setJValue(jvalue& jval, jfloat v) {
	jval.f = v;
	return jval;
}
inline jvalue& setJValue(jvalue& jval, jdouble v) {
	jval.d = v;
	return jval;
}
inline jvalue& setJValue(jvalue& jval, jobject v) {
	jval.l = v;
	return jval;
}

inline jvalue* createJValueArgs() {
	return NULL;
}
template <class T1>
inline JValues<1> createJValueArgs(T1 arg1) {
	JValues<1> jvals;
	setJValue(jvals.vals[0], arg1);
	return jvals;
}
template <class T1, class T2>
inline JValues<2> createJValueArgs(T1 arg1, T2 arg2) {
	JValues<2> jvals;
	setJValue(jvals.vals[0], arg1);
	setJValue(jvals.vals[1], arg2);
	return jvals;
}
template <class T1, class T2, class T3>
inline JValues<3> createJValueArgs(T1 arg1, T2 arg2, T3 arg3) {
	JValues<3> jvals;
	setJValue(jvals.vals[0], arg1);
	setJValue(jvals.vals[1], arg2);
	setJValue(jvals.vals[2], arg3);
	return jvals;
}
template <class T1, class T2, class T3, class T4>
inline JValues<4> createJValueArgs(T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
	JValues<4> jvals;
	setJValue(jvals.vals[0], arg1);
	setJValue(jvals.vals[1], arg2);
	setJValue(jvals.vals[2], arg3);
	setJValue(jvals.vals[3], arg4);
	return jvals;
}

#endif /* JNIWRAPPER_H_ */
