#include <jni.h>
#include <android/log.h>

#include <string.h>
#include <unistd.h>
#include <pthread.h>

#include "android/com_funambol_omadmclient_android_service_NotificationCallbackInterface.h"
#include "Logger/androidlog.h"
//--------------------------------------------------------------------------------------------------

static JavaVM *gJavaVM;

static jobject gInterfaceObject;
const char* interfacePath = "com/funambol/omadmclient/android/service/NotificationCallbackInterface";

static const char* LOG_TAG = "NotificationCallbackInterface";
//--------------------------------------------------------------------------------------------------
// IMessageHandler implementation for Android
//--------------------------------------------------------------------------------------------------

static NS_DM_Client::StatusCode confirmation_callback_handler(char* message);
static NS_DM_Client::StatusCode display_callback_handler(char* message);
static NS_DM_Client::StatusCode userchoice_callback_handler(char* message, char* avialable_choise_set, char*& choise_set, bool allowMultipleChoises);
static NS_DM_Client::StatusCode userinput_callback_handler(char* message, char*& input, long max_len, bool echo_password);
NS_DM_Client::StatusCode jintToStatusCode(int jres);

namespace NS_DM_Client
{

bool AndroidMessageHandler::Init()
{
LOGI("enter >> AndroidMessageHandler.Init");
    return true;
}

bool AndroidMessageHandler::Release()
{
LOGI("enter >> AndroidMessageHandler.Release");
    return true;
}

StatusCode AndroidMessageHandler::Display(const char* message, long min_time, long max_time)
{
    LOGI("enter >> AndroidMessageHandler.Display");

    StatusCode res = e_CommandFailed;
    if (message)
    {
        res = display_callback_handler((char*)message);
    }
    else
    {
        LOGE("error: message pointer is NULL");
    }

    LOGI("leave << AndroidMessageHandler.Display. Result: %d", res);

    return res;
}

StatusCode AndroidMessageHandler::Confirmation(const char* message, long min_time, long max_time)
{
    LOGI("enter >> AndroidMessageHandler.Confirmation");

    StatusCode res = e_CommandFailed;
    if (message)
    {
        res = confirmation_callback_handler((char*)message);
    }
    else
    {
        LOGE("error: message pointer is NULL");
        res = e_IncompleteCommand;
    }

    LOGI("leave << AndroidMessageHandler.Confirmation. Result: %d", res);

    return res;
}

StatusCode AndroidMessageHandler::UserInput(const char* message, long min_time, long max_time, char*& input, long max_len, bool echo_password)
{
    LOGI("enter >> AndroidMessageHandler.UserInput");

    StatusCode res = e_CommandFailed;
    if (message)
    {
        res = userinput_callback_handler((char*)message, input, max_len, echo_password);
    }
    else
    {
        LOGE("error: message pointer is NULL");
        res = e_IncompleteCommand;
    }

    LOGI("leave << AndroidMessageHandler.UserInput. Result: %d", res);

    return res;
}

StatusCode AndroidMessageHandler::UserChoice(const char* message, long min_time, long max_time, const char* avialable_choise_set, char*& choise_set, bool allowMultipleChoises)
{
    LOGI("enter >> AndroidMessageHandler.UserChoice");

    StatusCode res = e_CommandFailed;
    if (message)
    {
        res = userchoice_callback_handler((char*)message ,(char*)avialable_choise_set, choise_set, allowMultipleChoises);
    }
    else
    {
        LOGE("error: message pointer is NULL");
        res = e_IncompleteCommand;
    }

    LOGI("leave << AndroidMessageHandler.UserChoice. Result: %d", res);

    return res;
}

StatusCode AndroidMessageHandler::ProgressNotification(const char* message, long min_time, long max_time, long size_to_download)
{
    return e_Failed;
}

void AndroidMessageHandler::ReleaseString(char* str)
{
    // NULL  
}

}

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

static NS_DM_Client::StatusCode confirmation_callback_handler(char* message) 
{
    LOGI("enter >> confirmation_callback_handler");

    NS_DM_Client::StatusCode res = NS_DM_Client::e_CommandFailed;

    int status = -1;
    JNIEnv *env = 0;
    bool isAttached = false;

    status = gJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
    if(status < 0)
    {
        LOGE("failed to get JNI environment, assuming native thread");
        status = gJavaVM->AttachCurrentThread(&env, NULL);
        if(status < 0)
        {
            LOGE("failed to attach current thread");
            return res;
        }
        isAttached = true;
        LOGI("success to attach current thread");
    }

    jclass interfaceClass = env->GetObjectClass(gInterfaceObject);
    if(!interfaceClass)
    {
        LOGE("failed to get class reference");
        if(isAttached) 
        {
            gJavaVM->DetachCurrentThread();
        }
        return res;
    }

    jmethodID method = env->GetStaticMethodID(interfaceClass, "ConfirmationCallBack", "(Ljava/lang/String;)I");
    if(!method)
    {
        LOGE("failed to get method ID");
        if(isAttached)
        {
            gJavaVM->DetachCurrentThread();
        }
        return res;
    }

    jstring js = env->NewStringUTF(message);

    int jres = env->CallStaticIntMethod(interfaceClass, method, js);
    LOGI("ConfirmationCallBack.Res: %d", jres);
    res = jintToStatusCode(jres);

    if(isAttached) 
    {
        gJavaVM->DetachCurrentThread();
    }

    LOGI("leave << confirmation_callback_handler.Res: %d", res);
    return res;
}

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

static NS_DM_Client::StatusCode display_callback_handler(char* message) 
{
    LOGI("enter >> display_callback_handler");
    NS_DM_Client::StatusCode res = NS_DM_Client::e_CommandFailed;

    int status = -1;
    JNIEnv *env = 0;
    bool isAttached = false;

    /* Get JNIEnv variable of JavaVM */
    status = gJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
    if(status < 0)
    {
        LOGE("failed to get JNI environment, assuming native thread");
        status = gJavaVM->AttachCurrentThread(&env, NULL);
        if(status < 0)
        {
            LOGE("failed to attach current thread");
            return res;
        }
        isAttached = true;
        LOGI("success to attach current thread");
    }

    /* Get callBack Class */
    jclass interfaceClass = env->GetObjectClass(gInterfaceObject);
    if(!interfaceClass)
    {
        LOGE("failed to get class reference");
        if(isAttached)
        {
            gJavaVM->DetachCurrentThread();
        }
        return res;
    }

    /* Find the callBack method ID */
    jmethodID method = env->GetStaticMethodID(interfaceClass, "DisplayCallBack", "(Ljava/lang/String;)I");
    if(!method)
    {
        LOGE("failed to get method ID");
        if(isAttached)
        {
            gJavaVM->DetachCurrentThread();
        }
        return res;
    }

    /* Construct a Java string */
    jstring js = env->NewStringUTF(message);

    /* Call callBack */
    int jres = env->CallStaticIntMethod(interfaceClass, method, js);
    LOGI("DisplayCallBack.Res: %d", jres);

    /* Convert return value to StatuCode */
    res = jintToStatusCode(jres);

    /* Release Resource */
    if(isAttached)
    {
            gJavaVM->DetachCurrentThread();
    }

    LOGI("leave << display_callback_handler.Res: %d", res);
    return res;
}

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

static NS_DM_Client::StatusCode userinput_callback_handler(char* message, char*& input, long max_len, bool echo_password)
{
    LOGI("enter >> userinput_callback_handler");
    NS_DM_Client::StatusCode res = NS_DM_Client::e_CommandFailed;

    int status = -1;
    JNIEnv *env = 0;
    bool isAttached = false;

    status = gJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
    if(status < 0)
    {
        LOGE("failed to get JNI environment, assuming native thread");
        status = gJavaVM->AttachCurrentThread(&env, NULL);
        if(status < 0)
        {
            LOGE("failed to attach current thread");
            return res;
        }
        isAttached = true;
        LOGI("success to attach current thread");
    }

    jclass interfaceClass = env->GetObjectClass(gInterfaceObject);
    if(!interfaceClass)
    {
        LOGE("failed to get Interface class reference");
        if(isAttached)
        {
            gJavaVM->DetachCurrentThread();
        }
        return res;
    }

    //  public static int UserInputCallBack(java.lang.String, java.lang.String[], long, boolean);
    // Signature: (Ljava/lang/String;[Ljava/lang/String;JZ)I
    /* Find the callBack method ID */
    LOGI("GetStaticMethodID: UserInputCallBack");
    jmethodID method = env->GetStaticMethodID(interfaceClass, "UserInputCallBack", "(Ljava/lang/String;[Ljava/lang/String;JZ)I");
    if(!method)
    {
        LOGE("failed to get method ID");
        if(isAttached)
        {
            gJavaVM->DetachCurrentThread();
        }
        return res;
    }

    LOGI("FindClass: java/lang/String");
    jclass jclazz = env->FindClass("java/lang/String");
    if(jclazz == NULL)
    {
        LOGE("failed to get String class reference");
        if(isAttached)
        {
            gJavaVM->DetachCurrentThread();
        }
        return res;
    }

    LOGI("NewObjectArray: jclazz");
    jobjectArray joarray = env->NewObjectArray(1, jclazz, env->NewStringUTF(""));
    if(joarray == NULL)
    {
        LOGE("failed to get String array");
        if(isAttached)
        {
            gJavaVM->DetachCurrentThread();
        }
        return res;
    }

    jlong jl = max_len;
    jboolean jb = 0;
    jstring js = env->NewStringUTF(message);

    LOGI("call: UserInputCallBack");
    int jres = env->CallStaticIntMethod(interfaceClass, method, js, joarray, jl, jb);
    LOGI("UserInputCallBack.Res: %d", jres);

    if(jres == NS_DM_Client::e_Ok)
    {
        jobject jo = env->GetObjectArrayElement(joarray, 0);
        const char* cstr = env->GetStringUTFChars((jstring)jo, 0);
        input = strdup(cstr);
        env->ReleaseStringUTFChars((jstring)jo, cstr);
    }

    res = jintToStatusCode(jres);

    if(isAttached)
    {
            gJavaVM->DetachCurrentThread();
    }

    LOGI("leave << userinput_callback_handler.Res: %d", res);
    return res;
}

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

static NS_DM_Client::StatusCode userchoice_callback_handler(char* message ,char* avialable_choise_set, char*& choise_set, bool allowMultipleChoises) 
{
    LOGI("enter >> confirmation_callback_handler");

    NS_DM_Client::StatusCode res = NS_DM_Client::e_CommandFailed;
    int status = -1;
    JNIEnv *env = 0;
    bool isAttached = false;

    status = gJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
    if(status < 0)
    {
        LOGE("failed to get JNI environment, assuming native thread");
        status = gJavaVM->AttachCurrentThread(&env, NULL);
        if(status < 0)
        {
            LOGE("failed to attach current thread");
            return res;
        }
        isAttached = true;
        LOGI("success to attach current thread");
    }

    jclass interfaceClass = env->GetObjectClass(gInterfaceObject);
    if(!interfaceClass)
    {
        LOGE("failed to get Interface class reference");
        if(isAttached)
        {
            gJavaVM->DetachCurrentThread();
        }
        return res;
    }

// public static int UserChoiceCallBack(java.lang.String, java.lang.String, java.lang.String[], boolean);
//  Signature: (Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Z)I
//             (Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Z)I
    LOGI("GetStaticMethodID: UserChoiceCallBack");
    jmethodID method = env->GetStaticMethodID(interfaceClass, "UserChoiceCallBack", "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Z)I");
    if(!method)
    {
        LOGE("failed to get method ID");
        if(isAttached)
        {
            gJavaVM->DetachCurrentThread();
        }
        return res;
    }

    LOGI("FindClass: java/lang/String");
    jclass jclazz = env->FindClass("java/lang/String");
    if(jclazz == NULL)
    {
        LOGE("failed to get String class reference");
        if(isAttached)
        {
            gJavaVM->DetachCurrentThread();
        }
        return res;
    }

    LOGI("NewObjectArray: jclazz");
    jobjectArray joarray = env->NewObjectArray(1, jclazz, env->NewStringUTF(""));
    if(joarray == NULL)
    {
        LOGE("failed to get String array");
        if(isAttached)
        {
            gJavaVM->DetachCurrentThread();
        }
        return res;
    }

    jboolean jb = 0;
    jb = allowMultipleChoises ? 1: 0;
    jstring js = env->NewStringUTF(message);
    jstring js2 = env->NewStringUTF(avialable_choise_set);

    LOGI("call: UserChoiceCallBack");
    int jres = env->CallStaticIntMethod(interfaceClass, method, js, js2, joarray, jb);
    LOGI("UserChoiceCallBack.Res: %d", jres);

    if(jres == NS_DM_Client::e_Ok)
    {
        jobject jo = env->GetObjectArrayElement(joarray, 0);
        const char* cstr = env->GetStringUTFChars((jstring)jo, 0);
        choise_set = strdup(cstr);
        env->ReleaseStringUTFChars((jstring)jo, cstr);
    }

    res = jintToStatusCode(jres);

    if(isAttached)
    {
            gJavaVM->DetachCurrentThread();
    }

    LOGI("enter >> confirmation_callback_handler");

    return res;
}

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

bool initClassHelper(JNIEnv *env, const char *path, jobject *objptr) 
{
    LOGI("enter >> initClassHelper");

    jclass cls = env->FindClass(path);
    if(!cls) 
    {
        LOGE("initClassHelper: failed to get %s class reference", path);
         return false;
    }

    jmethodID constr = env->GetMethodID(cls, "<init>", "()V");
    if(!constr) 
    {
        LOGE("initClassHelper: failed to get %s constructor", path);
         return false;
    }

   jobject obj = env->NewObject(cls, constr);
   if(!obj) 
   {
        LOGE("initClassHelper: failed to create a %s object", path);
         return false;
   }
   (*objptr) = env->NewGlobalRef(obj);

    LOGI("leave << initClassHelper");
    return true;
}

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

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    LOGI("enter >> JNI_OnLoad");
    JNIEnv *env = 0;
    gJavaVM = vm;

    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
    {
        LOGE("Failed to get the environment using GetEnv()");
        return -1;
    }

    bool init_res = initClassHelper(env, interfacePath, &gInterfaceObject);
    if (!init_res)
    {
        LOGE("Failed to init Interface classes");
         return -1;
    }

    LOGI("leave << JNI_OnLoad");
    return JNI_VERSION_1_6;
}
//--------------------------------------------------------------------------------------------------

NS_DM_Client::StatusCode jintToStatusCode(int jres)
{
    NS_DM_Client::StatusCode res = NS_DM_Client::e_CommandFailed;

    switch(jres)
    {
        case NS_DM_Client::e_Ok:
            res = NS_DM_Client::e_Ok;
            break;
        case NS_DM_Client::e_AcceptForProcessing:
            res = NS_DM_Client::e_AcceptForProcessing;
            break;
        case NS_DM_Client::e_ChunkedItemAcceptedAndBuffered:
            res = NS_DM_Client::e_ChunkedItemAcceptedAndBuffered;
            break;
        case NS_DM_Client::e_OperationCancelled:
            res = NS_DM_Client::e_OperationCancelled;
            break;
        case NS_DM_Client::e_NotExecuted:
            res = NS_DM_Client::e_NotExecuted;
            break;
        case NS_DM_Client::e_AtomicRollbackOK:
            res = NS_DM_Client::e_AtomicRollbackOK;
            break;
        case NS_DM_Client::e_NotModified:
            res = NS_DM_Client::e_NotModified;
            break;
        case NS_DM_Client::e_Unauthorized:
            res = NS_DM_Client::e_Unauthorized;
            break;
        case NS_DM_Client::e_NotFound:
            res = NS_DM_Client::e_NotFound;
            break;
        case NS_DM_Client::e_ForbiddenCommand:
            res = NS_DM_Client::e_ForbiddenCommand;
            break;
        case NS_DM_Client::e_OptionalFeatureNotSupported:
            res = NS_DM_Client::e_OptionalFeatureNotSupported;
            break;
        case NS_DM_Client::e_AuthenticationRequired:
            res = NS_DM_Client::e_AuthenticationRequired;
            break;
        case NS_DM_Client::e_IncompleteCommand:
            res = NS_DM_Client::e_IncompleteCommand;
            break;
        case NS_DM_Client::e_EntityTooLarge:
            res = NS_DM_Client::e_EntityTooLarge;
            break;
        case NS_DM_Client::e_UnsupportedMediaTypeOrFormat:
            res = NS_DM_Client::e_UnsupportedMediaTypeOrFormat;
            break;
        case NS_DM_Client::e_AlreadyExists:
            res = NS_DM_Client::e_AlreadyExists;
            break;
        case NS_DM_Client::e_DeviceFull:
            res = NS_DM_Client::e_DeviceFull;
            break;
        case NS_DM_Client::e_SizeMismatch:
            res = NS_DM_Client::e_SizeMismatch;
            break;
        case NS_DM_Client::e_PermissionDenied:
            res = NS_DM_Client::e_PermissionDenied;
            break;
        case NS_DM_Client::e_CommandFailed:
            res = NS_DM_Client::e_CommandFailed;
            break;
        case NS_DM_Client::e_Failed:
            res = NS_DM_Client::e_Failed;
            break;
        case NS_DM_Client::e_AtomicFailed:
            res = NS_DM_Client::e_AtomicFailed;
            break;
        case NS_DM_Client::e_AtomicRollBackFailed:
            res = NS_DM_Client::e_AtomicRollBackFailed;
            break;
        case NS_DM_Client::e_WriteInstanceNotAlive:
            res = NS_DM_Client::e_WriteInstanceNotAlive;
            break;
        case NS_DM_Client::e_ReadInstanceNotAlive:
            res = NS_DM_Client::e_ReadInstanceNotAlive;
            break;
        case NS_DM_Client::e_Stop:
            res = NS_DM_Client::e_Stop;
        default:
            break;
    }

    return res;
}
//--------------------------------------------------------------------------------------------------
