#include "JPeerSDK.h"
#include <pthread.h>

using namespace PeerSDK;


struct fields_t {
    jfieldID  ptr;
	jfieldID  ptrAddress;
	jfieldID  jptr;
};
static fields_t fields;

struct methods_t {
	jmethodID  NativeBaseObject_VideoArrivedEventListener_onVideoArrived;
};
static methods_t methods;

struct clazz_t {
	jclass    NativeBaseObject_VideoArrivedEventListener;
};
static clazz_t clazz;

//-------------------------------------

class JPeerStream : public JObject
{
public:
	JPeerStream(JNIEnv *env, jobject thiz)
	: JObject(env, thiz)
	{
        env->GetJavaVM(&mJvm);
		mJvmThread = pthread_self();

        mVideoArrivedEventListener             = NULL;
		env->SetLongField(thiz, fields.jptr, (jlong) this);
		env->SetLongField(thiz, fields.ptr, (jlong) mStream);
		env->SetLongField(thiz, fields.ptrAddress, (jlong) &mStream);
	}
	
	virtual ~JPeerStream()
    {
		mStream->Dispose();
    }

public:
	void InitObjects(JNIEnv* env, jobject thiz)
	{
	}
	
	void DisposeObjects(JNIEnv* env)
	{		
        if (mVideoArrivedEventListener != NULL)
			env->DeleteGlobalRef(mVideoArrivedEventListener);
	}

public:	
	static void OnVideoArrived(void* tag, VideoArrivedEventArgs const& e)
	{
        JPeerStream* jstream = (JPeerStream*) tag;
        jstream->InvokeVideoArrivedEventListener(e.Channel(), e.PTS());
	}

public:	
	jobject GetVideoArrivedEventListener() const { return mVideoArrivedEventListener; }
	void SetVideoArrivedEventListener(JNIEnv* env, jobject listener)
	{
		if (listener == NULL)
			return;

		if (mVideoArrivedEventListener != NULL)
			env->DeleteGlobalRef(mVideoArrivedEventListener);

		mVideoArrivedEventListener = env->NewGlobalRef(listener);
	}
    
	void InvokeVideoArrivedEventListener(int32 channel, int64 pts)
	{
		JNIEnv* env = mEnv;
		JavaVM* jvm = mJvm;
		if (pthread_self() != mJvmThread)
		{
			jvm->AttachCurrentThread(&env, NULL);
			env->CallVoidMethod(mVideoArrivedEventListener, methods.NativeBaseObject_VideoArrivedEventListener_onVideoArrived, channel, pts);
			jvm->DetachCurrentThread();
            return;
		}

		env->CallVoidMethod(mVideoArrivedEventListener, methods.NativeBaseObject_VideoArrivedEventListener_onVideoArrived, channel, pts);
	}    
	
private:
	JavaVM*    mJvm;
    pthread_t  mJvmThread;
    
public:
	PeerStream* const getStream() const        { return mStream; }


private:
    jobject   mVideoArrivedEventListener;
    
private:	
	PeerStream*              mStream;
};

JNIEXPORT void JNICALL remote_demo_Native_PeerSDK_Peer_Stream_PeerStream_nativeInit
  (JNIEnv *env, jobject thiz)
{
	JPeerStream* jstream = new JPeerStream(env, thiz);
	if (jstream == NULL)
		return;
	jstream->InitObjects(env, thiz);
}

JNIEXPORT void JNICALL remote_demo_Native_PeerSDK_Peer_Stream_PeerStream_nativeRelease
  (JNIEnv *env, jobject thiz)
{
	JPeerStream* jstream = (JPeerStream*) env->GetLongField(thiz, fields.jptr);
	if (jstream == NULL)
		return;
	jstream->DisposeObjects(env);

	delete jstream;
}


JNIEXPORT jboolean JNICALL remote_demo_Native_PeerSDK_Peer_Stream_PeerStream_start
  (JNIEnv *env, jobject thiz)
{
	JPeerStream* jstream = (JPeerStream*) env->GetLongField(thiz, fields.jptr);
	if (jstream == NULL)
		return false;

	PeerStream* stream = jstream->getStream();
	if (stream == NULL)
		return false;

	jboolean ret = (jboolean)(bool) stream->Start();
	return ret;
}

JNIEXPORT void JNICALL remote_demo_Native_PeerSDK_Peer_Stream_PeerStream_addVideoArrivedEventListener
  (JNIEnv *env, jobject thiz, jobject listener)
{
	JPeerStream* jstream = (JPeerStream*) env->GetLongField(thiz, fields.jptr);
	PeerStream* stream = jstream->getStream();
	stream->VideoArrived().Add(jstream, &JPeerStream::OnVideoArrived);
	jstream->SetVideoArrivedEventListener(env, listener);
}


/*
 * Table of methods associated with a single class.
 */
static JNINativeMethod gMethods[] =  
{
	// Method, Signature, FuncPtr
	{"nativeInit",                       "()V",   (void*)remote_demo_Native_PeerSDK_Peer_Stream_PeerStream_nativeInit },
	{"nativeRelease",                    "()V",   (void*)remote_demo_Native_PeerSDK_Peer_Stream_PeerStream_nativeRelease },
	{"start",                            "()Z",   (void*)remote_demo_Native_PeerSDK_Peer_Stream_PeerStream_start },
    {"addVideoArrivedEventListener",     "(Lremote/demo/Native/NativeBaseObject$VideoArrivedEventListener;)V",
							                      (void*)remote_demo_Native_PeerSDK_Peer_Stream_PeerStream_addVideoArrivedEventListener },
};

static void InitJNIGlobalRef(JNIEnv* env)
{
	// get feildID of "PeerStream" Java object
	jclass thizClassRef = env->FindClass("remote/demo/Native/PeerSDK/Peer/Stream/PeerStream");
	fields.ptr        = env->GetFieldID(thizClassRef, "mPtr", "J");
	fields.ptrAddress = env->GetFieldID(thizClassRef, "mPtrAddress", "J");
	fields.jptr       = env->GetFieldID(thizClassRef, "mJPtr", "J");
	env->DeleteLocalRef(thizClassRef);
    
    // get class reference
    jniNewClassGlobalReference(env, clazz.NativeBaseObject_VideoArrivedEventListener,     "remote/demo/Native/NativeBaseObject$VideoArrivedEventListener");
	
    // get the methodID of "onVideoArrived" of VideoArrivedEventListener of NativeBaseObject
	methods.NativeBaseObject_VideoArrivedEventListener_onVideoArrived = 
		env->GetMethodID((jclass) clazz.NativeBaseObject_VideoArrivedEventListener, "onVideoArrived", "(IJ)V");

}
DEFINE_REGISTER_CLASS(remote_demo_Native_PeerSDK_Peer_Stream_PeerStream, remote/demo/Native/PeerSDK/Peer/Stream/PeerStream);
