/*
 * Copyright (C) 2011 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 "VelocityTracker-JNI"

#include "JNIHelp.h"

#include <android_runtime/AndroidRuntime.h>
#include <utils/Log.h>
#include <input/Input.h>
#include <input/VelocityTracker.h>
#include "android_view_MotionEvent.h"

#include <ScopedUtfChars.h>

#include "core_jni_helpers.h"

namespace android {

// Special constant to request the velocity of the active pointer.
static const int ACTIVE_POINTER_ID = -1;

static struct {
    jfieldID xCoeff;
    jfieldID yCoeff;
    jfieldID degree;
    jfieldID confidence;
} gEstimatorClassInfo;


// --- VelocityTrackerState ---

class VelocityTrackerState {
public:
    VelocityTrackerState(const char* strategy);

    void clear();
    void addMovement(const MotionEvent* event);
    void computeCurrentVelocity(int32_t units, float maxVelocity);
    void getVelocity(int32_t id, float* outVx, float* outVy);
    bool getEstimator(int32_t id, VelocityTracker::Estimator* outEstimator);

private:
    struct Velocity {
        float vx, vy;
    };

    VelocityTracker mVelocityTracker;
    int32_t mActivePointerId;
    BitSet32 mCalculatedIdBits;
    Velocity mCalculatedVelocity[MAX_POINTERS];
};

VelocityTrackerState::VelocityTrackerState(const char* strategy) :
        mVelocityTracker(strategy), mActivePointerId(-1) {
}

void VelocityTrackerState::clear() {
    mVelocityTracker.clear();
    mActivePointerId = -1;
    mCalculatedIdBits.clear();
}

void VelocityTrackerState::addMovement(const MotionEvent* event) {
    mVelocityTracker.addMovement(event);
}

void VelocityTrackerState::computeCurrentVelocity(int32_t units, float maxVelocity) {
    BitSet32 idBits(mVelocityTracker.getCurrentPointerIdBits());
    mCalculatedIdBits = idBits;

    for (uint32_t index = 0; !idBits.isEmpty(); index++) {
        uint32_t id = idBits.clearFirstMarkedBit();

        float vx, vy;
        mVelocityTracker.getVelocity(id, &vx, &vy);

        vx = vx * units / 1000;
        vy = vy * units / 1000;

        if (vx > maxVelocity) {
            vx = maxVelocity;
        } else if (vx < -maxVelocity) {
            vx = -maxVelocity;
        }
        if (vy > maxVelocity) {
            vy = maxVelocity;
        } else if (vy < -maxVelocity) {
            vy = -maxVelocity;
        }

        Velocity& velocity = mCalculatedVelocity[index];
        velocity.vx = vx;
        velocity.vy = vy;
    }
}

void VelocityTrackerState::getVelocity(int32_t id, float* outVx, float* outVy) {
    if (id == ACTIVE_POINTER_ID) {
        id = mVelocityTracker.getActivePointerId();
    }

    float vx, vy;
    if (id >= 0 && id <= MAX_POINTER_ID && mCalculatedIdBits.hasBit(id)) {
        uint32_t index = mCalculatedIdBits.getIndexOfBit(id);
        const Velocity& velocity = mCalculatedVelocity[index];
        vx = velocity.vx;
        vy = velocity.vy;
    } else {
        vx = 0;
        vy = 0;
    }

    if (outVx) {
        *outVx = vx;
    }
    if (outVy) {
        *outVy = vy;
    }
}

bool VelocityTrackerState::getEstimator(int32_t id, VelocityTracker::Estimator* outEstimator) {
    return mVelocityTracker.getEstimator(id, outEstimator);
}


// --- JNI Methods ---

static jlong android_view_VelocityTracker_nativeInitialize(JNIEnv* env, jclass clazz,
        jstring strategyStr) {
    if (strategyStr) {
        ScopedUtfChars strategy(env, strategyStr);
        return reinterpret_cast<jlong>(new VelocityTrackerState(strategy.c_str()));
    }
    return reinterpret_cast<jlong>(new VelocityTrackerState(NULL));
}

static void android_view_VelocityTracker_nativeDispose(JNIEnv* env, jclass clazz, jlong ptr) {
    VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
    delete state;
}

static void android_view_VelocityTracker_nativeClear(JNIEnv* env, jclass clazz, jlong ptr) {
    VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
    state->clear();
}

static void android_view_VelocityTracker_nativeAddMovement(JNIEnv* env, jclass clazz, jlong ptr,
        jobject eventObj) {
    const MotionEvent* event = android_view_MotionEvent_getNativePtr(env, eventObj);
    if (!event) {
        ALOGW("nativeAddMovement failed because MotionEvent was finalized.");
        return;
    }

    VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
    state->addMovement(event);
}

static void android_view_VelocityTracker_nativeComputeCurrentVelocity(JNIEnv* env, jclass clazz,
        jlong ptr, jint units, jfloat maxVelocity) {
    VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
    state->computeCurrentVelocity(units, maxVelocity);
}

static jfloat android_view_VelocityTracker_nativeGetXVelocity(JNIEnv* env, jclass clazz,
        jlong ptr, jint id) {
    VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
    float vx;
    state->getVelocity(id, &vx, NULL);
    return vx;
}

static jfloat android_view_VelocityTracker_nativeGetYVelocity(JNIEnv* env, jclass clazz,
        jlong ptr, jint id) {
    VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
    float vy;
    state->getVelocity(id, NULL, &vy);
    return vy;
}

static jboolean android_view_VelocityTracker_nativeGetEstimator(JNIEnv* env, jclass clazz,
        jlong ptr, jint id, jobject outEstimatorObj) {
    VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
    VelocityTracker::Estimator estimator;
    bool result = state->getEstimator(id, &estimator);

    jfloatArray xCoeffObj = jfloatArray(env->GetObjectField(outEstimatorObj,
            gEstimatorClassInfo.xCoeff));
    jfloatArray yCoeffObj = jfloatArray(env->GetObjectField(outEstimatorObj,
            gEstimatorClassInfo.yCoeff));

    env->SetFloatArrayRegion(xCoeffObj, 0, VelocityTracker::Estimator::MAX_DEGREE + 1,
            estimator.xCoeff);
    env->SetFloatArrayRegion(yCoeffObj, 0, VelocityTracker::Estimator::MAX_DEGREE + 1,
            estimator.yCoeff);
    env->SetIntField(outEstimatorObj, gEstimatorClassInfo.degree, estimator.degree);
    env->SetFloatField(outEstimatorObj, gEstimatorClassInfo.confidence, estimator.confidence);
    return result;
}


// --- JNI Registration ---

static JNINativeMethod gVelocityTrackerMethods[] = {
    /* name, signature, funcPtr */
    { "nativeInitialize",
            "(Ljava/lang/String;)J",
            (void*)android_view_VelocityTracker_nativeInitialize },
    { "nativeDispose",
            "(J)V",
            (void*)android_view_VelocityTracker_nativeDispose },
    { "nativeClear",
            "(J)V",
            (void*)android_view_VelocityTracker_nativeClear },
    { "nativeAddMovement",
            "(JLandroid/view/MotionEvent;)V",
            (void*)android_view_VelocityTracker_nativeAddMovement },
    { "nativeComputeCurrentVelocity",
            "(JIF)V",
            (void*)android_view_VelocityTracker_nativeComputeCurrentVelocity },
    { "nativeGetXVelocity",
            "(JI)F",
            (void*)android_view_VelocityTracker_nativeGetXVelocity },
    { "nativeGetYVelocity",
            "(JI)F",
            (void*)android_view_VelocityTracker_nativeGetYVelocity },
    { "nativeGetEstimator",
            "(JILandroid/view/VelocityTracker$Estimator;)Z",
            (void*)android_view_VelocityTracker_nativeGetEstimator },
};

int register_android_view_VelocityTracker(JNIEnv* env) {
    int res = RegisterMethodsOrDie(env, "android/view/VelocityTracker", gVelocityTrackerMethods,
                                   NELEM(gVelocityTrackerMethods));

    jclass clazz = FindClassOrDie(env, "android/view/VelocityTracker$Estimator");

    gEstimatorClassInfo.xCoeff = GetFieldIDOrDie(env, clazz, "xCoeff", "[F");
    gEstimatorClassInfo.yCoeff = GetFieldIDOrDie(env, clazz, "yCoeff", "[F");
    gEstimatorClassInfo.degree = GetFieldIDOrDie(env, clazz, "degree", "I");
    gEstimatorClassInfo.confidence = GetFieldIDOrDie(env, clazz, "confidence", "F");

    return res;
}

} // namespace android
