/* Copyright (c) 2019-2020 The Linux Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *     * Neither the name of The Linux Foundation nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#define LOG_TAG "LocSvc_LocationQapi"

#include <loc_pla.h>
#include "log_util.h"
#include "loc_cfg.h"
#include "LocationClientApi.h"
#include "qapi_location.h"
#include <pthread.h>
#include <vector>
#include <string.h>
#include <LocTimer.h>

using namespace std;
using namespace loc_util;
using namespace location_client;
using Runnable = std::function<void()>;

class QapiTimer : public LocTimer {
public:
    QapiTimer() : LocTimer(), mStarted(false) {}
    inline ~QapiTimer() { stop(); }
    inline void set(const time_t waitTimeMs, const Runnable& runable) {
        mWaitTimeInMs = waitTimeMs;
        mRunnable = runable;
    }
    inline void start() {
        mStarted = true;
        LocTimer::start(mWaitTimeInMs, false);
    }
    inline void start(const time_t waitTimeSec) {
        mWaitTimeInMs = waitTimeSec * 1000;
        start();
    }
    inline void stop() {
        if (mStarted) {
            LocTimer::stop();
            mStarted = false;
        }
    }
    inline void restart() { stop(); start(); }
    inline void restart(const time_t waitTimeSec) { stop(); start(waitTimeSec); }
    inline bool isStarted() { return mStarted; }

private:
    // Override
    inline virtual void timeOutCallback() override {
        mStarted = false;
        mRunnable();
    }

private:
    time_t mWaitTimeInMs;
    Runnable mRunnable;
    bool mStarted;

};

static pthread_mutex_t qMutex;
static bool qMutexInitDone = false;
static LocationClientApi* pLocClientApi = nullptr;
static LocationClientApi* pLocPassiveClientApi = nullptr;
static QapiTimer mTimer;
qapi_Location_Callbacks_t   qLocationCallbacks;
static qapi_Location_t qCacheLocation;
static QapiTimer mPassiveTimer;

static qapi_Location_Error_t get_qapi_error(LocationResponse err)
{
    switch (err)
    {
    case LOCATION_RESPONSE_SUCCESS:         return QAPI_LOCATION_ERROR_SUCCESS;
    case LOCATION_RESPONSE_UNKOWN_FAILURE:  return QAPI_LOCATION_ERROR_GENERAL_FAILURE;
    case LOCATION_RESPONSE_NOT_SUPPORTED:   return QAPI_LOCATION_ERROR_NOT_SUPPORTED;
    default:                                return QAPI_LOCATION_ERROR_GENERAL_FAILURE;
    }
}

static int32_t copy_to_user_space_buffer(
    uint8_t* user_space_buffer,
    uint32_t user_space_buffer_size,
    uint8_t* data_ptr,
    uint32_t data_size,
    uint32_t offset) {

    if (offset >= user_space_buffer_size) {
        LOC_LOGe("Invalid offset %d, buffer size %d",
            offset, user_space_buffer_size);
        return -1;
    }

    memcpy((user_space_buffer + offset), data_ptr, data_size);

    return 0;
}

static void print_qLocation_array(qapi_Location_t* qLocationArray, uint32_t length) {

    for (uint32_t i = 0; i < length; i++) {
        LOC_LOGv("LAT: %d.%d LON: %d.%d ALT: %d.%d ALT_MSL: %d.%d",
            (int)qLocationArray[i].latitude,
            (abs((int)(qLocationArray[i].latitude * 100000))) % 100000,
            (int)qLocationArray[i].longitude,
            (abs((int)(qLocationArray[i].longitude * 100000))) % 100000,
            (int)qLocationArray[i].altitude,
            (abs((int)(qLocationArray[i].altitude * 100))) % 100,
            (int)qLocationArray[i].altitudeMeanSeaLevel,
            (abs((int)(qLocationArray[i].altitudeMeanSeaLevel * 100))) % 100);

        LOC_LOGv("SPEED: %d.%d BEAR: %d.%d TIME: 0x%x%x FLAGS: %u",
            (int)qLocationArray[i].speed,
            (abs((int)(qLocationArray[i].speed * 100))) % 100,
            (int)qLocationArray[i].bearing,
            (abs((int)(qLocationArray[i].bearing * 100))) % 100,
            (int)(qLocationArray[i].timestamp >> 32),
            (int)qLocationArray[i].timestamp, qLocationArray[i].flags);

        LOC_LOGv("ACC: %d.%d VERT_ACC: %d.%d SPEED_ACC: %d.%d BEAR_ACC: %d.%d",
            (int)qLocationArray[i].accuracy,
            (abs((int)(qLocationArray[i].accuracy * 100))) % 100,
            (int)qLocationArray[i].verticalAccuracy,
            (abs((int)(qLocationArray[i].verticalAccuracy * 100))) % 100,
            (int)qLocationArray[i].speedAccuracy,
            (abs((int)(qLocationArray[i].speedAccuracy * 100))) % 100,
            (int)qLocationArray[i].bearingAccuracy,
            (abs((int)(qLocationArray[i].bearingAccuracy * 100))) % 100);
    }
}

static void print_qGnssData(qapi_Gnss_Data_t* qGnssData) {

    LOC_LOGv("JAMMER_GPS: %u JAMMER_GLONASS: %u",
        qGnssData->jammerInd[QAPI_GNSS_SV_TYPE_GPS],
        qGnssData->jammerInd[QAPI_GNSS_SV_TYPE_GLONASS]);

    LOC_LOGv("JAMMER_BEIDOU: %u JAMMER_GALILEO: %u",
        qGnssData->jammerInd[QAPI_GNSS_SV_TYPE_BEIDOU],
        qGnssData->jammerInd[QAPI_GNSS_SV_TYPE_GALILEO]);
}

static void location_capabilities_callback(
    LocationCapabilitiesMask capsMask)
{
    LOC_LOGv("location_capabilities_callback!");

    qLocationCallbacks.capabilitiesCb(
        (qapi_Location_Capabilities_Mask_t)capsMask);
}

static void location_response_callback(
    LocationResponse err)
{
    qapi_Location_Error_t qErr;

    qErr = get_qapi_error(err);
    LOC_LOGv("location_response_callback!");

    // hard-code id to 0 for now
    uint32_t id = 0;
    qLocationCallbacks.responseCb(qErr, id);
}

static void location_collective_response_callback(
    vector<std::pair<Geofence, LocationResponse>>& responses)
{
    qapi_Location_Error_t* qLocationErrorArray = NULL;

    // hard-coded
    qLocationErrorArray = new qapi_Location_Error_t[5];
    uint32_t* ids = new uint32_t[5];

    for (int i = 0; i < 5; i++) {
        qLocationErrorArray[i] = QAPI_LOCATION_ERROR_SUCCESS;
    }

    LOC_LOGd("Invoking Collective Response Cb");
    qLocationCallbacks.collectiveResponseCb(
        5, qLocationErrorArray, ids);

    delete[] qLocationErrorArray;
    delete[] ids;
}

static void location_tracking_callback(
    Location location)
{
    qapi_Location_t qLocation = {};
    bool bIsSingleShot = false;

    // first check if location is valid
    if (0 == location.flags) {
        LOC_LOGd("Ignore invalid location");
        return;
    }

    if (mTimer.isStarted()) {
        LOC_LOGd("Timer was started, meaning this is singleshot");
        if (nullptr != pLocClientApi) {
            LOC_LOGd("Stop singleshot session");
            pLocClientApi->stopPositionSession();
        }
        LOC_LOGd("Stop singleshot timer");
        mTimer.stop();
        bIsSingleShot = true;
    }
    qLocation.size = sizeof(qapi_Location_t);
    qLocation.timestamp = location.timestamp;
    qLocation.latitude = location.latitude;
    qLocation.longitude = location.longitude;
    qLocation.altitude = location.altitude;
// TODO
//    qLocation.altitudeMeanSeaLevel = location.altitudeMeanSeaLevel;
    qLocation.speed = location.speed;
    qLocation.bearing = location.bearing;
    qLocation.accuracy = location.horizontalAccuracy;
    qLocation.flags = location.flags;
    qLocation.flags &= ~QAPI_LOCATION_HAS_ALTITUDE_MSL_BIT;
    qLocation.verticalAccuracy = location.verticalAccuracy;
    qLocation.speedAccuracy = location.speedAccuracy;
    qLocation.bearingAccuracy = location.bearingAccuracy;

    print_qLocation_array(&qLocation, 1);
    if (bIsSingleShot) {
        LOC_LOGd("Invoking Singleshot Callback");
        if (qLocationCallbacks.singleShotCb) {
            qLocationCallbacks.singleShotCb(qLocation, QAPI_LOCATION_ERROR_SUCCESS);
        } else {
            LOC_LOGe("No singleshot cb registered");
        }
    } else {
        LOC_LOGd("Invoking Tracking Callback");
        if (qLocationCallbacks.trackingCb) {
            qLocationCallbacks.trackingCb(qLocation);
        }
        else {
            LOC_LOGe("No tracking cb registered");
        }
    }
}

static void gnss_location_tracking_callback(
    GnssLocation gnsslocation)
{
    qapi_Location_t qLocation = {};
    bool bIsSingleShot = false;

    // first check if location is valid
    if (0 == gnsslocation.flags) {
        LOC_LOGd("Ignore invalid location");
        return;
    }

    if (mTimer.isStarted()) {
        LOC_LOGd("Timer was started, meaning this is singleshot");
        if (nullptr != pLocClientApi) {
            LOC_LOGd("Stop singleshot session");
            pLocClientApi->stopPositionSession();
        }
        LOC_LOGd("Stop singleshot timer");
        mTimer.stop();
        bIsSingleShot = true;
    }
    qLocation.size = sizeof(qapi_Location_t);
    qLocation.timestamp = gnsslocation.timestamp;
    qLocation.latitude = gnsslocation.latitude;
    qLocation.longitude = gnsslocation.longitude;
    qLocation.altitude = gnsslocation.altitude;
    qLocation.altitudeMeanSeaLevel = gnsslocation.altitudeMeanSeaLevel;
    qLocation.speed = gnsslocation.speed;
    qLocation.bearing = gnsslocation.bearing;
    qLocation.accuracy = gnsslocation.horizontalAccuracy;
    qLocation.flags = gnsslocation.flags;
    if (gnsslocation.gnssInfoFlags & GNSS_LOCATION_INFO_ALTITUDE_MEAN_SEA_LEVEL_BIT) {
        qLocation.flags |= QAPI_LOCATION_HAS_ALTITUDE_MSL_BIT;
    }
    qLocation.verticalAccuracy = gnsslocation.verticalAccuracy;
    qLocation.speedAccuracy = gnsslocation.speedAccuracy;
    qLocation.bearingAccuracy = gnsslocation.bearingAccuracy;

    print_qLocation_array(&qLocation, 1);
    if (bIsSingleShot) {
        LOC_LOGd("Invoking Singleshot Callback");
        if (qLocationCallbacks.singleShotCb) {
            qLocationCallbacks.singleShotCb(qLocation, QAPI_LOCATION_ERROR_SUCCESS);
        } else {
            LOC_LOGe("No singleshot cb registered");
        }
    } else {
        LOC_LOGd("Invoking Tracking Callback");
        if (qLocationCallbacks.trackingCb) {
            qLocationCallbacks.trackingCb(qLocation);
        }
        else {
            LOC_LOGe("No tracking cb registered");
        }
    }
}

static void loc_passive_capabilities_callback(
    LocationCapabilitiesMask capsMask)
{
    LOC_LOGv("loc_passive_capabilities_callback!");
}

static void loc_passive_tracking_callback(
    GnssLocation gnsslocation)
{
    qCacheLocation.size = sizeof(qapi_Location_t);
    qCacheLocation.timestamp = gnsslocation.timestamp;
    qCacheLocation.latitude = gnsslocation.latitude;
    qCacheLocation.longitude = gnsslocation.longitude;
    qCacheLocation.altitude = gnsslocation.altitude;
    qCacheLocation.altitudeMeanSeaLevel = gnsslocation.altitudeMeanSeaLevel;
    qCacheLocation.speed = gnsslocation.speed;
    qCacheLocation.bearing = gnsslocation.bearing;
    qCacheLocation.accuracy = gnsslocation.horizontalAccuracy;
    qCacheLocation.flags = gnsslocation.flags;
    if (gnsslocation.gnssInfoFlags & GNSS_LOCATION_INFO_ALTITUDE_MEAN_SEA_LEVEL_BIT) {
        qCacheLocation.flags |= QAPI_LOCATION_HAS_ALTITUDE_MSL_BIT;
    }
    qCacheLocation.flags |= QAPI_LOCATION_IS_BEST_AVAIL_POS_BIT;
    qCacheLocation.verticalAccuracy = gnsslocation.verticalAccuracy;
    qCacheLocation.speedAccuracy = gnsslocation.speedAccuracy;
    qCacheLocation.bearingAccuracy = gnsslocation.bearingAccuracy;

    print_qLocation_array(&qCacheLocation, 1);
}

static void loc_passive_response_callback(
    LocationResponse err)
{
    LOC_LOGv("loc_passive_response_callback!");
}

struct MyClientCallbacks {
    CapabilitiesCb capabilitycb;
    ResponseCb responsecb;
    CollectiveResponseCb collectivecb;
    LocationCb locationcb;
};

static MyClientCallbacks gLocationCallbacks = {
    location_capabilities_callback,
    location_response_callback,
    location_collective_response_callback,
    location_tracking_callback,
};

#define LOC_PATH_QAPI_CONF_STR      "/etc/qapi.conf"

const char LOC_PATH_QAPI_CONF[] = LOC_PATH_QAPI_CONF_STR;
static uint32_t gDebug = 0;
static uint32_t gSingleshotTimeout = 0;

static const loc_param_s_type gConfigTable[] =
{
    { "DEBUG_LEVEL", &gDebug, NULL, 'n' }
};

static const loc_param_s_type gQapiConfigTable[] =
{
    { "SINGLESHOT_TIMEOUT", &gSingleshotTimeout, NULL, 'n' }
};

/* Parameters we need in a configuration file specifically for QAPI:
    SINGLESHOT_TIMEOUT
    */

extern "C" {
    qapi_Location_Error_t qapi_Loc_Init(
        qapi_loc_client_id* pClientId,
        const qapi_Location_Callbacks_t* pCallbacks)
    {
        qapi_Location_Error_t retVal =
            QAPI_LOCATION_ERROR_SUCCESS;

        // read configuration file
        UTIL_READ_CONF(LOC_PATH_GPS_CONF, gConfigTable);
        LOC_LOGd("gDebug=%u LOC_PATH_GPS_CONF=%s", gDebug, LOC_PATH_GPS_CONF);

        UTIL_READ_CONF(LOC_PATH_QAPI_CONF, gQapiConfigTable);
        LOC_LOGd("gSingleshotTimeout=%u LOC_PATH_QAPI_CONF=%s", gSingleshotTimeout, LOC_PATH_QAPI_CONF);

        MyClientCallbacks locationCallbacks = gLocationCallbacks;

        LOC_LOGd("qapi_Loc_Init! pCallbacks %p"
            "cap %p res %p col %p trk %p",
            pCallbacks,
            pCallbacks->capabilitiesCb,
            pCallbacks->responseCb,
            pCallbacks->collectiveResponseCb,
            pCallbacks->trackingCb);

        LOC_LOGd("qapi_Loc_Init! pCallbacks %p"
            "bat %p gf %p ss %p gd %p",
            pCallbacks,
            pCallbacks->batchingCb,
            pCallbacks->geofenceBreachCb,
            pCallbacks->singleShotCb,
            pCallbacks->gnssDataCb);

        if (!qMutexInitDone)
        {
            pthread_mutex_init(&qMutex, NULL);
            qMutexInitDone = true;
        }

        /* Input sanity */
        if (NULL == pCallbacks->capabilitiesCb ||
            NULL == pCallbacks->responseCb ||
            NULL == pCallbacks->collectiveResponseCb)
        {
            return QAPI_LOCATION_ERROR_CALLBACK_MISSING;
        }
        /* Don't register callbacks not provided by client */
        if (NULL == pCallbacks->trackingCb) {
            locationCallbacks.locationcb = NULL;
        }
        pthread_mutex_lock(&qMutex);

        do
        {
            qLocationCallbacks = *pCallbacks;
            if (nullptr == pLocClientApi) {
                pLocClientApi = new LocationClientApi(
                    location_capabilities_callback);
                retVal = QAPI_LOCATION_ERROR_SUCCESS;
            } else {
                retVal = QAPI_LOCATION_ERROR_ALREADY_STARTED;
            }
            /* Start passive listener here in order to be able to satisfy
            qapi_Loc_Get_Best_Available_Position call */

            if (nullptr == pLocPassiveClientApi) {
                pLocPassiveClientApi = new LocationClientApi(
                    loc_passive_capabilities_callback);
                retVal = QAPI_LOCATION_ERROR_SUCCESS;
                bool ret;
                memset(&qCacheLocation, 0, sizeof(qCacheLocation));
                qCacheLocation.flags |= QAPI_LOCATION_IS_BEST_AVAIL_POS_BIT;

                GnssReportCbs gnnsReportCbs;
                memset(&gnnsReportCbs, 0, sizeof(gnnsReportCbs));
                gnnsReportCbs.gnssLocationCallback = loc_passive_tracking_callback;

                ret = pLocPassiveClientApi->startPositionSession(
                                    0,  // both 0 means passive listener
                                    gnnsReportCbs,
                                    loc_passive_response_callback);
                if (!ret) {
                    retVal = QAPI_LOCATION_ERROR_GENERAL_FAILURE;
                }
            } else {
                retVal = QAPI_LOCATION_ERROR_ALREADY_STARTED;
            }

            *pClientId = 0;
        } while (0);
        pthread_mutex_unlock(&qMutex);
        return retVal;
    }

    qapi_Location_Error_t qapi_Loc_Deinit(
        qapi_loc_client_id clientId)
    {
        qapi_Location_Error_t retVal =
            QAPI_LOCATION_ERROR_ID_UNKNOWN;

        LOC_LOGd("gDebug=%u LOC_PATH_GPS_CONF=%s", gDebug, LOC_PATH_GPS_CONF);
        LOC_LOGd("qapi_Loc_Deinit client %d", clientId);

        pthread_mutex_lock(&qMutex);
        if (nullptr != pLocClientApi) {
            delete pLocClientApi;
            pLocClientApi = nullptr;
            retVal = QAPI_LOCATION_ERROR_SUCCESS;
        }
        pthread_mutex_unlock(&qMutex);
        return retVal;
    }

    qapi_Location_Error_t qapi_Loc_Set_User_Buffer(
        qapi_loc_client_id clientId,
        uint8_t* pUserBuffer,
        size_t bufferSize)
    {
        return QAPI_LOCATION_ERROR_NOT_SUPPORTED;
    }

    qapi_Location_Error_t qapi_Loc_Start_Tracking(
        qapi_loc_client_id clientId,
        const qapi_Location_Options_t* pOptions,
        uint32_t* pSessionId)
    {
        qapi_Location_Error_t retVal =
            QAPI_LOCATION_ERROR_SUCCESS;

        LOC_LOGd("qapi_Loc_Start_Tracking! pOptions=%p pSessionId=%p clientId=%d",
            pOptions, pSessionId, clientId);

        pthread_mutex_lock(&qMutex);
        do {
            if (NULL == pOptions)
            {
                LOC_LOGe("pOptions NULL");
                retVal = QAPI_LOCATION_ERROR_INVALID_PARAMETER;
                break;
            }

            LOC_LOGd("qapi_Loc_Start_Tracking! minInterval=%d minDistance=%d",
                pOptions->minInterval,
                pOptions->minDistance);

            if (nullptr != pLocClientApi) {
                bool ret;
                ret = pLocClientApi->startPositionSession(
                        pOptions->minInterval,
                        pOptions->minDistance,
                        location_tracking_callback,
                        location_response_callback);
                if (!ret) {
                    retVal = QAPI_LOCATION_ERROR_GENERAL_FAILURE;
                }
            } else {
                retVal = QAPI_LOCATION_ERROR_GENERAL_FAILURE;
            }
        } while (0);
        *pSessionId = 1;
        pthread_mutex_unlock(&qMutex);
        return retVal;
    }

    qapi_Location_Error_t qapi_Loc_Stop_Tracking(
        qapi_loc_client_id clientId,
        uint32_t sessionId)
    {
        qapi_Location_Error_t retVal =
            QAPI_LOCATION_ERROR_SUCCESS;

        LOC_LOGv("qapi_Loc_Stop_Tracking! clientId %d, sessionId %d",
            clientId, sessionId);

        pthread_mutex_lock(&qMutex);

        if (nullptr != pLocClientApi) {
            pLocClientApi->stopPositionSession();
        } else {
            retVal = QAPI_LOCATION_ERROR_GENERAL_FAILURE;
        }
        pthread_mutex_unlock(&qMutex);
        return retVal;
    }

    qapi_Location_Error_t qapi_Loc_Update_Tracking_Options(
        qapi_loc_client_id clientId,
        uint32_t sessionId,
        const qapi_Location_Options_t* pOptions)
    {
        return QAPI_LOCATION_ERROR_NOT_SUPPORTED;
    }

    qapi_Location_Error_t qapi_Loc_Start_Batching(
        qapi_loc_client_id clientId,
        const qapi_Location_Options_t* pOptions,
        uint32_t* pSessionId)
    {
        return QAPI_LOCATION_ERROR_NOT_SUPPORTED;
    }

    qapi_Location_Error_t qapi_Loc_Stop_Batching(
        qapi_loc_client_id clientId,
        uint32_t sessionId)
    {
        return QAPI_LOCATION_ERROR_NOT_SUPPORTED;
    }

    qapi_Location_Error_t qapi_Loc_Update_Batching_Options(
        qapi_loc_client_id clientId,
        uint32_t sessionId,
        const qapi_Location_Options_t* pOptions)
    {
        return QAPI_LOCATION_ERROR_NOT_SUPPORTED;
    }

    qapi_Location_Error_t qapi_Loc_Get_Batched_Locations(
        qapi_loc_client_id clientId,
        uint32_t sessionId,
        size_t count)
    {
        return QAPI_LOCATION_ERROR_NOT_SUPPORTED;
    }

    qapi_Location_Error_t qapi_Loc_Add_Geofences(
        qapi_loc_client_id clientId,
        size_t count,
        const qapi_Geofence_Option_t* pOptions,
        const qapi_Geofence_Info_t* pInfo,
        uint32_t** pIdArray)
    {
        return QAPI_LOCATION_ERROR_NOT_SUPPORTED;
    }

    qapi_Location_Error_t qapi_Loc_Remove_Geofences(
        qapi_loc_client_id clientId,
        size_t count,
        const uint32_t* pIDs)
    {
        return QAPI_LOCATION_ERROR_NOT_SUPPORTED;
    }

    qapi_Location_Error_t qapi_Loc_Modify_Geofences(
        qapi_loc_client_id clientId,
        size_t count,
        const uint32_t* pIDs,
        const qapi_Geofence_Option_t* options)
    {
        return QAPI_LOCATION_ERROR_NOT_SUPPORTED;
    }

    qapi_Location_Error_t qapi_Loc_Pause_Geofences(
        qapi_loc_client_id clientId,
        size_t count,
        const uint32_t* pIDs)
    {
        return QAPI_LOCATION_ERROR_NOT_SUPPORTED;
    }

    qapi_Location_Error_t qapi_Loc_Resume_Geofences(
        qapi_loc_client_id clientId,
        size_t count,
        const uint32_t* pIDs)
    {
        return QAPI_LOCATION_ERROR_NOT_SUPPORTED;
    }

    qapi_Location_Error_t qapi_Loc_Get_Single_Shot(
        qapi_loc_client_id clientId,
        qapi_Location_Power_Level_t powerLevel,
        uint32_t* pSessionId)
    {
        qapi_Location_Error_t retVal =
            QAPI_LOCATION_ERROR_SUCCESS;
        bool bFound = false;
        qapi_Location_Options_t options;

        LOC_LOGd("qapi_Loc_Get_Single_Shot! pSessionId=%p clientId=%d",
            pSessionId, clientId);

        pthread_mutex_lock(&qMutex);
        do {
            if (QAPI_LOCATION_POWER_HIGH != powerLevel &&
                QAPI_LOCATION_POWER_LOW != powerLevel)
            {
                LOC_LOGe("powerLevel is invalid");
                retVal = QAPI_LOCATION_ERROR_INVALID_PARAMETER;
                break;
            }

            LOC_LOGd("qapi_Loc_Get_Single_Shot! powerLevel=%d",
                     powerLevel);

            GnssReportCbs gnnsReportCbs;
            memset(&gnnsReportCbs, 0, sizeof(gnnsReportCbs));
            gnnsReportCbs.gnssLocationCallback = gnss_location_tracking_callback;

            if (nullptr != pLocClientApi) {
                bool ret;
                ret = pLocClientApi->startPositionSession(
                        1000,
                        gnnsReportCbs,
                        location_response_callback);
                if (!ret) {
                    retVal = QAPI_LOCATION_ERROR_GENERAL_FAILURE;
                } else {
                    Runnable timerRunnable = [pLocClientApi] {
                        pLocClientApi->stopPositionSession();
                        if (qLocationCallbacks.singleShotCb) {
                            qapi_Location_t qLocation;
                            qLocationCallbacks.singleShotCb(qLocation, QAPI_LOCATION_ERROR_TIMEOUT);
                        } else {
                            LOC_LOGe("No singleshot cb registered");
                        }
                    };
                    mTimer.set(gSingleshotTimeout, timerRunnable);
                    mTimer.start();
                }
            } else {
                retVal = QAPI_LOCATION_ERROR_GENERAL_FAILURE;
            }
        } while (0);
        *pSessionId = 1;
        pthread_mutex_unlock(&qMutex);
        return retVal;
    }

    qapi_Location_Error_t qapi_Loc_Cancel_Single_Shot(
        qapi_loc_client_id clientId,
        uint32_t sessionId)
    {
        qapi_Location_Error_t retVal =
            QAPI_LOCATION_ERROR_SUCCESS;

        LOC_LOGv("qapi_Loc_Cancel_Single_Shot! clientId %d, sessionId %d",
            clientId, sessionId);

        pthread_mutex_lock(&qMutex);
        if (mTimer.isStarted()) {
            LOC_LOGd("Timer was started, singleshot session in progress");
            if (nullptr != pLocClientApi) {
                LOC_LOGd("Stop singleshot session");
                pLocClientApi->stopPositionSession();
            }
            LOC_LOGd("Stop singleshot timer");
            mTimer.stop();
        } else {
            LOC_LOGd("No singleshot session in progress!");
        }
        pthread_mutex_unlock(&qMutex);
        return retVal;
    }

    qapi_Location_Error_t qapi_Loc_Start_Get_Gnss_Data(
        qapi_loc_client_id clientId,
        uint32_t* pSessionId)
    {
        return QAPI_LOCATION_ERROR_NOT_SUPPORTED;
    }

    qapi_Location_Error_t qapi_Loc_Stop_Get_Gnss_Data(
        qapi_loc_client_id clientId,
        uint32_t sessionId)
    {
        return QAPI_LOCATION_ERROR_NOT_SUPPORTED;
    }

    qapi_Location_Error_t qapi_Loc_Get_Best_Available_Position(
        qapi_loc_client_id clientId,
        uint32_t* pSessionId)
    {
        qapi_Location_Error_t retVal = QAPI_LOCATION_ERROR_SUCCESS;

        LOC_LOGv("qapi_Loc_Get_Best_Available_Position! clientId %d", clientId);
        pthread_mutex_lock(&qMutex);
        do {
            if (nullptr != pLocClientApi) {
                Runnable timerRunnable = [] {
                    if (qLocationCallbacks.singleShotCb) {
                        qLocationCallbacks.singleShotCb(qCacheLocation, QAPI_LOCATION_ERROR_SUCCESS);
                    } else {
                        LOC_LOGe("No singleshot cb registered");
                    }
                };
                mPassiveTimer.set(500, timerRunnable);
                mPassiveTimer.start();

            } else {
                retVal = QAPI_LOCATION_ERROR_GENERAL_FAILURE;
            }
        } while (0);

        pthread_mutex_unlock(&qMutex);
        return retVal;
    }

} /* extern "C" */
