/*
 * Copyright (C) 2022 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 "GnssCallbackJni"

#include "GnssCallback.h"

#include <hardware_legacy/power.h>

#define WAKE_LOCK_NAME "GPS"

namespace android::gnss {

using android::hardware::gnss::V1_0::GnssLocationFlags;
using binder::Status;
using hardware::hidl_vec;
using hardware::Return;
using hardware::Void;

using GnssLocationAidl = android::hardware::gnss::GnssLocation;
using GnssSignalType = android::hardware::gnss::GnssSignalType;
using GnssLocation_V1_0 = android::hardware::gnss::V1_0::GnssLocation;
using GnssLocation_V2_0 = android::hardware::gnss::V2_0::GnssLocation;
using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback;
using IGnssCallback_V1_0 = android::hardware::gnss::V1_0::IGnssCallback;
using IGnssCallback_V2_0 = android::hardware::gnss::V2_0::IGnssCallback;
using IGnssCallback_V2_1 = android::hardware::gnss::V2_1::IGnssCallback;

jmethodID method_reportGnssServiceDied;

namespace {

jclass class_arrayList;
jclass class_gnssSignalType;

jmethodID method_arrayListAdd;
jmethodID method_arrayListCtor;
jmethodID method_gnssSignalTypeCreate;
jmethodID method_reportLocation;
jmethodID method_reportStatus;
jmethodID method_reportSvStatus;
jmethodID method_reportNmea;
jmethodID method_setTopHalCapabilities;
jmethodID method_setSignalTypeCapabilities;
jmethodID method_setGnssYearOfHardware;
jmethodID method_setGnssHardwareModelName;
jmethodID method_requestLocation;
jmethodID method_requestUtcTime;

// Returns true if location has lat/long information.
inline bool hasLatLong(const GnssLocationAidl& location) {
    return (location.gnssLocationFlags & hardware::gnss::GnssLocation::HAS_LAT_LONG) != 0;
}

// Returns true if location has lat/long information.
inline bool hasLatLong(const GnssLocation_V1_0& location) {
    return (static_cast<uint32_t>(location.gnssLocationFlags) & GnssLocationFlags::HAS_LAT_LONG) !=
            0;
}

// Returns true if location has lat/long information.
inline bool hasLatLong(const GnssLocation_V2_0& location) {
    return hasLatLong(location.v1_0);
}

inline jboolean boolToJbool(bool value) {
    return value ? JNI_TRUE : JNI_FALSE;
}

// Must match the value from GnssMeasurement.java
const uint32_t SVID_FLAGS_HAS_BASEBAND_CN0 = (1 << 4);

} // anonymous namespace

bool isSvStatusRegistered = false;
bool isNmeaRegistered = false;

void Gnss_class_init_once(JNIEnv* env, jclass& clazz) {
    method_reportLocation =
            env->GetMethodID(clazz, "reportLocation", "(ZLandroid/location/Location;)V");
    method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V");
    method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "(I[I[F[F[F[F[F)V");
    method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");

    method_setTopHalCapabilities = env->GetMethodID(clazz, "setTopHalCapabilities", "(IZ)V");
    method_setSignalTypeCapabilities =
            env->GetMethodID(clazz, "setSignalTypeCapabilities", "(Ljava/util/List;)V");
    method_setGnssYearOfHardware = env->GetMethodID(clazz, "setGnssYearOfHardware", "(I)V");
    method_setGnssHardwareModelName =
            env->GetMethodID(clazz, "setGnssHardwareModelName", "(Ljava/lang/String;)V");

    method_requestLocation = env->GetMethodID(clazz, "requestLocation", "(ZZ)V");
    method_requestUtcTime = env->GetMethodID(clazz, "requestUtcTime", "()V");
    method_reportGnssServiceDied = env->GetMethodID(clazz, "reportGnssServiceDied", "()V");

    jclass arrayListClass = env->FindClass("java/util/ArrayList");
    class_arrayList = (jclass)env->NewGlobalRef(arrayListClass);
    method_arrayListCtor = env->GetMethodID(class_arrayList, "<init>", "()V");
    method_arrayListAdd = env->GetMethodID(class_arrayList, "add", "(Ljava/lang/Object;)Z");

    jclass gnssSignalTypeClass = env->FindClass("android/location/GnssSignalType");
    class_gnssSignalType = (jclass)env->NewGlobalRef(gnssSignalTypeClass);
    method_gnssSignalTypeCreate =
            env->GetStaticMethodID(class_gnssSignalType, "create",
                                   "(IDLjava/lang/String;)Landroid/location/GnssSignalType;");
}

Status GnssCallbackAidl::gnssSetCapabilitiesCb(const int capabilities) {
    ALOGD("%s: %du\n", __func__, capabilities);
    bool isAdrCapabilityKnown = (getInterfaceVersion() >= 3) ? true : false;
    JNIEnv* env = getJniEnv();
    env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities,
                        isAdrCapabilityKnown);
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    return Status::ok();
}

namespace {

jobject translateSingleSignalType(JNIEnv* env, const GnssSignalType& signalType) {
    jstring jstringCodeType = env->NewStringUTF(signalType.codeType.c_str());
    jobject signalTypeObject =
            env->CallStaticObjectMethod(class_gnssSignalType, method_gnssSignalTypeCreate,
                                        signalType.constellation, signalType.carrierFrequencyHz,
                                        jstringCodeType);
    env->DeleteLocalRef(jstringCodeType);
    return signalTypeObject;
}

} // anonymous namespace

Status GnssCallbackAidl::gnssSetSignalTypeCapabilitiesCb(
        const std::vector<GnssSignalType>& signalTypes) {
    ALOGD("%s: %d signal types", __func__, (int)signalTypes.size());
    JNIEnv* env = getJniEnv();
    jobject arrayList = env->NewObject(class_arrayList, method_arrayListCtor);
    for (auto& signalType : signalTypes) {
        jobject signalTypeObject = translateSingleSignalType(env, signalType);
        env->CallBooleanMethod(arrayList, method_arrayListAdd, signalTypeObject);
        // Delete Local Refs
        env->DeleteLocalRef(signalTypeObject);
    }
    env->CallVoidMethod(mCallbacksObj, method_setSignalTypeCapabilities, arrayList);
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    env->DeleteLocalRef(arrayList);
    return Status::ok();
}

Status GnssCallbackAidl::gnssStatusCb(const GnssStatusValue status) {
    JNIEnv* env = getJniEnv();
    env->CallVoidMethod(mCallbacksObj, method_reportStatus, status);
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    return Status::ok();
}

Status GnssCallbackAidl::gnssSvStatusCb(const std::vector<GnssSvInfo>& svInfoList) {
    GnssCallbackHidl::gnssSvStatusCbImpl<std::vector<GnssSvInfo>, GnssSvInfo>(svInfoList);
    return Status::ok();
}

Status GnssCallbackAidl::gnssLocationCb(const hardware::gnss::GnssLocation& location) {
    GnssCallbackHidl::gnssLocationCbImpl<hardware::gnss::GnssLocation>(location);
    return Status::ok();
}

Status GnssCallbackAidl::gnssNmeaCb(const int64_t timestamp, const std::string& nmea) {
    // In AIDL v1, if no listener is registered, do not report nmea to the framework.
    if (getInterfaceVersion() <= 1) {
        if (!isNmeaRegistered) {
            return Status::ok();
        }
    }
    JNIEnv* env = getJniEnv();
    /*
     * The Java code will call back to read these values.
     * We do this to avoid creating unnecessary String objects.
     */
    GnssCallbackHidl::sNmeaString = nmea.c_str();
    GnssCallbackHidl::sNmeaStringLength = nmea.size();

    env->CallVoidMethod(mCallbacksObj, method_reportNmea, timestamp);
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    return Status::ok();
}

Status GnssCallbackAidl::gnssAcquireWakelockCb() {
    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME);
    return Status::ok();
}

Status GnssCallbackAidl::gnssReleaseWakelockCb() {
    release_wake_lock(WAKE_LOCK_NAME);
    return Status::ok();
}

Status GnssCallbackAidl::gnssSetSystemInfoCb(const GnssSystemInfo& info) {
    ALOGD("%s: yearOfHw=%d, name=%s\n", __func__, info.yearOfHw, info.name.c_str());
    JNIEnv* env = getJniEnv();
    env->CallVoidMethod(mCallbacksObj, method_setGnssYearOfHardware, info.yearOfHw);
    jstring jstringName = env->NewStringUTF(info.name.c_str());
    env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareModelName, jstringName);
    if (jstringName) {
        env->DeleteLocalRef(jstringName);
    }
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    return Status::ok();
}

Status GnssCallbackAidl::gnssRequestTimeCb() {
    JNIEnv* env = getJniEnv();
    env->CallVoidMethod(mCallbacksObj, method_requestUtcTime);
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    return Status::ok();
}

Status GnssCallbackAidl::gnssRequestLocationCb(const bool independentFromGnss,
                                               const bool isUserEmergency) {
    JNIEnv* env = getJniEnv();
    env->CallVoidMethod(mCallbacksObj, method_requestLocation, boolToJbool(independentFromGnss),
                        boolToJbool(isUserEmergency));
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    return Status::ok();
}

// Implementation of IGnssCallbackHidl

Return<void> GnssCallbackHidl::gnssNameCb(const android::hardware::hidl_string& name) {
    ALOGD("%s: name=%s\n", __func__, name.c_str());

    JNIEnv* env = getJniEnv();
    jstring jstringName = env->NewStringUTF(name.c_str());
    env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareModelName, jstringName);
    if (jstringName) {
        env->DeleteLocalRef(jstringName);
    }
    checkAndClearExceptionFromCallback(env, __FUNCTION__);

    return Void();
}

const char* GnssCallbackHidl::sNmeaString = nullptr;
size_t GnssCallbackHidl::sNmeaStringLength = 0;

template <class T>
Return<void> GnssCallbackHidl::gnssLocationCbImpl(const T& location) {
    JNIEnv* env = getJniEnv();

    jobject jLocation = translateGnssLocation(env, location);

    env->CallVoidMethod(mCallbacksObj, method_reportLocation, boolToJbool(hasLatLong(location)),
                        jLocation);
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    env->DeleteLocalRef(jLocation);
    return Void();
}

Return<void> GnssCallbackHidl::gnssLocationCb(const GnssLocation_V1_0& location) {
    return gnssLocationCbImpl<GnssLocation_V1_0>(location);
}

Return<void> GnssCallbackHidl::gnssLocationCb_2_0(const GnssLocation_V2_0& location) {
    return gnssLocationCbImpl<GnssLocation_V2_0>(location);
}

Return<void> GnssCallbackHidl::gnssStatusCb(const IGnssCallback_V2_0::GnssStatusValue status) {
    JNIEnv* env = getJniEnv();
    env->CallVoidMethod(mCallbacksObj, method_reportStatus, status);
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    return Void();
}

template <>
uint32_t GnssCallbackHidl::getHasBasebandCn0DbHzFlag(
        const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svStatus) {
    return SVID_FLAGS_HAS_BASEBAND_CN0;
}

template <>
uint32_t GnssCallbackHidl::getHasBasebandCn0DbHzFlag(
        const std::vector<IGnssCallbackAidl::GnssSvInfo>& svStatus) {
    return SVID_FLAGS_HAS_BASEBAND_CN0;
}

template <>
double GnssCallbackHidl::getBasebandCn0DbHz(
        const std::vector<IGnssCallbackAidl::GnssSvInfo>& svInfoList, size_t i) {
    return svInfoList[i].basebandCN0DbHz;
}

template <>
double GnssCallbackHidl::getBasebandCn0DbHz(
        const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) {
    return svInfoList[i].basebandCN0DbHz;
}

template <>
uint32_t GnssCallbackHidl::getGnssSvInfoListSize(const IGnssCallback_V1_0::GnssSvStatus& svStatus) {
    return svStatus.numSvs;
}

template <>
uint32_t GnssCallbackHidl::getConstellationType(const IGnssCallback_V1_0::GnssSvStatus& svStatus,
                                                size_t i) {
    return static_cast<uint32_t>(svStatus.gnssSvList.data()[i].constellation);
}

template <>
uint32_t GnssCallbackHidl::getConstellationType(
        const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) {
    return static_cast<uint32_t>(svInfoList[i].v2_0.constellation);
}

template <class T_list, class T_sv_info>
Return<void> GnssCallbackHidl::gnssSvStatusCbImpl(const T_list& svStatus) {
    // In HIDL or AIDL v1, if no listener is registered, do not report svInfoList to the framework.
    if (!isSvStatusRegistered) {
        return Void();
    }

    JNIEnv* env = getJniEnv();

    uint32_t listSize = getGnssSvInfoListSize(svStatus);

    jintArray svidWithFlagArray = env->NewIntArray(listSize);
    jfloatArray cn0Array = env->NewFloatArray(listSize);
    jfloatArray elevArray = env->NewFloatArray(listSize);
    jfloatArray azimArray = env->NewFloatArray(listSize);
    jfloatArray carrierFreqArray = env->NewFloatArray(listSize);
    jfloatArray basebandCn0Array = env->NewFloatArray(listSize);

    jint* svidWithFlags = env->GetIntArrayElements(svidWithFlagArray, 0);
    jfloat* cn0s = env->GetFloatArrayElements(cn0Array, 0);
    jfloat* elev = env->GetFloatArrayElements(elevArray, 0);
    jfloat* azim = env->GetFloatArrayElements(azimArray, 0);
    jfloat* carrierFreq = env->GetFloatArrayElements(carrierFreqArray, 0);
    jfloat* basebandCn0s = env->GetFloatArrayElements(basebandCn0Array, 0);

    /*
     * Read GNSS SV info.
     */
    for (size_t i = 0; i < listSize; ++i) {
        enum ShiftWidth : uint8_t { SVID_SHIFT_WIDTH = 12, CONSTELLATION_TYPE_SHIFT_WIDTH = 8 };

        const T_sv_info& info = getGnssSvInfoOfIndex(svStatus, i);
        svidWithFlags[i] = (info.svid << SVID_SHIFT_WIDTH) |
                (getConstellationType(svStatus, i) << CONSTELLATION_TYPE_SHIFT_WIDTH) |
                static_cast<uint32_t>(info.svFlag);
        cn0s[i] = info.cN0Dbhz;
        elev[i] = info.elevationDegrees;
        azim[i] = info.azimuthDegrees;
        carrierFreq[i] = info.carrierFrequencyHz;
        svidWithFlags[i] |= getHasBasebandCn0DbHzFlag(svStatus);
        basebandCn0s[i] = getBasebandCn0DbHz(svStatus, i);
    }

    env->ReleaseIntArrayElements(svidWithFlagArray, svidWithFlags, 0);
    env->ReleaseFloatArrayElements(cn0Array, cn0s, 0);
    env->ReleaseFloatArrayElements(elevArray, elev, 0);
    env->ReleaseFloatArrayElements(azimArray, azim, 0);
    env->ReleaseFloatArrayElements(carrierFreqArray, carrierFreq, 0);
    env->ReleaseFloatArrayElements(basebandCn0Array, basebandCn0s, 0);

    env->CallVoidMethod(mCallbacksObj, method_reportSvStatus, static_cast<jint>(listSize),
                        svidWithFlagArray, cn0Array, elevArray, azimArray, carrierFreqArray,
                        basebandCn0Array);

    env->DeleteLocalRef(svidWithFlagArray);
    env->DeleteLocalRef(cn0Array);
    env->DeleteLocalRef(elevArray);
    env->DeleteLocalRef(azimArray);
    env->DeleteLocalRef(carrierFreqArray);
    env->DeleteLocalRef(basebandCn0Array);

    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    return Void();
}

Return<void> GnssCallbackHidl::gnssNmeaCb(int64_t timestamp,
                                          const ::android::hardware::hidl_string& nmea) {
    // In HIDL, if no listener is registered, do not report nmea to the framework.
    if (!isNmeaRegistered) {
        return Void();
    }
    JNIEnv* env = getJniEnv();
    /*
     * The Java code will call back to read these values.
     * We do this to avoid creating unnecessary String objects.
     */
    sNmeaString = nmea.c_str();
    sNmeaStringLength = nmea.size();

    env->CallVoidMethod(mCallbacksObj, method_reportNmea, timestamp);
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    return Void();
}

Return<void> GnssCallbackHidl::gnssSetCapabilitesCb(uint32_t capabilities) {
    ALOGD("%s: %du\n", __func__, capabilities);

    JNIEnv* env = getJniEnv();
    env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities,
                        /* isAdrCapabilityKnown= */ false);
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    return Void();
}

Return<void> GnssCallbackHidl::gnssSetCapabilitiesCb_2_0(uint32_t capabilities) {
    return GnssCallbackHidl::gnssSetCapabilitesCb(capabilities);
}

Return<void> GnssCallbackHidl::gnssSetCapabilitiesCb_2_1(uint32_t capabilities) {
    return GnssCallbackHidl::gnssSetCapabilitesCb(capabilities);
}

Return<void> GnssCallbackHidl::gnssAcquireWakelockCb() {
    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME);
    return Void();
}

Return<void> GnssCallbackHidl::gnssReleaseWakelockCb() {
    release_wake_lock(WAKE_LOCK_NAME);
    return Void();
}

Return<void> GnssCallbackHidl::gnssRequestTimeCb() {
    JNIEnv* env = getJniEnv();
    env->CallVoidMethod(mCallbacksObj, method_requestUtcTime);
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    return Void();
}

Return<void> GnssCallbackHidl::gnssRequestLocationCb(const bool independentFromGnss) {
    return GnssCallbackHidl::gnssRequestLocationCb_2_0(independentFromGnss, /* isUserEmergency= */
                                                       false);
}

Return<void> GnssCallbackHidl::gnssRequestLocationCb_2_0(const bool independentFromGnss,
                                                         const bool isUserEmergency) {
    JNIEnv* env = getJniEnv();
    env->CallVoidMethod(mCallbacksObj, method_requestLocation, boolToJbool(independentFromGnss),
                        boolToJbool(isUserEmergency));
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    return Void();
}

Return<void> GnssCallbackHidl::gnssSetSystemInfoCb(const IGnssCallback_V2_0::GnssSystemInfo& info) {
    ALOGD("%s: yearOfHw=%d\n", __func__, info.yearOfHw);

    JNIEnv* env = getJniEnv();
    env->CallVoidMethod(mCallbacksObj, method_setGnssYearOfHardware, info.yearOfHw);
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
    return Void();
}

} // namespace android::gnss