/*
 * Copyright (C) 2007 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_TAG "Input"

#include "jni.h"
#include "JNIHelp.h"
#include <utils/misc.h>
#include <utils/Log.h>

#include <ui/EventHub.h>
#include <utils/threads.h>

#include <stdio.h>

namespace android {

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

static struct input_offsets_t
{
    jfieldID mMinValue;
    jfieldID mMaxValue;
    jfieldID mFlat;
    jfieldID mFuzz;
    
    jfieldID mDeviceId;
    jfieldID mType;
    jfieldID mScancode;
    jfieldID mKeycode;
    jfieldID mFlags;
    jfieldID mValue;
    jfieldID mWhen;
} gInputOffsets;

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

static Mutex gLock;
static sp<EventHub> gHub;

static jboolean
android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz,
                                          jobject event)
{
    gLock.lock();
    sp<EventHub> hub = gHub;
    if (hub == NULL) {
        hub = new EventHub;
        gHub = hub;
    }
    gLock.unlock();

    int32_t deviceId;
    int32_t type;
    int32_t scancode, keycode;
    uint32_t flags;
    int32_t value;
    nsecs_t when;
    bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,
            &flags, &value, &when);

    env->SetIntField(event, gInputOffsets.mDeviceId, (jint)deviceId);
    env->SetIntField(event, gInputOffsets.mType, (jint)type);
    env->SetIntField(event, gInputOffsets.mScancode, (jint)scancode);
    env->SetIntField(event, gInputOffsets.mKeycode, (jint)keycode);
    env->SetIntField(event, gInputOffsets.mFlags, (jint)flags);
    env->SetIntField(event, gInputOffsets.mValue, value);
    env->SetLongField(event, gInputOffsets.mWhen,
                        (jlong)(nanoseconds_to_milliseconds(when)));

    return res;
}

static jint
android_server_KeyInputQueue_getDeviceClasses(JNIEnv* env, jobject clazz,
                                              jint deviceId)
{
    jint classes = 0;
    gLock.lock();
    if (gHub != NULL) classes = gHub->getDeviceClasses(deviceId);
    gLock.unlock();
    return classes;
}

static jstring
android_server_KeyInputQueue_getDeviceName(JNIEnv* env, jobject clazz,
                                              jint deviceId)
{
    String8 name;
    gLock.lock();
    if (gHub != NULL) name = gHub->getDeviceName(deviceId);
    gLock.unlock();
    
    if (name.size() > 0) {
        return env->NewStringUTF(name.string());
    }
    return NULL;
}

static void
android_server_KeyInputQueue_addExcludedDevice(JNIEnv* env, jobject clazz,
                                              jstring deviceName)
{
    gLock.lock();
    sp<EventHub> hub = gHub;
    if (hub == NULL) {
        hub = new EventHub;
        gHub = hub;
    }
    gLock.unlock();

    const char* nameStr = env->GetStringUTFChars(deviceName, NULL);
    gHub->addExcludedDevice(nameStr);
    env->ReleaseStringUTFChars(deviceName, nameStr);
}

static jboolean
android_server_KeyInputQueue_getAbsoluteInfo(JNIEnv* env, jobject clazz,
                                             jint deviceId, jint axis,
                                             jobject info)
{
    int32_t minValue, maxValue, flat, fuzz;
    int res = -1;
    gLock.lock();
    if (gHub != NULL) {
        res = gHub->getAbsoluteInfo(deviceId, axis,
                &minValue, &maxValue, &flat, &fuzz);
    }
    gLock.unlock();
    
    if (res < 0) return JNI_FALSE;
    
    env->SetIntField(info, gInputOffsets.mMinValue, (jint)minValue);
    env->SetIntField(info, gInputOffsets.mMaxValue, (jint)maxValue);
    env->SetIntField(info, gInputOffsets.mFlat, (jint)flat);
    env->SetIntField(info, gInputOffsets.mFuzz, (jint)fuzz);
    return JNI_TRUE;
}

static jint
android_server_KeyInputQueue_getSwitchState(JNIEnv* env, jobject clazz,
                                           jint sw)
{
    jint st = -1;
    gLock.lock();
    if (gHub != NULL) st = gHub->getSwitchState(sw);
    gLock.unlock();
    
    return st;
}

static jint
android_server_KeyInputQueue_getSwitchStateDevice(JNIEnv* env, jobject clazz,
                                            jint deviceId, jint sw)
{
    jint st = -1;
    gLock.lock();
    if (gHub != NULL) st = gHub->getSwitchState(deviceId, sw);
    gLock.unlock();
    
    return st;
}

static jint
android_server_KeyInputQueue_getScancodeState(JNIEnv* env, jobject clazz,
                                           jint sw)
{
    jint st = -1;
    gLock.lock();
    if (gHub != NULL) st = gHub->getScancodeState(sw);
    gLock.unlock();
    
    return st;
}

static jint
android_server_KeyInputQueue_getScancodeStateDevice(JNIEnv* env, jobject clazz,
                                            jint deviceId, jint sw)
{
    jint st = -1;
    gLock.lock();
    if (gHub != NULL) st = gHub->getScancodeState(deviceId, sw);
    gLock.unlock();
    
    return st;
}

static jint
android_server_KeyInputQueue_getKeycodeState(JNIEnv* env, jobject clazz,
                                           jint sw)
{
    jint st = -1;
    gLock.lock();
    if (gHub != NULL) st = gHub->getKeycodeState(sw);
    gLock.unlock();
    
    return st;
}

static jint
android_server_KeyInputQueue_getKeycodeStateDevice(JNIEnv* env, jobject clazz,
                                            jint deviceId, jint sw)
{
    jint st = -1;
    gLock.lock();
    if (gHub != NULL) st = gHub->getKeycodeState(deviceId, sw);
    gLock.unlock();
    
    return st;
}

static jint
android_server_KeyInputQueue_scancodeToKeycode(JNIEnv* env, jobject clazz,
                                            jint deviceId, jint scancode)
{
    jint res = 0;
    gLock.lock();
    if (gHub != NULL) {
        int32_t keycode;
        uint32_t flags;
        gHub->scancodeToKeycode(deviceId, scancode, &keycode, &flags);
        res = keycode;
    }
    gLock.unlock();
    
    return res;
}

static jboolean
android_server_KeyInputQueue_hasKeys(JNIEnv* env, jobject clazz,
                                     jintArray keyCodes, jbooleanArray outFlags)
{
    jboolean ret = JNI_FALSE;

    int32_t* codes = env->GetIntArrayElements(keyCodes, NULL);
    uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL);
    size_t numCodes = env->GetArrayLength(keyCodes);
    if (numCodes == env->GetArrayLength(outFlags)) {
        gLock.lock();
        if (gHub != NULL) ret = gHub->hasKeys(numCodes, codes, flags);
        gLock.unlock();
    }

    env->ReleaseBooleanArrayElements(outFlags, flags, 0);
    env->ReleaseIntArrayElements(keyCodes, codes, 0);
    return ret;
}

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

/*
 * JNI registration.
 */
static JNINativeMethod gInputMethods[] = {
    /* name, signature, funcPtr */
    { "readEvent",       "(Landroid/view/RawInputEvent;)Z",
            (void*) android_server_KeyInputQueue_readEvent },
    { "getDeviceClasses", "(I)I",
        (void*) android_server_KeyInputQueue_getDeviceClasses },
    { "getDeviceName", "(I)Ljava/lang/String;",
        (void*) android_server_KeyInputQueue_getDeviceName },
    { "addExcludedDevice", "(Ljava/lang/String;)V",
        (void*) android_server_KeyInputQueue_addExcludedDevice },
    { "getAbsoluteInfo", "(IILcom/android/server/InputDevice$AbsoluteInfo;)Z",
        (void*) android_server_KeyInputQueue_getAbsoluteInfo },
    { "getSwitchState", "(I)I",
        (void*) android_server_KeyInputQueue_getSwitchState },
    { "getSwitchState", "(II)I",
        (void*) android_server_KeyInputQueue_getSwitchStateDevice },
    { "nativeGetScancodeState", "(I)I",
        (void*) android_server_KeyInputQueue_getScancodeState },
    { "nativeGetScancodeState", "(II)I",
        (void*) android_server_KeyInputQueue_getScancodeStateDevice },
    { "nativeGetKeycodeState", "(I)I",
        (void*) android_server_KeyInputQueue_getKeycodeState },
    { "nativeGetKeycodeState", "(II)I",
        (void*) android_server_KeyInputQueue_getKeycodeStateDevice },
    { "hasKeys", "([I[Z)Z",
        (void*) android_server_KeyInputQueue_hasKeys },
    { "scancodeToKeycode", "(II)I",
        (void*) android_server_KeyInputQueue_scancodeToKeycode },
};

int register_android_server_KeyInputQueue(JNIEnv* env)
{
    jclass input = env->FindClass("com/android/server/KeyInputQueue");
    LOG_FATAL_IF(input == NULL, "Unable to find class com/android/server/KeyInputQueue");
    int res = jniRegisterNativeMethods(env, "com/android/server/KeyInputQueue",
                                        gInputMethods, NELEM(gInputMethods));

    jclass absoluteInfo = env->FindClass("com/android/server/InputDevice$AbsoluteInfo");
    LOG_FATAL_IF(absoluteInfo == NULL, "Unable to find class com/android/server/InputDevice$AbsoluteInfo");
    
    gInputOffsets.mMinValue
        = env->GetFieldID(absoluteInfo, "minValue", "I");
    LOG_FATAL_IF(gInputOffsets.mMinValue == NULL, "Unable to find InputDevice.AbsoluteInfo.minValue");
    
    gInputOffsets.mMaxValue
        = env->GetFieldID(absoluteInfo, "maxValue", "I");
    LOG_FATAL_IF(gInputOffsets.mMaxValue == NULL, "Unable to find InputDevice.AbsoluteInfo.maxValue");
    
    gInputOffsets.mFlat
        = env->GetFieldID(absoluteInfo, "flat", "I");
    LOG_FATAL_IF(gInputOffsets.mFlat == NULL, "Unable to find InputDevice.AbsoluteInfo.flat");
    
    gInputOffsets.mFuzz
        = env->GetFieldID(absoluteInfo, "fuzz", "I");
    LOG_FATAL_IF(gInputOffsets.mFuzz == NULL, "Unable to find InputDevice.AbsoluteInfo.fuzz");
    
    jclass inputEvent = env->FindClass("android/view/RawInputEvent");
    LOG_FATAL_IF(inputEvent == NULL, "Unable to find class android/view/RawInputEvent");

    gInputOffsets.mDeviceId
        = env->GetFieldID(inputEvent, "deviceId", "I");
    LOG_FATAL_IF(gInputOffsets.mDeviceId == NULL, "Unable to find RawInputEvent.deviceId");
    
    gInputOffsets.mType
        = env->GetFieldID(inputEvent, "type", "I");
    LOG_FATAL_IF(gInputOffsets.mType == NULL, "Unable to find RawInputEvent.type");
    
    gInputOffsets.mScancode
        = env->GetFieldID(inputEvent, "scancode", "I");
    LOG_FATAL_IF(gInputOffsets.mScancode == NULL, "Unable to find RawInputEvent.scancode");

    gInputOffsets.mKeycode
        = env->GetFieldID(inputEvent, "keycode", "I");
    LOG_FATAL_IF(gInputOffsets.mKeycode == NULL, "Unable to find RawInputEvent.keycode");

    gInputOffsets.mFlags
        = env->GetFieldID(inputEvent, "flags", "I");
    LOG_FATAL_IF(gInputOffsets.mFlags == NULL, "Unable to find RawInputEvent.flags");

    gInputOffsets.mValue
        = env->GetFieldID(inputEvent, "value", "I");
    LOG_FATAL_IF(gInputOffsets.mValue == NULL, "Unable to find RawInputEvent.value");
    
    gInputOffsets.mWhen
        = env->GetFieldID(inputEvent, "when", "J");
    LOG_FATAL_IF(gInputOffsets.mWhen == NULL, "Unable to find RawInputEvent.when");

    return res;
}

}; // namespace android