/*
 * Copyright (C) 2020 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.
 */

#ifndef _ANDROID_SERVER_GNSS_UTILS_H
#define _ANDROID_SERVER_GNSS_UTILS_H

#pragma once

#ifndef LOG_TAG
#error LOG_TAG must be defined before including this file.
#endif

#include <android/hardware/gnss/1.0/IGnss.h>
#include <android/hardware/gnss/BnGnss.h>
#include <log/log.h>
#include <nativehelper/JNIHelp.h>
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/Log.h"
#include "jni.h"

namespace android {

namespace {

// Must match the value from GnssMeasurement.java
const uint32_t ADR_STATE_HALF_CYCLE_REPORTED = (1 << 4);
extern jmethodID method_locationCtor;

} // anonymous namespace

extern jclass class_location;
extern jobject mCallbacksObj;

namespace gnss {
void Utils_class_init_once(JNIEnv* env);
} // namespace gnss

jobject& getCallbacksObj();

jboolean checkHidlReturn(hardware::Return<bool>& result, const char* errorMessage);

jboolean checkAidlStatus(const android::binder::Status& status, const char* errorMessage);

void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);

void callObjectMethodIgnoringResult(JNIEnv* env, jobject obj, jmethodID mid, ...);

template <class T>
void logHidlError(hardware::Return<T>& result, const char* errorMessage) {
    ALOGE("%s HIDL transport error: %s", errorMessage, result.description().c_str());
}

template <class T>
jboolean checkHidlReturn(hardware::Return<T>& result, const char* errorMessage) {
    if (!result.isOk()) {
        logHidlError(result, errorMessage);
        return JNI_FALSE;
    } else {
        return JNI_TRUE;
    }
}

template <class T>
jboolean checkHidlReturn(hardware::Return<sp<T>>& result, const char* errorMessage) {
    if (!result.isOk()) {
        logHidlError(result, errorMessage);
        return JNI_FALSE;
    } else if ((sp<T>)result == nullptr) {
        return JNI_FALSE;
    } else {
        return JNI_TRUE;
    }
}

template <class T>
class JavaMethodHelper {
public:
    // Helper function to call setter on a Java object.
    static void callJavaMethod(JNIEnv* env, jclass clazz, jobject object, const char* method_name,
                               T value);

private:
    static const char* const signature_;
};

// Define Java method signatures for all known types.
template <>
const char* const JavaMethodHelper<uint8_t>::signature_;
template <>
const char* const JavaMethodHelper<int8_t>::signature_;
template <>
const char* const JavaMethodHelper<int16_t>::signature_;
template <>
const char* const JavaMethodHelper<uint16_t>::signature_;
template <>
const char* const JavaMethodHelper<int32_t>::signature_;
template <>
const char* const JavaMethodHelper<uint32_t>::signature_;
template <>
const char* const JavaMethodHelper<int64_t>::signature_;
template <>
const char* const JavaMethodHelper<uint64_t>::signature_;
template <>
const char* const JavaMethodHelper<float>::signature_;
template <>
const char* const JavaMethodHelper<double>::signature_;
template <>
const char* const JavaMethodHelper<bool>::signature_;
template <>
const char* const JavaMethodHelper<jstring>::signature_;
template <>
const char* const JavaMethodHelper<jdoubleArray>::signature_;

template <class T>
void JavaMethodHelper<T>::callJavaMethod(JNIEnv* env, jclass clazz, jobject object,
                                         const char* method_name, T value) {
    jmethodID method = env->GetMethodID(clazz, method_name, signature_);
    env->CallVoidMethod(object, method, value);
}

class JavaObject {
public:
    JavaObject(JNIEnv* env, jclass clazz, jmethodID defaultCtor);
    JavaObject(JNIEnv* env, jclass clazz, jmethodID stringCtor, const char* sz_arg_1);
    JavaObject(JNIEnv* env, jclass clazz, jobject object);

    virtual ~JavaObject() = default;

    template <class T>
    void callSetter(const char* method_name, T value);
    template <class T>
    void callSetter(const char* method_name, T* value, size_t size);
    jobject get() { return object_; }

private:
    JNIEnv* env_;
    jclass clazz_;
    jobject object_;
};

template <class T>
void JavaObject::callSetter(const char* method_name, T value) {
    JavaMethodHelper<T>::callJavaMethod(env_, clazz_, object_, method_name, value);
}

#define SET(setter, value) object.callSetter("set" #setter, (value))

class ScopedJniThreadAttach {
public:
    static JavaVM* sJvm;

    ScopedJniThreadAttach() {
        /*
         * attachResult will also be JNI_OK if the thead was already attached to
         * JNI before the call to AttachCurrentThread().
         */
        jint attachResult = sJvm->AttachCurrentThread(&mEnv, nullptr);
        LOG_ALWAYS_FATAL_IF(attachResult != JNI_OK, "Unable to attach thread. Error %d",
                            attachResult);
    }

    ~ScopedJniThreadAttach() {
        jint detachResult = sJvm->DetachCurrentThread();
        /*
         * Return if the thread was already detached. Log error for any other
         * failure.
         */
        if (detachResult == JNI_EDETACHED) {
            return;
        }

        LOG_ALWAYS_FATAL_IF(detachResult != JNI_OK, "Unable to detach thread. Error %d",
                            detachResult);
    }

    JNIEnv* getEnv() {
        /*
         * Checking validity of mEnv in case the thread was detached elsewhere.
         */
        LOG_ALWAYS_FATAL_IF(AndroidRuntime::getJNIEnv() != mEnv);
        return mEnv;
    }

private:
    JNIEnv* mEnv = nullptr;
};

struct ScopedJniString {
    ScopedJniString(JNIEnv* env, jstring javaString) : mEnv(env), mJavaString(javaString) {
        mNativeString = mEnv->GetStringUTFChars(mJavaString, nullptr);
    }

    ~ScopedJniString() {
        if (mNativeString != nullptr) {
            mEnv->ReleaseStringUTFChars(mJavaString, mNativeString);
        }
    }

    const char* c_str() const { return mNativeString; }

    operator hardware::hidl_string() const { return hardware::hidl_string(mNativeString); }

private:
    ScopedJniString(const ScopedJniString&) = delete;
    ScopedJniString& operator=(const ScopedJniString&) = delete;

    JNIEnv* mEnv;
    jstring mJavaString;
    const char* mNativeString;
};

JNIEnv* getJniEnv();

template <class T>
jobject translateGnssLocation(JNIEnv* env, const T& location);

} // namespace android

#endif // _ANDROID_SERVER_GNSS_UTILS_H
