#include <stdio.h>
#include <android/log.h>  
extern "C" {
#include "libavutil/log.h"
}

#include "VN_JPlayer.h"
#include "JsonObjectsTracker.h"
#include "GraphicsProxy.h"

#define DEBUG_TAG "VN_JPlayer"
JavaVM *g_jvm;

std::string state_to_str(VN_PLAYER_STREAM_STATE state)
{
    std::string state_str;
    switch (state)
    {
        case IDLE:
            state_str="IDLE";
            break;
        case PLAY_FROM_SERVER:
            state_str="PLAY_FROM_SERVER";
            break;
        case PLAY_FROM_BUFFER:
            state_str="PLAY_FROM_BUFFER";
            break;
        case STOPPED:
            state_str="STOPPED";
            break;
        case OPENING:
            state_str="OPENING";
            break;
        case BUFFERING:
            state_str="BUFFERING";
            break;
        case OPENED:
            state_str="OPENED";
            break;
        default:
            state_str="UNKNOWN";
    }
		
    return state_str;
}


static void av_log_callback(void *ptr, int level, const char *fmt, va_list vl)                                                        
{                                                                                                                                         
    va_list vl2;                                                                                                                          
    char line[1024] = {0};                                                                                                                      
    static int print_prefix = 1;                                                                                                          
                                                                                                                                          
    va_copy(vl2, vl);                                                                                                                     
    av_log_default_callback(ptr, level, fmt, vl);                                                                                         
    av_log_format_line(ptr, level, fmt, vl2, line, sizeof(line), &print_prefix);                                                          
    va_end(vl2);                                                                                                                          

    __android_log_print(ANDROID_LOG_INFO, DEBUG_TAG, "%s", line);
}               


VN_JPlayer::VN_JPlayer(JavaVM *jvm, 
                       jobject jobj, 
                       const std::map<std::string, jmethodID> &jms)
    : jvm_(jvm), jobj_(jobj), jmethods_(jms), jpict_(0), jpict_data_(0)
    //, jsOT_(new JsonObjectsTracker)
{

    memset(&conf_, 0, sizeof(conf_));
    conf_.cache_size = 2;
    conf_.decoder_type = ::DECODER_SW;
    conf_.pixel_format = ::PIX_RGB32;
    conf_.rtp_transport = RTP_TRANSPORT_TCP;
    conf_.client_data = this;
    conf_.buffer_len = 1000;

    struct vn_player_callbacks_t callbacks = {};
    //memset(&callbacks, 0, sizeof(callbacks));
    callbacks.buffer_changed      = buffer_changed;
    callbacks.new_frame           = new_frame;
    callbacks.new_stream          = new_stream;
    callbacks.state_changed       = state_changed;
    callbacks.stream_removed      = stream_removed;
    //callbacks.create_image_buffer = create_image_buffer;
    //callbacks.lock_image_buffer   = lock_image_buffer;
    //callbacks.unlock_image_buffer = unlock_image_buffer;
    callbacks.msg_log             = msg_log;
    
    ctx_ = vn_player_create(&conf_);
    vn_player_set_callbacks(ctx_, &callbacks);

    av_log_set_callback(av_log_callback);
}

/*virtual*/ VN_JPlayer::~VN_JPlayer()
{
    //delete jsOT_;
}

void VN_JPlayer::open(const std::string &url)
{
    vn_player_open(ctx_, url.c_str());
}

void VN_JPlayer::play()
{
    vn_player_play(ctx_);
}
    
void VN_JPlayer::resume(int direction)
{
    vn_player_resume(ctx_, direction);
}
	
void VN_JPlayer::pause()
{
    vn_player_pause(ctx_);
}
	
void VN_JPlayer::teardown(JNIEnv *jenv)
{
    if(ctx_)
        vn_player_destroy(ctx_);

    if (jpict_)
    {
        jenv->ReleaseIntArrayElements(jpict_, jpict_data_, JNI_ABORT);
        jenv->DeleteGlobalRef(jpict_);
    }

    jenv->DeleteGlobalRef(jobj_);
}
	
void VN_JPlayer::jump(time_t timestamp)
{
    vn_player_move_to_ts(ctx_, timestamp);
}

/*static*/ void VN_JPlayer::lock_image_buffer(void *client_data)
{
    VN_JPlayer *p = (VN_JPlayer*)client_data;

    JNIEnv *jenv;
    p->jvm_->AttachCurrentThread(&jenv, NULL);

    jenv->CallVoidMethod(p->jobj_, p->jmethods_["lockImageBuffer"]);

    p->jvm_->DetachCurrentThread();
}

/*static*/ void VN_JPlayer::unlock_image_buffer(void *client_data)
{
    VN_JPlayer *p = (VN_JPlayer*)client_data;

    JNIEnv *jenv;
    p->jvm_->AttachCurrentThread(&jenv, NULL);

    jenv->CallVoidMethod(p->jobj_, p->jmethods_["unlockImageBuffer"]);

    p->jvm_->DetachCurrentThread();
}

/*static void VN_JPlayer::new_frame(const vn_player_frame_t *frame,
                                      const vn_player_cache_boundaries_t *bounds, 
                                      void *client_data)
{	
    VN_JPlayer *p = (VN_JPlayer*)client_data;

    JNIEnv *jenv;
    p->jvm_->AttachCurrentThread(&jenv, NULL);

    if (frame->stream_info->type == VIDEO && frame->data[0] && p->jpict_data_)
    {
        //__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "New video frame: %p, %d", frame, frame->data_size[0]);

        // Copy frame data and commit to java
        memcpy(p->jpict_data_, frame->data[0], frame->width * frame->height * 4);
        jenv->ReleaseIntArrayElements(p->jpict_, p->jpict_data_, JNI_COMMIT);

        // parse objects data
        jbyteArray barr = NULL;
        /*p->jsOT_->setFrameTime(frame->captured_time);
        
        if (frame->objects_data_size > 2)
        {
            //MutexGuard mg(jmp->analyticsKeysVisualisationMutex_);
            
            // json data
            //jmp->jsOT_->setMetadataInfo(jmp->analyticsKeysVisualisation_);
            p->jsOT_->setObjectsData((const char*)frame->objects_data);
        }

        GraphicsProxy grp;
        grp.setStrokeWidth( 1 );
        grp.setColor( Color( 0, 255, 0 ) );
        grp.setFont( "Verdana", 14, Graphics::Regular );
        p->jsOT_->parseAndDraw( &grp, Rectangle2( 0, 0, frame->width, frame->height ), Rectangle2( 0, 0, frame->width, frame->height ), 1.0 );
        grp.finalizeSequence();
        
        barr = jenv->NewByteArray(grp.binaryData.size());
            
        jenv->SetByteArrayRegion(barr, 0, grp.binaryData.size(), (jbyte*)grp.binaryData.data());

//        if (frame->objects_data_size)
//            __android_log_print(ANDROID_LOG_INFO, DEBUG_TAG, "objs data: (%s), %d", frame->objects_data, frame->objects_data_size);
        *//*
        // Call "onNewVideoFrame" method to inform java that we have new frame
        jenv->CallVoidMethod(p->jobj_, p->jmethods_["onNewVideoFrame"],
                             //p->jpict_, frame->width, frame->height, barr,
                             frame->captured_time->tv_sec, frame->captured_time->tv_usec);
    }

    //p->jvm_->DetachCurrentThread();
}*/

/*static*/ void VN_JPlayer::new_frame(const vn_player_frame_t *frame,
                                      const vn_player_cache_boundaries_t *bounds,
                                      void *client_data)
{
    VN_JPlayer *p = (VN_JPlayer*)client_data;

    if (frame->stream_info->type == VIDEO && frame->data[0])
    {
        //__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "New video frame: %p, %d", frame, frame->data_size[0]);
        JNIEnv *jenv;
        p->jvm_->AttachCurrentThread(&jenv, NULL);

        if(p->jpict_data_ == 0)
        {
            //float aspect_ratio = dar ? dar : 1.0f;
            float aspect_ratio = 1.0f;
            jintArray jpict = (jintArray)jenv->CallObjectMethod(
                    p->jobj_,
                    p->jmethods_["allocImageBuf"],
                    frame->width, frame->height, aspect_ratio);
            if (jpict == NULL) //TODO: reporting of JVM memory lack
                return;

            __android_log_print(ANDROID_LOG_INFO, DEBUG_TAG, "jni image buffer allocated: %dx%d",
                                frame->width, frame->height);

            p->jpict_ = (jintArray)jenv->NewGlobalRef(jpict);

            jboolean iscopy = JNI_FALSE;
            p->jpict_data_ = jenv->GetIntArrayElements(p->jpict_, &iscopy);

            jenv->DeleteLocalRef(jpict);
        }

        // Copy frame data and commit to java
        memcpy(p->jpict_data_, frame->data[0], frame->width * frame->height * 4);
        jenv->ReleaseIntArrayElements(p->jpict_, p->jpict_data_, JNI_COMMIT);

        // parse objects data
        jbyteArray barr = NULL;
        /*p->jsOT_->setFrameTime(frame->captured_time);

        if (frame->objects_data_size > 2)
        {
            //MutexGuard mg(jmp->analyticsKeysVisualisationMutex_);

            // json data
            //jmp->jsOT_->setMetadataInfo(jmp->analyticsKeysVisualisation_);
            p->jsOT_->setObjectsData((const char*)frame->objects_data);
        }

        GraphicsProxy grp;
        grp.setStrokeWidth( 1 );
        grp.setColor( Color( 0, 255, 0 ) );
        grp.setFont( "Verdana", 14, Graphics::Regular );
        p->jsOT_->parseAndDraw( &grp, Rectangle2( 0, 0, frame->width, frame->height ), Rectangle2( 0, 0, frame->width, frame->height ), 1.0 );
        grp.finalizeSequence();

        barr = jenv->NewByteArray(grp.binaryData.size());

        jenv->SetByteArrayRegion(barr, 0, grp.binaryData.size(), (jbyte*)grp.binaryData.data());

//        if (frame->objects_data_size)
//            __android_log_print(ANDROID_LOG_INFO, DEBUG_TAG, "objs data: (%s), %d", frame->objects_data, frame->objects_data_size);
        */
        // Call "onNewVideoFrame" method to inform java that we have new frame
        jenv->CallVoidMethod(p->jobj_, p->jmethods_["onNewVideoFrame"],
                //p->jpict_, frame->width, frame->height, barr,
                             frame->captured_time->tv_sec, frame->captured_time->tv_usec);

        p->jvm_->DetachCurrentThread();
    }
}

/*static*/ void VN_JPlayer::buffer_changed(const vn_player_cache_boundaries_t *bounds, void *client_data)
{
}

/*static*/ void VN_JPlayer::state_changed(VN_PLAYER_STREAM_STATE state, const vn_player_result_status_t *status, 
                                          void *client_data)
{
    VN_JPlayer *p = (VN_JPlayer*)client_data;

    JNIEnv *jenv;
    p->jvm_->AttachCurrentThread(&jenv, NULL);

    jstring jstr = jenv->NewStringUTF(status->error_str?status->error_str:"");
    if (jstr == NULL)
        return;

    __android_log_print(ANDROID_LOG_INFO, DEBUG_TAG, "State changed---");
    __android_log_print(ANDROID_LOG_INFO, DEBUG_TAG, "State: %d (%s)", state, state_to_str(state).c_str());   
    __android_log_print(ANDROID_LOG_INFO, DEBUG_TAG, "Result status: %d (%s)", status->error_code, status->error_str?status->error_str:"");

    //if(state == OPENED)
    //    p->play();

    jenv->CallVoidMethod(p->jobj_, p->jmethods_["onStateChanged"], state, status->error_code,  jstr);

    jenv->DeleteLocalRef(jstr);

    p->jvm_->DetachCurrentThread();
}

/*static*/ void VN_JPlayer::new_stream(const vn_player_stream_info_t *streamInfo, void *client_data)
{
//    __android_log_print(ANDROID_LOG_INFO, DEBUG_TAG, "NEW STREAM ");
}

/*static*/ void VN_JPlayer::stream_removed(const vn_player_stream_info_t *streamInfo, void *client_data)
{
//    __android_log_print(ANDROID_LOG_INFO, DEBUG_TAG, "STREAM REMOVED!");
}

/*static*/void VN_JPlayer::msg_log(const char *msg, void *client_data)
{
    __android_log_print(ANDROID_LOG_INFO, DEBUG_TAG, "%s", msg);
}
	
///////////// JNI methods implementation ///////////////////
JNIEXPORT jlong JNICALL Java_com_videonext_MediaPlayer_nativeOpen(JNIEnv *jenv, jobject jobj, jstring url)
{
    jclass jcls;
    jmethodID jmid;
    std::map<std::string, jmethodID> jmethods;
    
    jcls = jenv->GetObjectClass(jobj);
      
    jmid = jenv->GetMethodID(jcls, "onStateChanged", "(IILjava/lang/String;)V");
    if (jmid == NULL)         // NoSuchMethodError already thrown
        return 0;
    jmethods.insert(std::pair<std::string, jmethodID>("onStateChanged", jmid));
    
 
    //jmid = jenv->GetMethodID(jcls, "onNewVideoFrame", "([III[BII)V");
    jmid = jenv->GetMethodID(jcls, "onNewVideoFrame", "(II)V");
    if (jmid == NULL)
        return 0;
    jmethods.insert(std::pair<std::string, jmethodID>("onNewVideoFrame", jmid));

    jmid = jenv->GetMethodID(jcls, "allocImageBuf", "(II)[I");
    if (jmid == NULL)
        return 0;
    jmethods.insert(std::pair<std::string, jmethodID>("allocImageBuf", jmid));

    jmid = jenv->GetMethodID(jcls, "lockImageBuffer", "()V");
    if (jmid == NULL)
        return 0;
    jmethods.insert(std::pair<std::string, jmethodID>("lockImageBuffer", jmid));

    jmid = jenv->GetMethodID(jcls, "unlockImageBuffer", "()V");
    if (jmid == NULL)
        return 0;
    jmethods.insert(std::pair<std::string, jmethodID>("unlockImageBuffer", jmid));

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

    VN_JPlayer *player = new VN_JPlayer(g_jvm, jenv->NewGlobalRef(jobj), jmethods);

    player->open(url_buf);
 
    jenv->ReleaseStringUTFChars(url, url_buf);

    return (jlong)player;
}

JNIEXPORT void JNICALL Java_com_videonext_MediaPlayer_nativePlay(JNIEnv *env, jobject obj, jlong p)
{
    VN_JPlayer *player = (VN_JPlayer*)p;
    if (player == 0)
        return;

    player->play();
}

JNIEXPORT void JNICALL Java_com_videonext_MediaPlayer_nativeStop(JNIEnv *env, jobject obj, jlong p)
{
    VN_JPlayer *player = (VN_JPlayer*)p;
    if (player == 0)
        return;

    player->pause();
    usleep(1000000);
    player->teardown(env);
    delete player;
}

JNIEXPORT void JNICALL Java_com_videonext_MediaPlayer_nativePause(JNIEnv *env, jobject obj, jlong p)
{
    VN_JPlayer *player = (VN_JPlayer*)p;
    if (player == 0)
        return;

    player->pause();
}

JNIEXPORT void JNICALL Java_com_videonext_MediaPlayer_nativeResume(JNIEnv *env, jobject obj, jlong p, jint direction)
{
    VN_JPlayer *player = (VN_JPlayer*)p;
    if (player == 0)
        return;

    player->resume(direction);
}

JNIEXPORT void JNICALL Java_com_videonext_MediaPlayer_nativeJump(JNIEnv *env, jobject obj, jlong p, jint timestamp)
{
    VN_JPlayer *player = (VN_JPlayer*)p;
    if (player == 0)
        return;

    player->jump(timestamp);
}

JNIEXPORT jint JNI_OnLoad(JavaVM *jvm, void *reserved) 
{ 
    // store java virtual machine in global variable 
    g_jvm = jvm; 
    return JNI_VERSION_1_6; 
} 
