/*
 * Copyright 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_TAG "SensorManager"

#include <nativehelper/JNIHelp.h>
#include "android_os_MessageQueue.h"
#include "core_jni_helpers.h"
#include "jni.h"

#include <nativehelper/ScopedUtfChars.h>
#include <nativehelper/ScopedLocalRef.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_hardware_HardwareBuffer.h>
#include <vndk/hardware_buffer.h>
#include <sensor/Sensor.h>
#include <sensor/SensorEventQueue.h>
#include <sensor/SensorManager.h>
#include <cutils/native_handle.h>
#include <utils/Log.h>
#include <utils/Looper.h>
#include <utils/Vector.h>

#include <map>

namespace {

using namespace android;

struct {
    jclass clazz;
    jmethodID dispatchSensorEvent;
    jmethodID dispatchFlushCompleteEvent;
    jmethodID dispatchAdditionalInfoEvent;
} gBaseEventQueueClassInfo;

struct SensorOffsets
{
    jclass      clazz;
    //fields
    jfieldID    name;
    jfieldID    vendor;
    jfieldID    version;
    jfieldID    handle;
    jfieldID    range;
    jfieldID    resolution;
    jfieldID    power;
    jfieldID    minDelay;
    jfieldID    fifoReservedEventCount;
    jfieldID    fifoMaxEventCount;
    jfieldID    stringType;
    jfieldID    requiredPermission;
    jfieldID    maxDelay;
    jfieldID    flags;
    //methods
    jmethodID   setType;
    jmethodID   setUuid;
    jmethodID   init;
} gSensorOffsets;

struct ListOffsets {
    jclass      clazz;
    jmethodID   add;
} gListOffsets;

struct StringOffsets {
    jclass      clazz;
    jmethodID   intern;
    jstring     emptyString;
} gStringOffsets;

/*
 * nativeClassInit is not inteneded to be thread-safe. It should be called before other native...
 * functions (except nativeCreate).
 */
static void
nativeClassInit (JNIEnv *_env, jclass _this)
{
    //android.hardware.Sensor
    SensorOffsets& sensorOffsets = gSensorOffsets;
    jclass sensorClass = (jclass)
            MakeGlobalRefOrDie(_env, FindClassOrDie(_env, "android/hardware/Sensor"));
    sensorOffsets.clazz = sensorClass;
    sensorOffsets.name = GetFieldIDOrDie(_env, sensorClass, "mName", "Ljava/lang/String;");
    sensorOffsets.vendor = GetFieldIDOrDie(_env, sensorClass, "mVendor", "Ljava/lang/String;");
    sensorOffsets.version = GetFieldIDOrDie(_env, sensorClass, "mVersion", "I");
    sensorOffsets.handle = GetFieldIDOrDie(_env, sensorClass, "mHandle", "I");
    sensorOffsets.range = GetFieldIDOrDie(_env, sensorClass, "mMaxRange", "F");
    sensorOffsets.resolution = GetFieldIDOrDie(_env, sensorClass, "mResolution","F");
    sensorOffsets.power = GetFieldIDOrDie(_env, sensorClass, "mPower", "F");
    sensorOffsets.minDelay = GetFieldIDOrDie(_env, sensorClass, "mMinDelay", "I");
    sensorOffsets.fifoReservedEventCount =
            GetFieldIDOrDie(_env,sensorClass, "mFifoReservedEventCount", "I");
    sensorOffsets.fifoMaxEventCount = GetFieldIDOrDie(_env,sensorClass, "mFifoMaxEventCount", "I");
    sensorOffsets.stringType =
            GetFieldIDOrDie(_env,sensorClass, "mStringType", "Ljava/lang/String;");
    sensorOffsets.requiredPermission =
            GetFieldIDOrDie(_env,sensorClass, "mRequiredPermission", "Ljava/lang/String;");
    sensorOffsets.maxDelay = GetFieldIDOrDie(_env,sensorClass, "mMaxDelay", "I");
    sensorOffsets.flags = GetFieldIDOrDie(_env,sensorClass, "mFlags", "I");

    sensorOffsets.setType = GetMethodIDOrDie(_env,sensorClass, "setType", "(I)Z");
    sensorOffsets.setUuid = GetMethodIDOrDie(_env,sensorClass, "setUuid", "(JJ)V");
    sensorOffsets.init = GetMethodIDOrDie(_env,sensorClass, "<init>", "()V");

    // java.util.List;
    ListOffsets& listOffsets = gListOffsets;
    jclass listClass = (jclass) MakeGlobalRefOrDie(_env, FindClassOrDie(_env, "java/util/List"));
    listOffsets.clazz = listClass;
    listOffsets.add = GetMethodIDOrDie(_env,listClass, "add", "(Ljava/lang/Object;)Z");

    // initialize java.lang.String and empty string intern
    StringOffsets& stringOffsets = gStringOffsets;
    stringOffsets.clazz = MakeGlobalRefOrDie(_env, FindClassOrDie(_env, "java/lang/String"));
    stringOffsets.intern =
            GetMethodIDOrDie(_env, stringOffsets.clazz, "intern", "()Ljava/lang/String;");
    ScopedLocalRef<jstring> empty(_env, _env->NewStringUTF(""));
    stringOffsets.emptyString = (jstring)
            MakeGlobalRefOrDie(_env, _env->CallObjectMethod(empty.get(), stringOffsets.intern));
}

static jstring getJavaInternedString(JNIEnv *env, const String8 &string) {
    if (string == "") {
        return gStringOffsets.emptyString;
    }

    ScopedLocalRef<jstring> javaString(env, env->NewStringUTF(string.string()));
    jstring internedString = (jstring)
            env->CallObjectMethod(javaString.get(), gStringOffsets.intern);
    return internedString;
}

static jlong
nativeCreate
(JNIEnv *env, jclass clazz, jstring opPackageName)
{
    ScopedUtfChars opPackageNameUtf(env, opPackageName);
    return (jlong) &SensorManager::getInstanceForPackage(String16(opPackageNameUtf.c_str()));
}

static jobject
translateNativeSensorToJavaSensor(JNIEnv *env, jobject sensor, const Sensor& nativeSensor) {
    const SensorOffsets& sensorOffsets(gSensorOffsets);

    if (sensor == NULL) {
        // Sensor sensor = new Sensor();
        sensor = env->NewObject(sensorOffsets.clazz, sensorOffsets.init, "");
    }

    if (sensor != NULL) {
        jstring name = getJavaInternedString(env, nativeSensor.getName());
        jstring vendor = getJavaInternedString(env, nativeSensor.getVendor());
        jstring requiredPermission =
                getJavaInternedString(env, nativeSensor.getRequiredPermission());

        env->SetObjectField(sensor, sensorOffsets.name,      name);
        env->SetObjectField(sensor, sensorOffsets.vendor,    vendor);
        env->SetIntField(sensor, sensorOffsets.version,      nativeSensor.getVersion());
        env->SetIntField(sensor, sensorOffsets.handle,       nativeSensor.getHandle());
        env->SetFloatField(sensor, sensorOffsets.range,      nativeSensor.getMaxValue());
        env->SetFloatField(sensor, sensorOffsets.resolution, nativeSensor.getResolution());
        env->SetFloatField(sensor, sensorOffsets.power,      nativeSensor.getPowerUsage());
        env->SetIntField(sensor, sensorOffsets.minDelay,     nativeSensor.getMinDelay());
        env->SetIntField(sensor, sensorOffsets.fifoReservedEventCount,
                         nativeSensor.getFifoReservedEventCount());
        env->SetIntField(sensor, sensorOffsets.fifoMaxEventCount,
                         nativeSensor.getFifoMaxEventCount());
        env->SetObjectField(sensor, sensorOffsets.requiredPermission,
                            requiredPermission);
        env->SetIntField(sensor, sensorOffsets.maxDelay, nativeSensor.getMaxDelay());
        env->SetIntField(sensor, sensorOffsets.flags, nativeSensor.getFlags());

        if (env->CallBooleanMethod(sensor, sensorOffsets.setType, nativeSensor.getType())
                == JNI_FALSE) {
            jstring stringType = getJavaInternedString(env, nativeSensor.getStringType());
            env->SetObjectField(sensor, sensorOffsets.stringType, stringType);
        }

        // TODO(b/29547335): Rename "setUuid" method to "setId".
        int64_t id = nativeSensor.getId();
        env->CallVoidMethod(sensor, sensorOffsets.setUuid, id, 0);
    }
    return sensor;
}

static jboolean
nativeGetSensorAtIndex(JNIEnv *env, jclass clazz, jlong sensorManager, jobject sensor, jint index)
{
    SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);

    Sensor const* const* sensorList;
    ssize_t count = mgr->getSensorList(&sensorList);
    if (ssize_t(index) >= count) {
        return false;
    }

    return translateNativeSensorToJavaSensor(env, sensor, *sensorList[index]) != NULL;
}

static void
nativeGetDynamicSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jobject sensorList) {

    SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
    const ListOffsets& listOffsets(gListOffsets);

    Vector<Sensor> nativeList;

    mgr->getDynamicSensorList(nativeList);

    ALOGI("DYNS native SensorManager.getDynamicSensorList return %zu sensors", nativeList.size());
    for (size_t i = 0; i < nativeList.size(); ++i) {
        jobject sensor = translateNativeSensorToJavaSensor(env, NULL, nativeList[i]);
        // add to list
        env->CallBooleanMethod(sensorList, listOffsets.add, sensor);
    }
}

static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong sensorManager) {
    SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
    return mgr->isDataInjectionEnabled();
}

static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
        jlong size, jint channelType, jint fd, jobject hardwareBufferObj) {
    const native_handle_t *nativeHandle = nullptr;
    NATIVE_HANDLE_DECLARE_STORAGE(ashmemHandle, 1, 0);

    if (channelType == SENSOR_DIRECT_MEM_TYPE_ASHMEM) {
        native_handle_t *handle = native_handle_init(ashmemHandle, 1, 0);
        handle->data[0] = fd;
        nativeHandle = handle;
    } else if (channelType == SENSOR_DIRECT_MEM_TYPE_GRALLOC) {
        AHardwareBuffer *hardwareBuffer =
                android_hardware_HardwareBuffer_getNativeHardwareBuffer(_env, hardwareBufferObj);
        if (hardwareBuffer != nullptr) {
            nativeHandle = AHardwareBuffer_getNativeHandle(hardwareBuffer);
        }
    }

    if (nativeHandle == nullptr) {
        return BAD_VALUE;
    }

    SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
    return mgr->createDirectChannel(size, channelType, nativeHandle);
}

static void nativeDestroyDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
        jint channelHandle) {
    SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
    mgr->destroyDirectChannel(channelHandle);
}

static jint nativeConfigDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
        jint channelHandle, jint sensorHandle, jint rate) {
    SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
    return mgr->configureDirectChannel(channelHandle, sensorHandle, rate);
}

static jint nativeSetOperationParameter(JNIEnv *_env, jclass _this, jlong sensorManager,
        jint handle, jint type, jfloatArray floats, jintArray ints) {
    SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
    Vector<float> floatVector;
    Vector<int32_t> int32Vector;

    if (floats != nullptr) {
        floatVector.resize(_env->GetArrayLength(floats));
        _env->GetFloatArrayRegion(floats, 0, _env->GetArrayLength(floats), floatVector.editArray());
    }

    if (ints != nullptr) {
        int32Vector.resize(_env->GetArrayLength(ints));
        _env->GetIntArrayRegion(ints, 0, _env->GetArrayLength(ints), int32Vector.editArray());
    }

    return mgr->setOperationParameter(handle, type, floatVector, int32Vector);
}

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

class Receiver : public LooperCallback {
    sp<SensorEventQueue> mSensorQueue;
    sp<MessageQueue> mMessageQueue;
    jobject mReceiverWeakGlobal;
    jfloatArray mFloatScratch;
    jintArray   mIntScratch;
public:
    Receiver(const sp<SensorEventQueue>& sensorQueue,
            const sp<MessageQueue>& messageQueue,
            jobject receiverWeak) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        mSensorQueue = sensorQueue;
        mMessageQueue = messageQueue;
        mReceiverWeakGlobal = env->NewGlobalRef(receiverWeak);

        mIntScratch = (jintArray) env->NewGlobalRef(env->NewIntArray(16));
        mFloatScratch = (jfloatArray) env->NewGlobalRef(env->NewFloatArray(16));
    }
    ~Receiver() {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        env->DeleteGlobalRef(mReceiverWeakGlobal);
        env->DeleteGlobalRef(mFloatScratch);
        env->DeleteGlobalRef(mIntScratch);
    }
    sp<SensorEventQueue> getSensorEventQueue() const {
        return mSensorQueue;
    }

    void destroy() {
        mMessageQueue->getLooper()->removeFd( mSensorQueue->getFd() );
    }

private:
    virtual void onFirstRef() {
        LooperCallback::onFirstRef();
        mMessageQueue->getLooper()->addFd(mSensorQueue->getFd(), 0,
                ALOOPER_EVENT_INPUT, this, mSensorQueue.get());
    }

    virtual int handleEvent(int fd, int events, void* data) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        sp<SensorEventQueue> q = reinterpret_cast<SensorEventQueue *>(data);
        ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal));

        ssize_t n;
        ASensorEvent buffer[16];
        while ((n = q->read(buffer, 16)) > 0) {
            for (int i=0 ; i<n ; i++) {
                if (buffer[i].type == SENSOR_TYPE_STEP_COUNTER) {
                    // step-counter returns a uint64, but the java API only deals with floats
                    float value = float(buffer[i].u64.step_counter);
                    env->SetFloatArrayRegion(mFloatScratch, 0, 1, &value);
                } else if (buffer[i].type == SENSOR_TYPE_DYNAMIC_SENSOR_META) {
                    float value[2];
                    value[0] = buffer[i].dynamic_sensor_meta.connected ? 1.f: 0.f;
                    value[1] = float(buffer[i].dynamic_sensor_meta.handle);
                    env->SetFloatArrayRegion(mFloatScratch, 0, 2, value);
                } else if (buffer[i].type == SENSOR_TYPE_ADDITIONAL_INFO) {
                    env->SetIntArrayRegion(mIntScratch, 0, 14,
                                           buffer[i].additional_info.data_int32);
                    env->SetFloatArrayRegion(mFloatScratch, 0, 14,
                                             buffer[i].additional_info.data_float);
                } else {
                    env->SetFloatArrayRegion(mFloatScratch, 0, 16, buffer[i].data);
                }

                if (buffer[i].type == SENSOR_TYPE_META_DATA) {
                    // This is a flush complete sensor event. Call dispatchFlushCompleteEvent
                    // method.
                    if (receiverObj.get()) {
                        env->CallVoidMethod(receiverObj.get(),
                                            gBaseEventQueueClassInfo.dispatchFlushCompleteEvent,
                                            buffer[i].meta_data.sensor);
                    }
                } else if (buffer[i].type == SENSOR_TYPE_ADDITIONAL_INFO) {
                    // This is a flush complete sensor event. Call dispatchAdditionalInfoEvent
                    // method.
                    if (receiverObj.get()) {
                        int type = buffer[i].additional_info.type;
                        int serial = buffer[i].additional_info.serial;
                        env->CallVoidMethod(receiverObj.get(),
                                            gBaseEventQueueClassInfo.dispatchAdditionalInfoEvent,
                                            buffer[i].sensor,
                                            type, serial,
                                            mFloatScratch,
                                            mIntScratch,
                                            buffer[i].timestamp);
                    }
                }else {
                    int8_t status;
                    switch (buffer[i].type) {
                    case SENSOR_TYPE_ORIENTATION:
                    case SENSOR_TYPE_MAGNETIC_FIELD:
                    case SENSOR_TYPE_ACCELEROMETER:
                    case SENSOR_TYPE_GYROSCOPE:
                    case SENSOR_TYPE_GRAVITY:
                    case SENSOR_TYPE_LINEAR_ACCELERATION:
                        status = buffer[i].vector.status;
                        break;
                    case SENSOR_TYPE_HEART_RATE:
                        status = buffer[i].heart_rate.status;
                        break;
                    default:
                        status = SENSOR_STATUS_ACCURACY_HIGH;
                        break;
                    }
                    if (receiverObj.get()) {
                        env->CallVoidMethod(receiverObj.get(),
                                            gBaseEventQueueClassInfo.dispatchSensorEvent,
                                            buffer[i].sensor,
                                            mFloatScratch,
                                            status,
                                            buffer[i].timestamp);
                    }
                }
                if (env->ExceptionCheck()) {
                    mSensorQueue->sendAck(buffer, n);
                    ALOGE("Exception dispatching input event.");
                    return 1;
                }
            }
            mSensorQueue->sendAck(buffer, n);
        }
        if (n<0 && n != -EAGAIN) {
            // FIXME: error receiving events, what to do in this case?
        }
        return 1;
    }
};

static jlong nativeInitSensorEventQueue(JNIEnv *env, jclass clazz, jlong sensorManager,
        jobject eventQWeak, jobject msgQ, jstring packageName, jint mode) {
    SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
    ScopedUtfChars packageUtf(env, packageName);
    String8 clientName(packageUtf.c_str());
    sp<SensorEventQueue> queue(mgr->createEventQueue(clientName, mode));

    if (queue == NULL) {
        jniThrowRuntimeException(env, "Cannot construct native SensorEventQueue.");
        return 0;
    }

    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, msgQ);
    if (messageQueue == NULL) {
        jniThrowRuntimeException(env, "MessageQueue is not initialized.");
        return 0;
    }

    sp<Receiver> receiver = new Receiver(queue, messageQueue, eventQWeak);
    receiver->incStrong((void*)nativeInitSensorEventQueue);
    return jlong(receiver.get());
}

static jint nativeEnableSensor(JNIEnv *env, jclass clazz, jlong eventQ, jint handle, jint rate_us,
                               jint maxBatchReportLatency) {
    sp<Receiver> receiver(reinterpret_cast<Receiver *>(eventQ));
    return receiver->getSensorEventQueue()->enableSensor(handle, rate_us, maxBatchReportLatency,
                                                         0);
}

static jint nativeDisableSensor(JNIEnv *env, jclass clazz, jlong eventQ, jint handle) {
    sp<Receiver> receiver(reinterpret_cast<Receiver *>(eventQ));
    return receiver->getSensorEventQueue()->disableSensor(handle);
}

static void nativeDestroySensorEventQueue(JNIEnv *env, jclass clazz, jlong eventQ) {
    sp<Receiver> receiver(reinterpret_cast<Receiver *>(eventQ));
    receiver->destroy();
    receiver->decStrong((void*)nativeInitSensorEventQueue);
}

static jint nativeFlushSensor(JNIEnv *env, jclass clazz, jlong eventQ) {
    sp<Receiver> receiver(reinterpret_cast<Receiver *>(eventQ));
    return receiver->getSensorEventQueue()->flush();
}

static jint nativeInjectSensorData(JNIEnv *env, jclass clazz, jlong eventQ, jint handle,
        jfloatArray values, jint accuracy, jlong timestamp) {
    sp<Receiver> receiver(reinterpret_cast<Receiver *>(eventQ));
    // Create a sensor_event from the above data which can be injected into the HAL.
    ASensorEvent sensor_event;
    memset(&sensor_event, 0, sizeof(sensor_event));
    sensor_event.sensor = handle;
    sensor_event.timestamp = timestamp;
    env->GetFloatArrayRegion(values, 0, env->GetArrayLength(values), sensor_event.data);
    return receiver->getSensorEventQueue()->injectSensorEvent(sensor_event);
}
//----------------------------------------------------------------------------

static const JNINativeMethod gSystemSensorManagerMethods[] = {
    {"nativeClassInit",
            "()V",
            (void*)nativeClassInit },
    {"nativeCreate",
             "(Ljava/lang/String;)J",
             (void*)nativeCreate },

    {"nativeGetSensorAtIndex",
            "(JLandroid/hardware/Sensor;I)Z",
            (void*)nativeGetSensorAtIndex },

    {"nativeGetDynamicSensors",
            "(JLjava/util/List;)V",
            (void*)nativeGetDynamicSensors },

    {"nativeIsDataInjectionEnabled",
            "(J)Z",
            (void*)nativeIsDataInjectionEnabled },

    {"nativeCreateDirectChannel",
            "(JJIILandroid/hardware/HardwareBuffer;)I",
            (void*)nativeCreateDirectChannel },

    {"nativeDestroyDirectChannel",
            "(JI)V",
            (void*)nativeDestroyDirectChannel },

    {"nativeConfigDirectChannel",
            "(JIII)I",
            (void*)nativeConfigDirectChannel },

    {"nativeSetOperationParameter",
            "(JII[F[I)I",
            (void*)nativeSetOperationParameter },
};

static const JNINativeMethod gBaseEventQueueMethods[] = {
    {"nativeInitBaseEventQueue",
             "(JLjava/lang/ref/WeakReference;Landroid/os/MessageQueue;Ljava/lang/String;ILjava/lang/String;)J",
             (void*)nativeInitSensorEventQueue },

    {"nativeEnableSensor",
            "(JIII)I",
            (void*)nativeEnableSensor },

    {"nativeDisableSensor",
            "(JI)I",
            (void*)nativeDisableSensor },

    {"nativeDestroySensorEventQueue",
            "(J)V",
            (void*)nativeDestroySensorEventQueue },

    {"nativeFlushSensor",
            "(J)I",
            (void*)nativeFlushSensor },

    {"nativeInjectSensorData",
            "(JI[FIJ)I",
            (void*)nativeInjectSensorData },
};

} //unnamed namespace

int register_android_hardware_SensorManager(JNIEnv *env)
{
    RegisterMethodsOrDie(env, "android/hardware/SystemSensorManager",
            gSystemSensorManagerMethods, NELEM(gSystemSensorManagerMethods));

    RegisterMethodsOrDie(env, "android/hardware/SystemSensorManager$BaseEventQueue",
            gBaseEventQueueMethods, NELEM(gBaseEventQueueMethods));

    gBaseEventQueueClassInfo.clazz = FindClassOrDie(env,
            "android/hardware/SystemSensorManager$BaseEventQueue");

    gBaseEventQueueClassInfo.dispatchSensorEvent = GetMethodIDOrDie(env,
            gBaseEventQueueClassInfo.clazz, "dispatchSensorEvent", "(I[FIJ)V");

    gBaseEventQueueClassInfo.dispatchFlushCompleteEvent = GetMethodIDOrDie(env,
            gBaseEventQueueClassInfo.clazz, "dispatchFlushCompleteEvent", "(I)V");

    gBaseEventQueueClassInfo.dispatchAdditionalInfoEvent = GetMethodIDOrDie(env,
            gBaseEventQueueClassInfo.clazz, "dispatchAdditionalInfoEvent", "(III[F[I)V");

    return 0;
}
