/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//#define LOG_NDEBUG 0
#define LOG_TAG "JET_JNI"


#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

#include <jni.h>
#include <JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>

#include <utils/Log.h>
#include <media/JetPlayer.h>


using namespace android;

// ----------------------------------------------------------------------------
static const char* const kClassPathName = "android/media/JetPlayer";

// ----------------------------------------------------------------------------
struct fields_t {
    // these fields provide access from C++ to the...
    jclass    jetClass;              // JetPlayer java class global ref
    jmethodID postNativeEventInJava; // java method to post events to the Java thread from native
    jfieldID  nativePlayerInJavaObj; // stores in Java the native JetPlayer object
};

static fields_t javaJetPlayerFields;


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

/*
 * This function is called from JetPlayer instance's render thread
 */
static void
jetPlayerEventCallback(int what, int arg1=0, int arg2=0, void* javaTarget = NULL)
{
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    if (env) {
        env->CallStaticVoidMethod(
            javaJetPlayerFields.jetClass, javaJetPlayerFields.postNativeEventInJava,
            javaTarget,
            what, arg1, arg2);
        if (env->ExceptionCheck()) {
            env->ExceptionDescribe();
            env->ExceptionClear();
        }
    } else {
        ALOGE("JET jetPlayerEventCallback(): No JNI env for JET event callback, can't post event.");
        return;
    }
}


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

static jboolean
android_media_JetPlayer_setup(JNIEnv *env, jobject thiz, jobject weak_this,
    jint maxTracks, jint trackBufferSize)
{
    //ALOGV("android_media_JetPlayer_setup(): entering.");
    JetPlayer* lpJet = new JetPlayer(env->NewGlobalRef(weak_this), maxTracks, trackBufferSize);

    EAS_RESULT result = lpJet->init();

    if (result==EAS_SUCCESS) {
        // save our newly created C++ JetPlayer in the "nativePlayerInJavaObj" field
        // of the Java object (in mNativePlayerInJavaObj)
        env->SetIntField(thiz, javaJetPlayerFields.nativePlayerInJavaObj, (int)lpJet);
        return JNI_TRUE;
    } else {
        ALOGE("android_media_JetPlayer_setup(): initialization failed with EAS error code %d", (int)result);
        delete lpJet;
        env->SetIntField(weak_this, javaJetPlayerFields.nativePlayerInJavaObj, 0);
        return JNI_FALSE;
    }
}


// ----------------------------------------------------------------------------
static void
android_media_JetPlayer_finalize(JNIEnv *env, jobject thiz)
{
    ALOGV("android_media_JetPlayer_finalize(): entering.");
    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    if (lpJet != NULL) {
        lpJet->release();
        delete lpJet;
    }

    ALOGV("android_media_JetPlayer_finalize(): exiting.");
}


// ----------------------------------------------------------------------------
static void
android_media_JetPlayer_release(JNIEnv *env, jobject thiz)
{
    android_media_JetPlayer_finalize(env, thiz);
    env->SetIntField(thiz, javaJetPlayerFields.nativePlayerInJavaObj, 0);
    ALOGV("android_media_JetPlayer_release() done");
}


// ----------------------------------------------------------------------------
static jboolean
android_media_JetPlayer_loadFromFile(JNIEnv *env, jobject thiz, jstring path)
{
    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    if (lpJet == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve JetPlayer pointer for openFile()");
        return JNI_FALSE;
    }

    // set up event callback function
    lpJet->setEventCallback(jetPlayerEventCallback);

    const char *pathStr = env->GetStringUTFChars(path, NULL);
    if (pathStr == NULL) {  // Out of memory
        ALOGE("android_media_JetPlayer_openFile(): aborting, out of memory");
        return JNI_FALSE;
    }

    ALOGV("android_media_JetPlayer_openFile(): trying to open %s", pathStr );
    EAS_RESULT result = lpJet->loadFromFile(pathStr);
    env->ReleaseStringUTFChars(path, pathStr);

    if (result==EAS_SUCCESS) {
        //ALOGV("android_media_JetPlayer_openFile(): file successfully opened");
        return JNI_TRUE;
    } else {
        ALOGE("android_media_JetPlayer_openFile(): failed to open file with EAS error %d",
            (int)result);
        return JNI_FALSE;
    }
}


// ----------------------------------------------------------------------------
static jboolean
android_media_JetPlayer_loadFromFileD(JNIEnv *env, jobject thiz,
    jobject fileDescriptor, jlong offset, jlong length)
{
    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    if (lpJet == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve JetPlayer pointer for openFile()");
        return JNI_FALSE;
    }

    // set up event callback function
    lpJet->setEventCallback(jetPlayerEventCallback);

    ALOGV("android_media_JetPlayer_openFileDescr(): trying to load JET file through its fd" );
    EAS_RESULT result = lpJet->loadFromFD(jniGetFDFromFileDescriptor(env, fileDescriptor),
        (long long)offset, (long long)length); // cast params to types used by EAS_FILE

    if (result==EAS_SUCCESS) {
        ALOGV("android_media_JetPlayer_openFileDescr(): file successfully opened");
        return JNI_TRUE;
    } else {
        ALOGE("android_media_JetPlayer_openFileDescr(): failed to open file with EAS error %d",
            (int)result);
        return JNI_FALSE;
    }
}


// ----------------------------------------------------------------------------
static jboolean
android_media_JetPlayer_closeFile(JNIEnv *env, jobject thiz)
{
    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    if (lpJet == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve JetPlayer pointer for closeFile()");
        return JNI_FALSE;
    }

    if (lpJet->closeFile()==EAS_SUCCESS) {
        //ALOGV("android_media_JetPlayer_closeFile(): file successfully closed");
        return JNI_TRUE;
    } else {
        ALOGE("android_media_JetPlayer_closeFile(): failed to close file");
        return JNI_FALSE;
    }
}


// ----------------------------------------------------------------------------
static jboolean
android_media_JetPlayer_play(JNIEnv *env, jobject thiz)
{
    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    if (lpJet == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve JetPlayer pointer for play()");
        return JNI_FALSE;
    }

    EAS_RESULT result = lpJet->play();
    if (result==EAS_SUCCESS) {
        //ALOGV("android_media_JetPlayer_play(): play successful");
        return JNI_TRUE;
    } else {
        ALOGE("android_media_JetPlayer_play(): failed to play with EAS error code %ld",
            result);
        return JNI_FALSE;
    }
}


// ----------------------------------------------------------------------------
static jboolean
android_media_JetPlayer_pause(JNIEnv *env, jobject thiz)
{
    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    if (lpJet == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve JetPlayer pointer for pause()");
        return JNI_FALSE;
    }

    EAS_RESULT result = lpJet->pause();
    if (result==EAS_SUCCESS) {
        //ALOGV("android_media_JetPlayer_pause(): pause successful");
        return JNI_TRUE;
    } else {
        if (result==EAS_ERROR_QUEUE_IS_EMPTY) {
            ALOGV("android_media_JetPlayer_pause(): paused with an empty queue");
            return JNI_TRUE;
        } else
            ALOGE("android_media_JetPlayer_pause(): failed to pause with EAS error code %ld",
                result);
        return JNI_FALSE;
    }
}


// ----------------------------------------------------------------------------
static jboolean
android_media_JetPlayer_queueSegment(JNIEnv *env, jobject thiz,
        jint segmentNum, jint libNum, jint repeatCount, jint transpose, jint muteFlags,
        jbyte userID)
{
    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    if (lpJet == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve JetPlayer pointer for queueSegment()");
        return JNI_FALSE;
    }

    EAS_RESULT result
        = lpJet->queueSegment(segmentNum, libNum, repeatCount, transpose, muteFlags, userID);
    if (result==EAS_SUCCESS) {
        //ALOGV("android_media_JetPlayer_queueSegment(): segment successfully queued");
        return JNI_TRUE;
    } else {
        ALOGE("android_media_JetPlayer_queueSegment(): failed with EAS error code %ld",
            result);
        return JNI_FALSE;
    }
}


// ----------------------------------------------------------------------------
static jboolean
android_media_JetPlayer_queueSegmentMuteArray(JNIEnv *env, jobject thiz,
        jint segmentNum, jint libNum, jint repeatCount, jint transpose, jbooleanArray muteArray,
        jbyte userID)
{
    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    if (lpJet == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve JetPlayer pointer for queueSegmentMuteArray()");
        return JNI_FALSE;
    }

    EAS_RESULT result=EAS_FAILURE;

    jboolean *muteTracks = NULL;
    muteTracks = env->GetBooleanArrayElements(muteArray, NULL);
    if (muteTracks == NULL) {
        ALOGE("android_media_JetPlayer_queueSegment(): failed to read track mute mask.");
        return JNI_FALSE;
    }

    EAS_U32 muteMask=0;
    int maxTracks = lpJet->getMaxTracks();
    for (jint trackIndex=0; trackIndex<maxTracks; trackIndex++) {
        if (muteTracks[maxTracks-1-trackIndex]==JNI_TRUE)
            muteMask = (muteMask << 1) | 0x00000001;
        else
            muteMask = muteMask << 1;
    }
    //ALOGV("android_media_JetPlayer_queueSegmentMuteArray(): FINAL mute mask =0x%08lX", mask);

    result = lpJet->queueSegment(segmentNum, libNum, repeatCount, transpose, muteMask, userID);

    env->ReleaseBooleanArrayElements(muteArray, muteTracks, 0);
    if (result==EAS_SUCCESS) {
        //ALOGV("android_media_JetPlayer_queueSegmentMuteArray(): segment successfully queued");
        return JNI_TRUE;
    } else {
        ALOGE("android_media_JetPlayer_queueSegmentMuteArray(): failed with EAS error code %ld",
            result);
        return JNI_FALSE;
    }
}


// ----------------------------------------------------------------------------
static jboolean
android_media_JetPlayer_setMuteFlags(JNIEnv *env, jobject thiz,
         jint muteFlags /*unsigned?*/, jboolean bSync)
{
    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    if (lpJet == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve JetPlayer pointer for setMuteFlags()");
        return JNI_FALSE;
    }

    EAS_RESULT result;
    result = lpJet->setMuteFlags(muteFlags, bSync==JNI_TRUE ? true : false);
    if (result==EAS_SUCCESS) {
        //ALOGV("android_media_JetPlayer_setMuteFlags(): mute flags successfully updated");
        return JNI_TRUE;
    } else {
        ALOGE("android_media_JetPlayer_setMuteFlags(): failed with EAS error code %ld", result);
        return JNI_FALSE;
    }
}


// ----------------------------------------------------------------------------
static jboolean
android_media_JetPlayer_setMuteArray(JNIEnv *env, jobject thiz,
        jbooleanArray muteArray, jboolean bSync)
{
    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    if (lpJet == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve JetPlayer pointer for setMuteArray()");
        return JNI_FALSE;
    }

    EAS_RESULT result=EAS_FAILURE;

    jboolean *muteTracks = NULL;
    muteTracks = env->GetBooleanArrayElements(muteArray, NULL);
    if (muteTracks == NULL) {
        ALOGE("android_media_JetPlayer_setMuteArray(): failed to read track mute mask.");
        return JNI_FALSE;
    }

    EAS_U32 muteMask=0;
    int maxTracks = lpJet->getMaxTracks();
    for (jint trackIndex=0; trackIndex<maxTracks; trackIndex++) {
        if (muteTracks[maxTracks-1-trackIndex]==JNI_TRUE)
            muteMask = (muteMask << 1) | 0x00000001;
        else
            muteMask = muteMask << 1;
    }
    //ALOGV("android_media_JetPlayer_setMuteArray(): FINAL mute mask =0x%08lX", muteMask);

    result = lpJet->setMuteFlags(muteMask, bSync==JNI_TRUE ? true : false);

    env->ReleaseBooleanArrayElements(muteArray, muteTracks, 0);
    if (result==EAS_SUCCESS) {
        //ALOGV("android_media_JetPlayer_setMuteArray(): mute flags successfully updated");
        return JNI_TRUE;
    } else {
        ALOGE("android_media_JetPlayer_setMuteArray(): \
            failed to update mute flags with EAS error code %ld", result);
        return JNI_FALSE;
    }
}


// ----------------------------------------------------------------------------
static jboolean
android_media_JetPlayer_setMuteFlag(JNIEnv *env, jobject thiz,
         jint trackId, jboolean muteFlag, jboolean bSync)
{
    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    if (lpJet == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve JetPlayer pointer for setMuteFlag()");
        return JNI_FALSE;
    }

    EAS_RESULT result;
    result = lpJet->setMuteFlag(trackId,
        muteFlag==JNI_TRUE ? true : false, bSync==JNI_TRUE ? true : false);
    if (result==EAS_SUCCESS) {
        //ALOGV("android_media_JetPlayer_setMuteFlag(): mute flag successfully updated for track %d", trackId);
        return JNI_TRUE;
    } else {
        ALOGE("android_media_JetPlayer_setMuteFlag(): failed to update mute flag for track %d with EAS error code %ld",
                trackId, result);
        return JNI_FALSE;
    }
}


// ----------------------------------------------------------------------------
static jboolean
android_media_JetPlayer_triggerClip(JNIEnv *env, jobject thiz, jint clipId)
{
    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    if (lpJet == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve JetPlayer pointer for triggerClip()");
        return JNI_FALSE;
    }

    EAS_RESULT result;
    result = lpJet->triggerClip(clipId);
    if (result==EAS_SUCCESS) {
        //ALOGV("android_media_JetPlayer_triggerClip(): triggerClip successful for clip %d", clipId);
        return JNI_TRUE;
    } else {
        ALOGE("android_media_JetPlayer_triggerClip(): triggerClip for clip %d failed with EAS error code %ld",
                clipId, result);
        return JNI_FALSE;
    }
}


// ----------------------------------------------------------------------------
static jboolean
android_media_JetPlayer_clearQueue(JNIEnv *env, jobject thiz)
{
    JetPlayer *lpJet = (JetPlayer *)env->GetIntField(
        thiz, javaJetPlayerFields.nativePlayerInJavaObj);
    if (lpJet == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve JetPlayer pointer for clearQueue()");
        return JNI_FALSE;
    }

    EAS_RESULT result = lpJet->clearQueue();
    if (result==EAS_SUCCESS) {
        //ALOGV("android_media_JetPlayer_clearQueue(): clearQueue successful");
        return JNI_TRUE;
    } else {
        ALOGE("android_media_JetPlayer_clearQueue(): clearQueue failed with EAS error code %ld",
                result);
        return JNI_FALSE;
    }
}


// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
static JNINativeMethod gMethods[] = {
    // name,               signature,               funcPtr
    {"native_setup",       "(Ljava/lang/Object;II)Z", (void *)android_media_JetPlayer_setup},
    {"native_finalize",    "()V",                   (void *)android_media_JetPlayer_finalize},
    {"native_release",     "()V",                   (void *)android_media_JetPlayer_release},
    {"native_loadJetFromFile",
                           "(Ljava/lang/String;)Z", (void *)android_media_JetPlayer_loadFromFile},
    {"native_loadJetFromFileD", "(Ljava/io/FileDescriptor;JJ)Z",
                                                    (void *)android_media_JetPlayer_loadFromFileD},
    {"native_closeJetFile","()Z",                   (void *)android_media_JetPlayer_closeFile},
    {"native_playJet",     "()Z",                   (void *)android_media_JetPlayer_play},
    {"native_pauseJet",    "()Z",                   (void *)android_media_JetPlayer_pause},
    {"native_queueJetSegment",
                           "(IIIIIB)Z",             (void *)android_media_JetPlayer_queueSegment},
    {"native_queueJetSegmentMuteArray",
                           "(IIII[ZB)Z",     (void *)android_media_JetPlayer_queueSegmentMuteArray},
    {"native_setMuteFlags","(IZ)Z",                 (void *)android_media_JetPlayer_setMuteFlags},
    {"native_setMuteArray","([ZZ)Z",                (void *)android_media_JetPlayer_setMuteArray},
    {"native_setMuteFlag", "(IZZ)Z",                (void *)android_media_JetPlayer_setMuteFlag},
    {"native_triggerClip", "(I)Z",                  (void *)android_media_JetPlayer_triggerClip},
    {"native_clearQueue",  "()Z",                   (void *)android_media_JetPlayer_clearQueue},
};

#define JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME "mNativePlayerInJavaObj"
#define JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME "postEventFromNative"


int register_android_media_JetPlayer(JNIEnv *env)
{
    jclass jetPlayerClass = NULL;
    javaJetPlayerFields.jetClass = NULL;
    javaJetPlayerFields.postNativeEventInJava = NULL;
    javaJetPlayerFields.nativePlayerInJavaObj = NULL;

    // Get the JetPlayer java class
    jetPlayerClass = env->FindClass(kClassPathName);
    if (jetPlayerClass == NULL) {
        ALOGE("Can't find %s", kClassPathName);
        return -1;
    }
    javaJetPlayerFields.jetClass = (jclass)env->NewGlobalRef(jetPlayerClass);

    // Get the mNativePlayerInJavaObj variable field
    javaJetPlayerFields.nativePlayerInJavaObj = env->GetFieldID(
            jetPlayerClass,
            JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME, "I");
    if (javaJetPlayerFields.nativePlayerInJavaObj == NULL) {
        ALOGE("Can't find JetPlayer.%s", JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME);
        return -1;
    }

    // Get the callback to post events from this native code to Java
    javaJetPlayerFields.postNativeEventInJava = env->GetStaticMethodID(javaJetPlayerFields.jetClass,
            JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME, "(Ljava/lang/Object;III)V");
    if (javaJetPlayerFields.postNativeEventInJava == NULL) {
        ALOGE("Can't find Jet.%s", JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME);
        return -1;
    }

    return AndroidRuntime::registerNativeMethods(env,
            kClassPathName, gMethods, NELEM(gMethods));
}
