/*  Copyright (c) MediaArea.net SARL. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license that can
 *  be found in the License.html file in the root of the source tree.
 */

//---------------------------------------------------------------------------
#include "MediaInfo/Setup.h"
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
#if defined(MEDIAINFO_JNI_YES)
//---------------------------------------------------------------------------

#ifdef __ANDROID__
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
#include <bitset>
#include <ZenLib/Ztring.h>

#include "ThirdParty/jni/jni.h"
#include "MediaInfo/MediaInfo_Internal.h"

#define BUFFER_SIZE 1024 * 1024

using namespace MediaInfoLib;

/**
 * Helpers
 */

//------------------------------------------------------------------------------
static Ztring Jstring2Ztring(JNIEnv* _env, jstring jstr)
{
    const char* str = _env->GetStringUTFChars(jstr, JNI_FALSE);

    Ztring toReturn = Ztring().From_UTF8(str);

    _env->ReleaseStringUTFChars(jstr, str);

    return toReturn;
}

//------------------------------------------------------------------------------
static MediaInfo_Internal* GetMiObj(JNIEnv* _env, jobject _this)
{
    jclass cls = _env->GetObjectClass(_this);
    if (cls == NULL)
        return NULL;

    jfieldID miId = _env->GetFieldID(cls, "mi", "J");
    if (miId == NULL)
        return NULL;

    MediaInfo_Internal* mi = (MediaInfo_Internal*)_env->GetLongField(_this, miId);
    if (mi == NULL)
        return NULL;

    return mi;
}

//------------------------------------------------------------------------------
static jlong JNI_Init(JNIEnv*, jobject)
{
    return (jlong)new MediaInfo_Internal();
}


//------------------------------------------------------------------------------
static jint JNI_Destroy(JNIEnv* _env, jobject _this)
{
    delete GetMiObj(_env, _this);

    return 0;
}

/**
 * Interface
 */

//------------------------------------------------------------------------------
static jint JNI_Open(JNIEnv* _env, jobject _this, jstring name)
{
    MediaInfo_Internal* mi = GetMiObj(_env, _this);
    if (mi == NULL)
        return 1;

    return (jint)mi->Open(Jstring2Ztring(_env, name));
}

//------------------------------------------------------------------------------
#ifdef __ANDROID__
static jint JNI_OpenFd(JNIEnv* _env, jobject _this, jint fd, jstring name)
{
    MediaInfo_Internal* mi = GetMiObj(_env, _this);
    if (mi == NULL)
        return 1;

    int64u size = (int64u)-1;

    struct stat64 s;
    if (fstat64(fd, &s) != -1 && s.st_size > 0)
        size = (int64u)s.st_size;

    mi->Option(__T("File_FileName"), Jstring2Ztring(_env, name));

    int8u* buffer = new int8u[BUFFER_SIZE];

    mi->Open_Buffer_Init(size, 0);
    while (1) {

        ssize_t count = read(fd, (void*)buffer, BUFFER_SIZE);

        if (count < 0)
            break; // error

        std::bitset<32> state = mi->Open_Buffer_Continue(buffer, (size_t)count);

        // bit 3 set means finalized
        if (state.test(3))
            break;

        // test if there is a MediaInfo request to go elsewhere
        int64u seekTo = mi->Open_Buffer_Continue_GoTo_Get();
        if (seekTo != (int64u)-1)
        {
            off64_t offset = lseek64(fd, (off64_t)seekTo, SEEK_SET);
            if (offset >= 0) {
                mi->Open_Buffer_Init(size, (int64u)offset); // inform MediaInfo we have seek
                continue;
            }
        }

        // EOF and no seekTo request
        if (count == 0)
            break;
    }
    mi->Open_Buffer_Finalize();

    delete[] buffer;
    close(fd);

    return 0;
}
#endif //__ANDROID__

//------------------------------------------------------------------------------
static jint JNI_Open_Buffer_Init(JNIEnv* _env, jobject _this, jlong fileSize, jlong  fileOffset)
{
    MediaInfo_Internal* mi = GetMiObj(_env, _this);
    if (mi == NULL)
        return (jint)-1;

    return mi->Open_Buffer_Init((int64u)fileSize, (int64u)fileOffset);
}

//------------------------------------------------------------------------------
static jint JNI_Open_Buffer_Continue(JNIEnv* _env, jobject _this, jbyteArray buffer, jlong  bufferSize)
{
    MediaInfo_Internal* mi = GetMiObj(_env, _this);
    if (mi == NULL)
        return (jint)-1;

    int8u*  buff = (int8u*)_env->GetByteArrayElements(buffer, JNI_FALSE);
    jint toReturn = (jint)mi->Open_Buffer_Continue(buff, (size_t)bufferSize).to_ulong();

    _env->ReleaseByteArrayElements(buffer, (jbyte*)buff, 0);

    return toReturn;
}

//------------------------------------------------------------------------------
static jlong JNI_Open_Buffer_Continue_GoTo_Get(JNIEnv* _env, jobject _this)
{
    MediaInfo_Internal* mi = GetMiObj(_env, _this);
    if (mi == NULL)
        return (jint)-1;

    return (jlong)mi->Open_Buffer_Continue_GoTo_Get();
}

//------------------------------------------------------------------------------
static jlong JNI_Open_Buffer_Finalize(JNIEnv* _env, jobject _this)
{
    MediaInfo_Internal* mi = GetMiObj(_env, _this);
    if (mi == NULL)
        return (jlong)-1;

    return (jlong)mi->Open_Buffer_Finalize();
}


//------------------------------------------------------------------------------
static jint JNI_Close(JNIEnv* _env, jobject _this)
{
    MediaInfo_Internal* mi = GetMiObj(_env, _this);
    if (mi == NULL)
        return 1;

    mi->Close();

    return 0;
}

//------------------------------------------------------------------------------
static jstring JNI_Inform(JNIEnv* _env, jobject _this)
{
    MediaInfo_Internal* mi = GetMiObj(_env, _this);
    if (mi == NULL)
        return _env->NewStringUTF("");

    return _env->NewStringUTF(Ztring(MediaInfo_Internal::Inform(mi)).To_UTF8().c_str());
}

//------------------------------------------------------------------------------
static jstring JNI_GetI(JNIEnv* _env, jobject _this, jint streamKind, jint streamNumber, jint parameter, jint infoKind)
{
    MediaInfo_Internal* mi = GetMiObj(_env, _this);
    if (mi == NULL)
        return _env->NewStringUTF("");

    return _env->NewStringUTF(Ztring(mi->Get((stream_t) streamKind, (size_t)streamNumber, (size_t)parameter, (info_t)infoKind)).To_UTF8().c_str());
}

//------------------------------------------------------------------------------
static jstring JNI_GetS(JNIEnv* _env, jobject _this, jint streamKind, jint streamNumber, jstring parameter, jint infoKind, jint searchKind)
{
    MediaInfo_Internal* mi = GetMiObj(_env, _this);
    if (mi == NULL)
        return _env->NewStringUTF("");

    return _env->NewStringUTF(Ztring(mi->Get((stream_t) streamKind, (size_t)streamNumber, Jstring2Ztring(_env, parameter), (info_t)infoKind, (info_t)searchKind)).To_UTF8().c_str());
}

//------------------------------------------------------------------------------
static jstring JNI_Option(JNIEnv* _env, jobject _this, jstring option, jstring value)
{
    MediaInfo_Internal* mi = GetMiObj(_env, _this);
    if (mi == NULL)
        return _env->NewStringUTF("");

    return _env->NewStringUTF(Ztring(mi->Option(Jstring2Ztring(_env, option), Jstring2Ztring(_env, value))).To_UTF8().c_str());
}

//------------------------------------------------------------------------------
static jint JNI_State_Get(JNIEnv* _env, jobject _this)
{
    MediaInfo_Internal* mi = GetMiObj(_env, _this);
    if (mi == NULL)
        return 0;

    return (jint)mi->State_Get();
}

//------------------------------------------------------------------------------
static jint JNI_Count_Get(JNIEnv* _env, jobject _this, jint streamKind, jint streamNumber)
{
    MediaInfo_Internal* mi = GetMiObj(_env, _this);
    if (mi == NULL)
        return 0;

    return (jint)mi->Count_Get((stream_t)streamKind, streamNumber==-1?(size_t)-1:(size_t)streamNumber);
}

/**
 * Initialize
 */

//------------------------------------------------------------------------------
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*)
{
    static JNINativeMethod methods[] = {
        {"Init", "()J", (void*)&JNI_Init},
        {"Destroy", "()I", (void*)&JNI_Destroy},
        {"Open", "(Ljava/lang/String;)I", (void*)&JNI_Open},
#ifdef __ANDROID__
        {"OpenFd", "(ILjava/lang/String;)I", (void*)&JNI_OpenFd},
#endif
        {"Open_Buffer_Init", "(JJ)I", (void*)&JNI_Open_Buffer_Init},
        {"Open_Buffer_Continue", "([BJ)I", (void*)&JNI_Open_Buffer_Continue},
        {"Open_Buffer_Continue_GoTo_Get", "()J", (void*)&JNI_Open_Buffer_Continue_GoTo_Get},
        {"Open_Buffer_Finalize", "()J", (void*)&JNI_Open_Buffer_Finalize},
        {"Close", "()I", (void*)&JNI_Close},
        {"Inform", "()Ljava/lang/String;", (void*)&JNI_Inform},
        {"GetI", "(IIII)Ljava/lang/String;", (void*)&JNI_GetI},
        {"GetS", "(IILjava/lang/String;II)Ljava/lang/String;", (void*)&JNI_GetS},
        {"Option", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", (void*)&JNI_Option},
        {"State_Get", "()I", (void*)&JNI_State_Get},
        {"Count_Get", "(II)I", (void*)&JNI_Count_Get}
    };

    JNIEnv* env = NULL;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
        return JNI_ERR;

    jclass cls = NULL;
#ifdef __ANDROID__
    cls = env->FindClass("net/mediaarea/mediainfo/MediaInfo");
#else
    cls = env->FindClass("MediaInfo");
#endif

    if (cls == NULL)
        return JNI_ERR;

    if ((env->RegisterNatives(cls, methods, sizeof(methods) / sizeof(methods[0]))) < 0)
        return JNI_ERR;

    return JNI_VERSION_1_4;
}

#endif //MEDIAINFO_JNI_YES
