#include "vn_capture.h"
#include "JMediaCapturer.h"

extern JavaVM *jvm;

JMediaCapturer::JMediaCapturer(const std::map<std::string, jmethodID> &jms, JNIEnv *jenv, jobject jobj)
    :  jRGB24bytes_(0), jobj_(jobj), jms_(jms)
{
    ctx_ = vn_capture_create();

    struct vn_capture_callbacks_t callbacks = {};
    callbacks.on_error                    = on_error;
    callbacks.on_audio_stats              = on_audio_stats;
    callbacks.create_image_preview_buffer = create_image_preview_buffer;
    callbacks.on_new_preview_image        = on_new_preview_image;
    
    vn_capture_set_callbacks(ctx_, &callbacks, this);

    if (!jvm)
        jenv->GetJavaVM(&jvm);
}

/*virtual*/ JMediaCapturer::~JMediaCapturer()
{    
    vn_capture_destroy(ctx_);

    JNIEnv *jenv;
    jvm->AttachCurrentThread((void**)&jenv, NULL);

    if (jRGB24bytes_)
    {
        jenv->ReleaseByteArrayElements(jRGB24picture_, jRGB24bytes_, JNI_ABORT);   
        jenv->DeleteLocalRef(jRGB24picture_);
    }

    jenv->DeleteGlobalRef(jobj_);    
}

vn_capture_context_t *JMediaCapturer::get_context()
{
    return ctx_;
}
    
/*static*/ void JMediaCapturer::on_error(int error, const char *error_message, void *client_data)
{
    JMediaCapturer *jmc = (JMediaCapturer *)client_data;    

    JNIEnv *jenv;
    jvm->AttachCurrentThread((void**)&jenv, NULL);
    jstring jstr = jenv->NewStringUTF(error_message);
    if (jstr != NULL)
    {
        jenv->CallVoidMethod(jmc->jobj_, jmc->jms_["onError"], error, jstr);  
        jenv->DeleteLocalRef(jstr);
    }
}
    
/*static*/ void JMediaCapturer::on_audio_stats(short samples[], unsigned samples_count, void *client_data)
{
    JMediaCapturer *jmc = (JMediaCapturer *)client_data;    

    JNIEnv *jenv;
    jvm->AttachCurrentThread((void**)&jenv, NULL);

    jshortArray arr = jenv->NewShortArray(samples_count);
            
    jenv->SetShortArrayRegion(arr, 0, samples_count, samples);

    jenv->CallVoidMethod(jmc->jobj_, jmc->jms_["onAudioStats"], arr);

    jenv->DeleteLocalRef(arr);
}

/*static*/ void JMediaCapturer::on_new_preview_image(void *client_data)
{
    JMediaCapturer *jmc = (JMediaCapturer *)client_data;    

    JNIEnv *jenv;
    jvm->AttachCurrentThread((void**)&jenv, NULL);

    // Copy frame data and commit to java
    jenv->ReleaseByteArrayElements(jmc->jRGB24picture_, jmc->jRGB24bytes_, JNI_COMMIT);
}

/*static*/ void* JMediaCapturer::create_image_preview_buffer(int width, int height, void *client_data)
{
    JMediaCapturer *jmc = (JMediaCapturer *)client_data;    

    JNIEnv *jenv;
    jvm->AttachCurrentThread((void**)&jenv, NULL);

    if (jmc->jRGB24bytes_)
    {
        jenv->ReleaseByteArrayElements(jmc->jRGB24picture_, jmc->jRGB24bytes_, JNI_ABORT);   
        jenv->DeleteLocalRef(jmc->jRGB24picture_);
    }

    jmc->jRGB24picture_ = (jbyteArray)jenv->CallObjectMethod(jmc->jobj_, jmc->jms_["createPreviewBuffer"], width, height);
    if (!jmc->jRGB24picture_) //TODO: reporting of JVM memory lack
    {
        return 0;
    }

    jboolean iscopy = JNI_FALSE; 
    jmc->jRGB24bytes_ = jenv->GetByteArrayElements(jmc->jRGB24picture_, &iscopy);

    fprintf(stderr, "jni preview buffer created: %dx%d: %p\n", width, height,  jmc->jRGB24bytes_);

    return jmc->jRGB24bytes_;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////


JNIEXPORT jlong JNICALL Java_com_videonext_mplayer_Capturer_nativeInit(JNIEnv *jenv, jobject jobj)
{
    jclass jcls;
    jmethodID jmid;
    std::map<std::string, jmethodID> jmethods;
    
    jcls = jenv->GetObjectClass(jobj);
        
    jmid = jenv->GetMethodID(jcls, "onError", "(ILjava/lang/String;)V");
    if (jmid == NULL)
    {
        // NoSuchMethodError already thrown
        return 0;
    }
    jmethods.insert(std::pair<std::string, jmethodID>("onError", jmid));

    jmid = jenv->GetMethodID(jcls, "onAudioStats", "([S)V");
    if (jmid == NULL)
    {
        // NoSuchMethodError already thrown
        return 0;
    }
    jmethods.insert(std::pair<std::string, jmethodID>("onAudioStats", jmid));

    jmid = jenv->GetMethodID(jcls, "createPreviewBuffer", "(II)[B");
    if (jmid == NULL)
    {
        // NoSuchMethodError already thrown
        return 0;
    }
    jmethods.insert(std::pair<std::string, jmethodID>("createPreviewBuffer", jmid));

    
    JMediaCapturer *jmc = new JMediaCapturer(jmethods, jenv, jenv->NewGlobalRef(jobj));
    
    return (jlong)jmc;
}

JNIEXPORT jobject JNICALL Java_com_videonext_mplayer_Capturer_nativeGetAudioDevices(JNIEnv *jenv, jobject jobj, jlong captureId)
{
    JMediaCapturer *jmc = (JMediaCapturer *)captureId;    
    vn_capture_context_t *ctx = jmc->get_context();
    
    // Create arraylist
    jclass arrayListClass = jenv->FindClass("java/util/ArrayList");
    jmethodID initArrayListMethod = jenv->GetMethodID(arrayListClass, "<init>", "()V");
    jmethodID addArrayMethod = jenv->GetMethodID(arrayListClass, "add",
                                             "(Ljava/lang/Object;)Z");

    jobject arrayList = jenv->NewObject(arrayListClass, initArrayListMethod);

    char **devices = vn_capture_get_audio_devices(ctx);
    for (char **audio_device = devices; *audio_device != NULL; audio_device++)
    {
        jstring name = jenv->NewStringUTF(*audio_device);

        jenv->CallBooleanMethod(arrayList, addArrayMethod, name);

        jenv->DeleteLocalRef(name);
        
    }
    jenv->DeleteLocalRef(arrayListClass);

    return arrayList;

}

JNIEXPORT jobject JNICALL Java_com_videonext_mplayer_Capturer_nativeGetVideoDevices(JNIEnv *jenv, jobject jobj, jlong captureId)
{
    JMediaCapturer *jmc = (JMediaCapturer *)captureId;    
    vn_capture_context_t *ctx = jmc->get_context();
    
    // Create arraylist
    jclass arrayListClass = jenv->FindClass("java/util/ArrayList");
    jmethodID initArrayListMethod = jenv->GetMethodID(arrayListClass, "<init>", "()V");
    jmethodID addArrayMethod = jenv->GetMethodID(arrayListClass, "add",
                                             "(Ljava/lang/Object;)Z");

    jobject arrayList = jenv->NewObject(arrayListClass, initArrayListMethod);

    char **devices = vn_capture_get_video_devices(ctx);
    for (char **video_device = devices; *video_device != NULL; video_device++)
    {
        jstring name = jenv->NewStringUTF(*video_device);
        
        jenv->CallBooleanMethod(arrayList, addArrayMethod, name);

        jenv->DeleteLocalRef(name);
        
    }
    jenv->DeleteLocalRef(arrayListClass);

    return arrayList;
}

JNIEXPORT jobject JNICALL Java_com_videonext_mplayer_Capturer_nativeGetVideoParams(JNIEnv *jenv, jobject jobj,
                                                                                   jlong captureId, jstring videoDevice)
{
    JMediaCapturer *jmc = (JMediaCapturer *)captureId;    
    vn_capture_context_t *ctx = jmc->get_context();
    
    // Create arraylist
    jclass arrayListClass = jenv->FindClass("java/util/ArrayList");
    jmethodID initArrayListMethod = jenv->GetMethodID(arrayListClass, "<init>", "()V");
    jmethodID addArrayMethod = jenv->GetMethodID(arrayListClass, "add",
                                             "(Ljava/lang/Object;)Z");

    jobject arrayList = jenv->NewObject(arrayListClass, initArrayListMethod);

    const char *videoDeviceStr;
    videoDeviceStr = jenv->GetStringUTFChars(videoDevice, NULL);
    if (videoDeviceStr == NULL) 
    {
        return 0; /* OutOfMemoryError already thrown */
    }
    
    char **resolutions = vn_capture_get_video_device_parameters(ctx, videoDeviceStr);
    for (char **res = resolutions; *res != NULL; res++)
    {
        jstring name = jenv->NewStringUTF(*res);
        
        jenv->CallBooleanMethod(arrayList, addArrayMethod, name);

        jenv->DeleteLocalRef(name);
        
    }
    jenv->DeleteLocalRef(arrayListClass);

    return arrayList;    
}

JNIEXPORT void JNICALL Java_com_videonext_mplayer_Capturer_nativeStartCapture(JNIEnv *jenv, jobject jobj,
                                                                              jlong captureId,
                                                                              jstring audioDevice, jstring videoDevice,
                                                                              jstring videoRes, jstring url, jint bitRate)
{
    JMediaCapturer *jmc = (JMediaCapturer *)captureId;    
    vn_capture_context_t *ctx = jmc->get_context();

    const char *audioDeviceStr;
    audioDeviceStr = jenv->GetStringUTFChars(audioDevice, NULL);
    if (audioDeviceStr == NULL) 
    {
        return; /* OutOfMemoryError already thrown */
    }

    const char *videoDeviceStr;
    videoDeviceStr = jenv->GetStringUTFChars(videoDevice, NULL);
    if (videoDeviceStr == NULL) 
    {
        return; /* OutOfMemoryError already thrown */
    }

    const char *videoResStr;
    videoResStr = jenv->GetStringUTFChars(videoRes, NULL);
    if (videoResStr == NULL) 
    {
        return; /* OutOfMemoryError already thrown */
    }

    const char *urlStr;
    urlStr = jenv->GetStringUTFChars(url, NULL);
    if (urlStr == NULL) 
    {
        return; /* OutOfMemoryError already thrown */
    }

    int result = vn_capture_start(ctx, audioDeviceStr, videoDeviceStr, videoResStr, urlStr, bitRate);

    if (result < 0)
    {
        jclass exClass;
        char exClassName[] = "java/lang/Exception" ;
        char msgBuf[] = "Start failed";

        exClass = jenv->FindClass(exClassName );

        jenv->ThrowNew(exClass, msgBuf);
    }
}

JNIEXPORT void JNICALL Java_com_videonext_mplayer_Capturer_nativeStopCapture(JNIEnv *jenv, jobject jobj,
                                                                              jlong captureId)
{
    JMediaCapturer *jmc = (JMediaCapturer *)captureId;    
    vn_capture_context_t *ctx = jmc->get_context();

    vn_capture_stop(ctx);
}

