#include "release.h"
#if (IS_DEBUG)
//#define DEBUG_LOGD true
#define DEBUG_LOGD false
#else
#define DEBUG_LOGD false
#endif

#define  LOG_TAG    "NicoRoJNI"
#if (DEBUG_LOGD)
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#else
#define  LOGD(...)
#endif /* LOG_TAG */


#include <string.h>
#include <jni.h>

#include <unistd.h>
#include <sys/endian.h>

#include <android/log.h>

#include "NicoroFFmpegPlayer.h"

#include "JniWrapper.h"

#include "jp_sourceforge_nicoro_SwfPlayerFragment.h"
#include "jp_sourceforge_nicoro_FFmpegVideoDecoder.h"

static JavaVM* gVM = NULL;

extern "C"
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
	gVM = vm;
	return JNI_VERSION_1_4;
}

extern "C"
void JNI_OnUnload(JavaVM *vm, void *reserved) {
	gVM = NULL;
}

static bool checkNotNullAndThrow(JNIEnv * env, NicoroFFmpegPlayer* pNicoroFFmpegPlayer) {
	if (pNicoroFFmpegPlayer == NULL) {
		jclass clsj = env->FindClass("java/lang/NullPointerException");
		if (clsj != NULL) {
			env->ThrowNew(clsj, "Native instance is null");
			env->DeleteLocalRef(clsj);
		}
		return false;
	}
	return true;
}



JNIEXPORT jlong JNICALL Java_jp_sourceforge_nicoro_SwfPlayerFragment_createNative
  (JNIEnv * env, jobject thiz) {
	LOGD("SwfPlayerFragment#createNative start");
	return reinterpret_cast<jlong>(new NicoroFFmpegPlayer(gVM));
}

JNIEXPORT void JNICALL Java_jp_sourceforge_nicoro_SwfPlayerFragment_destroyNative
  (JNIEnv * env, jobject thiz, jlong nativeInstance) {
	LOGD("SwfPlayerFragment#destroyNative start");
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (pNicoroFFmpegPlayer != NULL) {
		delete pNicoroFFmpegPlayer;
	}
}

JNIEXPORT void JNICALL Java_jp_sourceforge_nicoro_SwfPlayerFragment_loadStreamADPCM
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jbyteArray buffer, jobject ioCallback, jint timeBaseNumerator, jint timeBaseDenominator, jint sampleRate, jint channels) {
	LOGD("SwfPlayerFragment#loadStreamADPCM start");
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoroFFmpegPlayer)) {
		return;
	}

	pNicoroFFmpegPlayer->startJNI(env, thiz);
	pNicoroFFmpegPlayer->loadStreamADPCM(env, thiz, buffer, ioCallback, timeBaseNumerator, timeBaseDenominator, sampleRate, channels);
	pNicoroFFmpegPlayer->endJNI();
}

JNIEXPORT void JNICALL Java_jp_sourceforge_nicoro_SwfPlayerFragment_loadStreamMP3
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jbyteArray buffer, jobject ioCallback, jint timeBaseNumerator, jint timeBaseDenominator, jint sampleRate, jint channels) {
	LOGD("SwfPlayerFragment#loadStreamMP3 start");
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoroFFmpegPlayer)) {
		return;
	}

	pNicoroFFmpegPlayer->startJNI(env, thiz);
	pNicoroFFmpegPlayer->loadStreamMP3(env, thiz, buffer, ioCallback, timeBaseNumerator, timeBaseDenominator, sampleRate, channels);
	pNicoroFFmpegPlayer->endJNI();
}

JNIEXPORT void JNICALL Java_jp_sourceforge_nicoro_SwfPlayerFragment_reopenInputStream
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jbyteArray buffer, jobject ioCallback) {
	LOGD("SwfPlayerFragment#reopenInputStream start");
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoroFFmpegPlayer)) {
		return;
	}

	pNicoroFFmpegPlayer->startJNI(env, thiz);
	pNicoroFFmpegPlayer->reopenInputStream(buffer, ioCallback);
	pNicoroFFmpegPlayer->endJNI();
}

JNIEXPORT jint JNICALL Java_jp_sourceforge_nicoro_SwfPlayerFragment_readFrame
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jbyteArray readBuffer, jobject ioCallback) {
	LOGD("SwfPlayerFragment#readFrame start");
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoroFFmpegPlayer)) {
		return NicoroFFmpegPlayer::CODE_FRAME_TYPE_ERROR_OR_END;
	}

	pNicoroFFmpegPlayer->startJNI(env, thiz);
	jint ret = pNicoroFFmpegPlayer->readFrame(env, thiz, readBuffer, ioCallback);
	pNicoroFFmpegPlayer->endJNI();
	return ret;
}

JNIEXPORT jboolean JNICALL Java_jp_sourceforge_nicoro_SwfPlayerFragment_decodeAudio
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jobject data) {
	LOGD("SwfPlayerFragment#decodeAudio start");
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoroFFmpegPlayer)) {
		return JNI_FALSE;
	}

	pNicoroFFmpegPlayer->startJNI(env, thiz);
	bool ret = pNicoroFFmpegPlayer->decodeAudio(env, thiz, data);
	pNicoroFFmpegPlayer->endJNI();
	if (ret) {
		return JNI_TRUE;
	} else {
		return JNI_FALSE;
	}
}



JNIEXPORT jlong JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_createNative
  (JNIEnv * env, jobject thiz) {
	LOGD("FFmpegVideoDecoder#createNative start");
	return reinterpret_cast<jlong>(new NicoroFFmpegPlayer(gVM));
}

JNIEXPORT void JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_destroyNative
  (JNIEnv * env, jobject thiz, jlong nativeInstance) {
	LOGD("FFmpegVideoDecoder#destroyNative start");
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (pNicoroFFmpegPlayer != NULL) {
		delete pNicoroFFmpegPlayer;
	}
}

JNIEXPORT void JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_loadFile
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jstring file) {
	LOGD("FFmpegVideoDecoder#loadFile start");
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoroFFmpegPlayer)) {
		return;
	}

	pNicoroFFmpegPlayer->startJNI(env, thiz);

	JAutoPtrUTFChars fileUtf8(env, file);
	if (fileUtf8.get() != NULL) {
		pNicoroFFmpegPlayer->loadFileUseStream(fileUtf8.get());
	} else {
		LOGD("get filename NG");
	}

	pNicoroFFmpegPlayer->endJNI();
}

JNIEXPORT void JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_loadStream
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jbyteArray buffer, jboolean isOpened, jobject ioCallback, jobject data) {
	LOGD("FFmpegVideoDecoder#loadStream start");
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoroFFmpegPlayer)) {
		return;
	}

	pNicoroFFmpegPlayer->startJNI(env, thiz);
	pNicoroFFmpegPlayer->loadStream(env, thiz, buffer, isOpened, ioCallback, data);
	pNicoroFFmpegPlayer->endJNI();
}

JNIEXPORT void JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_createAudioTrack
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jobject infoCallback) {
	LOGD("FFmpegVideoDecoder#createAudioTrack start");
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoroFFmpegPlayer)) {
		return;
	}

	pNicoroFFmpegPlayer->startJNI(env, thiz);
	pNicoroFFmpegPlayer->createAudioTrack(env, thiz, infoCallback);
	pNicoroFFmpegPlayer->endJNI();
}

JNIEXPORT void JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_createDrawBuffer
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jobject infoCallback) {
	LOGD("FFmpegVideoDecoder#createDrawBuffer start");
	NicoroFFmpegPlayer* pNicoro = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoro)) {
		return;
	}

	pNicoro->startJNI(env, thiz);
	pNicoro->createDrawBuffer(env, thiz, infoCallback);
	pNicoro->endJNI();
}

JNIEXPORT jint JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_decodeFrame
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jintArray drawBuffer, jint skipVideoFrame, jbyteArray readBuffer, jobject ioCallback, jobject data) {
	LOGD("FFmpegVideoDecoder#decodeFrame start");
	NicoroFFmpegPlayer* pNicoro = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoro)) {
		return NicoroFFmpegPlayer::CODE_DECODE_FRAME_ERROR_OR_END;
	}

	pNicoro->startJNI(env, thiz);
	jint ret = pNicoro->decodeFrame(env, thiz, drawBuffer, skipVideoFrame, readBuffer, ioCallback, data);
	pNicoro->endJNI();
	return ret;
}

JNIEXPORT jint JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_readFrame
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jbyteArray readBuffer, jobject ioCallback) {
	NicoroFFmpegPlayer* pNicoro = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoro)) {
		return NicoroFFmpegPlayer::CODE_FRAME_TYPE_ERROR_OR_END;
	}

	pNicoro->startJNI(env, thiz);
	jint ret = pNicoro->readFrame(env, thiz, readBuffer, ioCallback);
	pNicoro->endJNI();
	return ret;
}

JNIEXPORT jint JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_decodeVideo
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jintArray drawBuffer, jobject data, jint skipVideoFrame) {
	NicoroFFmpegPlayer* pNicoro = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoro)) {
		return NicoroFFmpegPlayer::CODE_DECODE_FRAME_ERROR_OR_END;
	}

	pNicoro->startJNI(env, thiz);
	jint ret = pNicoro->decodeVideo(env, thiz, drawBuffer, data, skipVideoFrame);
	pNicoro->endJNI();
	return ret;
}

JNIEXPORT jint JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_decodeVideo16bit
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jobject drawByteBuffer, jobject data, jint skipVideoFrame) {
	NicoroFFmpegPlayer* pNicoro = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoro)) {
		return NicoroFFmpegPlayer::CODE_DECODE_FRAME_ERROR_OR_END;
	}

	pNicoro->startJNI(env, thiz);
	jint ret = pNicoro->decodeVideo16bit(env, thiz, drawByteBuffer, data, skipVideoFrame);
	pNicoro->endJNI();
	return ret;
}

JNIEXPORT jboolean JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_decodeAudio
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jobject data) {
	NicoroFFmpegPlayer* pNicoro = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoro)) {
		return JNI_FALSE;
	}

	pNicoro->startJNI(env, thiz);
	bool ret = pNicoro->decodeAudio(env, thiz, data);
	pNicoro->endJNI();
	if (ret) {
		return JNI_TRUE;
	} else {
		return JNI_FALSE;
	}
}

JNIEXPORT jint JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_seekFrameBySecond
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jint second) {
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoroFFmpegPlayer)) {
		return -1;
	}

	pNicoroFFmpegPlayer->startJNI(env, thiz);
	int ret = pNicoroFFmpegPlayer->seekFrameBySecond(env, thiz, second);
	pNicoroFFmpegPlayer->endJNI();
	return ret;
}

JNIEXPORT void JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_reopenInputStream
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jbyteArray buffer, jobject ioCallback) {
	LOGD("NicoroFFmpegPlayer#reopenInputStream start");
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoroFFmpegPlayer)) {
		return;
	}

	pNicoroFFmpegPlayer->startJNI(env, thiz);
	pNicoroFFmpegPlayer->reopenInputStream(buffer, ioCallback);
	pNicoroFFmpegPlayer->endJNI();
}

JNIEXPORT void JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_setRescaleFlag
  (JNIEnv * env, jobject thiz, jlong nativeInstance, jint flag) {
	LOGD("NicoroFFmpegPlayer#setRescaleFlag start");
	NicoroFFmpegPlayer* pNicoroFFmpegPlayer = reinterpret_cast<NicoroFFmpegPlayer *>(nativeInstance);
	if (!checkNotNullAndThrow(env, pNicoroFFmpegPlayer)) {
		return;
	}

	pNicoroFFmpegPlayer->startJNI(env, thiz);
	pNicoroFFmpegPlayer->setRescaleFlag(flag);
	pNicoroFFmpegPlayer->endJNI();
}

JNIEXPORT void JNICALL Java_jp_sourceforge_nicoro_FFmpegVideoDecoder_copyDirect
  (JNIEnv * env, jclass, jobject src, jint srcPos, jshortArray dst, jint dstPos, jint lengthBytes) {
	const void* pSrc = env->GetDirectBufferAddress(src);
	if (pSrc == NULL) {
		LOGD("NicoroFFmpegPlayer#copyDirect src is not direct");
		jclass clsj = env->FindClass("java/lang/NullPointerException");
		if (clsj != NULL) {
			env->ThrowNew(clsj, "src is not direct");
			env->DeleteLocalRef(clsj);
		}
		return;
	}
	const void* pCpySrc = static_cast<const jbyte*>(pSrc) + srcPos;
	const int srcLength = env->GetDirectBufferCapacity(src);

	JShortArray jaDst(env, dst, 0);
	jshort* pDst = jaDst.getElements();
	if (pDst == NULL) {
		LOGD("NicoroFFmpegPlayer#copyDirect dst get failed");
		jclass clsj = env->FindClass("java/lang/NullPointerException");
		if (clsj != NULL) {
			env->ThrowNew(clsj, "dst get failed");
			env->DeleteLocalRef(clsj);
		}
		return;
	}
	jshort* pCpyDst = pDst + dstPos;
	const int dstLength = jaDst.getArrayLength();

	if (srcPos + srcLength < lengthBytes || dstPos + dstLength < lengthBytes) {
		LOGD("NicoroFFmpegPlayer#copyDirect IndexOutOfBoundsException");
		jclass clsj = env->FindClass("java/lang/IndexOutOfBoundsException");
		if (clsj != NULL) {
			env->ThrowNew(clsj, "");
			env->DeleteLocalRef(clsj);
		}
		return;
	}

	memcpy(pCpyDst, pCpySrc, lengthBytes);

	jaDst.release();
}
