/* Copyright (c) 2017 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.
 */

#ifndef LOCATION_API_CLINET_BASE_H
#define LOCATION_API_CLINET_BASE_H

#include <stdint.h>
#include <stdlib.h>
#include <pthread.h>
#include <queue>
#include <map>

#include "LocationAPI.h"
#include <loc_pla.h>
#include <log_util.h>

enum SESSION_MODE {
    SESSION_MODE_NONE = 0,
    SESSION_MODE_ON_FULL,
    SESSION_MODE_ON_FIX,
    SESSION_MODE_ON_TRIP_COMPLETED
};

enum REQUEST_TYPE {
    REQUEST_TRACKING = 0,
    REQUEST_SESSION,
    REQUEST_GEOFENCE,
    REQUEST_NIRESPONSE,
    REQUEST_MAX,
};

enum CTRL_REQUEST_TYPE {
    CTRL_REQUEST_DELETEAIDINGDATA = 0,
    CTRL_REQUEST_CONTROL,
    CTRL_REQUEST_CONFIG,
    CTRL_REQUEST_MAX,
};

class LocationAPIClientBase;

class LocationAPIRequest {
public:
    LocationAPIRequest() {}
    virtual ~LocationAPIRequest() {}
    virtual void onResponse(LocationError /*error*/, uint32_t /*id*/) {}
    virtual void onCollectiveResponse(
            size_t /*count*/, LocationError* /*errors*/, uint32_t* /*ids*/) {}
};

class RequestQueue {
public:
    RequestQueue(): mSession(0) {
    }
    virtual ~RequestQueue() {
        reset(0);
    }
    void inline setSession(uint32_t session) { mSession = session; }
    void reset(uint32_t session) {
        LocationAPIRequest* request = nullptr;
        while (!mQueue.empty()) {
            request = mQueue.front();
            mQueue.pop();
            delete request;
        }
        mSession = session;
    }
    void push(LocationAPIRequest* request) {
        mQueue.push(request);
    }
    LocationAPIRequest* pop() {
        LocationAPIRequest* request = nullptr;
        if (!mQueue.empty()) {
            request = mQueue.front();
            mQueue.pop();
        }
        return request;
    }
    uint32_t getSession() { return mSession; }
private:
    uint32_t mSession;
    std::queue<LocationAPIRequest*> mQueue;
};

class LocationAPIControlClient {
public:
    LocationAPIControlClient();
    virtual ~LocationAPIControlClient();
    LocationAPIControlClient(const LocationAPIControlClient&) = delete;
    LocationAPIControlClient& operator=(const LocationAPIControlClient&) = delete;

    LocationAPIRequest* getRequestBySession(uint32_t session);

    // LocationControlAPI
    uint32_t locAPIGnssDeleteAidingData(GnssAidingData& data);
    uint32_t locAPIEnable(LocationTechnologyType techType);
    void locAPIDisable();
    uint32_t locAPIGnssUpdateConfig(GnssConfig config);

    // callbacks
    void onCtrlResponseCb(LocationError error, uint32_t id);
    void onCtrlCollectiveResponseCb(size_t count, LocationError* errors, uint32_t* ids);

    inline virtual void onGnssDeleteAidingDataCb(LocationError /*error*/) {}
    inline virtual void onEnableCb(LocationError /*error*/) {}
    inline virtual void onDisableCb(LocationError /*error*/) {}
    inline virtual void onGnssUpdateConfigCb(
            size_t /*count*/, LocationError* /*errors*/, uint32_t* /*ids*/) {}

    class GnssDeleteAidingDataRequest : public LocationAPIRequest {
    public:
        GnssDeleteAidingDataRequest(LocationAPIControlClient& API) : mAPI(API) {}
        inline void onResponse(LocationError error, uint32_t /*id*/) {
            mAPI.onGnssDeleteAidingDataCb(error);
        }
        LocationAPIControlClient& mAPI;
    };

    class EnableRequest : public LocationAPIRequest {
    public:
        EnableRequest(LocationAPIControlClient& API) : mAPI(API) {}
        inline void onResponse(LocationError error, uint32_t /*id*/) {
            mAPI.onEnableCb(error);
        }
        LocationAPIControlClient& mAPI;
    };

    class DisableRequest : public LocationAPIRequest {
    public:
        DisableRequest(LocationAPIControlClient& API) : mAPI(API) {}
        inline void onResponse(LocationError error, uint32_t /*id*/) {
            mAPI.onDisableCb(error);
        }
        LocationAPIControlClient& mAPI;
    };

    class GnssUpdateConfigRequest : public LocationAPIRequest {
    public:
        GnssUpdateConfigRequest(LocationAPIControlClient& API) : mAPI(API) {}
        inline void onCollectiveResponse(size_t count, LocationError* errors, uint32_t* ids) {
            mAPI.onGnssUpdateConfigCb(count, errors, ids);
        }
        LocationAPIControlClient& mAPI;
    };

private:
    pthread_mutex_t mMutex;
    LocationControlAPI* mLocationControlAPI;
    RequestQueue mRequestQueues[CTRL_REQUEST_MAX];
    bool mEnabled;
    GnssConfig mConfig;
};

class LocationAPIClientBase {
public:
    LocationAPIClientBase();
    virtual ~LocationAPIClientBase();
    LocationAPIClientBase(const LocationAPIClientBase&) = delete;
    LocationAPIClientBase& operator=(const LocationAPIClientBase&) = delete;

    void locAPISetCallbacks(LocationCallbacks& locationCallbacks);
    void removeSession(uint32_t session);
    LocationAPIRequest* getRequestBySession(uint32_t session);

    // LocationAPI
    uint32_t locAPIStartTracking(LocationOptions& options);
    void locAPIStopTracking();
    void locAPIUpdateTrackingOptions(LocationOptions& options);

    int32_t locAPIGetBatchSize();
    uint32_t locAPIStartSession(uint32_t id, uint32_t sessionMode,
            LocationOptions& options);
    uint32_t locAPIStopSession(uint32_t id);
    uint32_t locAPIUpdateSessionOptions(uint32_t id, uint32_t sessionMode,
            LocationOptions& options);
    uint32_t locAPIGetBatchedLocations(uint32_t id, size_t count);

    uint32_t locAPIAddGeofences(size_t count, uint32_t* ids,
            GeofenceOption* options, GeofenceInfo* data);
    void locAPIRemoveGeofences(size_t count, uint32_t* ids);
    void locAPIModifyGeofences(size_t count, uint32_t* ids, GeofenceOption* options);
    void locAPIPauseGeofences(size_t count, uint32_t* ids);
    void locAPIResumeGeofences(size_t count, uint32_t* ids, GeofenceBreachTypeMask* mask);
    void locAPIRemoveAllGeofences();

    void locAPIGnssNiResponse(uint32_t id, GnssNiResponse response);

    // callbacks
    void onResponseCb(LocationError error, uint32_t id);
    void onCollectiveResponseCb(size_t count, LocationError* errors, uint32_t* ids);

    void beforeGeofenceBreachCb(GeofenceBreachNotification geofenceBreachNotification);

    inline virtual void onCapabilitiesCb(LocationCapabilitiesMask /*capabilitiesMask*/) {}
    inline virtual void onGnssNmeaCb(GnssNmeaNotification /*gnssNmeaNotification*/) {}
    inline virtual void onGnssMeasurementsCb(
            GnssMeasurementsNotification /*gnssMeasurementsNotification*/) {}

    inline virtual void onTrackingCb(Location /*location*/) {}
    inline virtual void onGnssSvCb(GnssSvNotification /*gnssSvNotification*/) {}
    inline virtual void onStartTrackingCb(LocationError /*error*/) {}
    inline virtual void onStopTrackingCb(LocationError /*error*/) {}
    inline virtual void onUpdateTrackingOptionsCb(LocationError /*error*/) {}

    inline virtual void onGnssLocationInfoCb(
            GnssLocationInfoNotification /*gnssLocationInfoNotification*/) {}

    inline virtual void onBatchingCb(size_t /*count*/, Location* /*location*/,
            BatchingOptions /*batchingOptions*/) {}
    inline virtual void onBatchingStatusCb(BatchingStatusInfo /*batchingStatus*/,
            std::list<uint32_t> &/*listOfCompletedTrips*/) {}
    void beforeBatchingStatusCb(BatchingStatusInfo batchStatus,
            std::list<uint32_t> & tripCompletedList);
    inline virtual void onStartBatchingCb(LocationError /*error*/) {}
    inline virtual void onStopBatchingCb(LocationError /*error*/) {}
    inline virtual void onUpdateBatchingOptionsCb(LocationError /*error*/) {}
    inline virtual void onGetBatchedLocationsCb(LocationError /*error*/) {}

    inline virtual void onGeofenceBreachCb(
            GeofenceBreachNotification /*geofenceBreachNotification*/) {}
    inline virtual void onGeofenceStatusCb(
            GeofenceStatusNotification /*geofenceStatusNotification*/) {}
    inline virtual void onAddGeofencesCb(
            size_t /*count*/, LocationError* /*errors*/, uint32_t* /*ids*/) {}
    inline virtual void onRemoveGeofencesCb(
            size_t /*count*/, LocationError* /*errors*/, uint32_t* /*ids*/) {}
    inline virtual void onModifyGeofencesCb(
            size_t /*count*/, LocationError* /*errors*/, uint32_t* /*ids*/) {}
    inline virtual void onPauseGeofencesCb(
            size_t /*count*/, LocationError* /*errors*/, uint32_t* /*ids*/) {}
    inline virtual void onResumeGeofencesCb(
            size_t /*count*/, LocationError* /*errors*/, uint32_t* /*ids*/) {}

    inline virtual void onGnssNiCb(uint32_t /*id*/, GnssNiNotification /*gnssNiNotification*/) {}
    inline virtual void onGnssNiResponseCb(LocationError /*error*/) {}

private:
    // private inner classes
    typedef struct {
        uint32_t id;
        uint32_t trackingSession;
        uint32_t batchingSession;
        uint32_t sessionMode;
    } SessionEntity;

    template<typename T>
    class BiDict {
    public:
        BiDict() {
            pthread_mutex_init(&mBiDictMutex, nullptr);
        }
        virtual ~BiDict() {
            pthread_mutex_destroy(&mBiDictMutex);
        }
        bool hasId(uint32_t id) {
            pthread_mutex_lock(&mBiDictMutex);
            bool ret = (mForwardMap.find(id) != mForwardMap.end());
            pthread_mutex_unlock(&mBiDictMutex);
            return ret;
        }
        bool hasSession(uint32_t session) {
            pthread_mutex_lock(&mBiDictMutex);
            bool ret = (mBackwardMap.find(session) != mBackwardMap.end());
            pthread_mutex_unlock(&mBiDictMutex);
            return ret;
        }
        void set(uint32_t id, uint32_t session, T& ext) {
            pthread_mutex_lock(&mBiDictMutex);
            mForwardMap[id] = session;
            mBackwardMap[session] = id;
            mExtMap[session] = ext;
            pthread_mutex_unlock(&mBiDictMutex);
        }
        void clear() {
            pthread_mutex_lock(&mBiDictMutex);
            mForwardMap.clear();
            mBackwardMap.clear();
            mExtMap.clear();
            pthread_mutex_unlock(&mBiDictMutex);
        }
        void rmById(uint32_t id) {
            pthread_mutex_lock(&mBiDictMutex);
            mBackwardMap.erase(mForwardMap[id]);
            mExtMap.erase(mForwardMap[id]);
            mForwardMap.erase(id);
            pthread_mutex_unlock(&mBiDictMutex);
        }
        void rmBySession(uint32_t session) {
            pthread_mutex_lock(&mBiDictMutex);
            mForwardMap.erase(mBackwardMap[session]);
            mBackwardMap.erase(session);
            mExtMap.erase(session);
            pthread_mutex_unlock(&mBiDictMutex);
        }
        uint32_t getId(uint32_t session) {
            pthread_mutex_lock(&mBiDictMutex);
            uint32_t ret = 0;
            auto it = mBackwardMap.find(session);
            if (it != mBackwardMap.end()) {
                ret = it->second;
            }
            pthread_mutex_unlock(&mBiDictMutex);
            return ret;
        }
        uint32_t getSession(uint32_t id) {
            pthread_mutex_lock(&mBiDictMutex);
            uint32_t ret = 0;
            auto it = mForwardMap.find(id);
            if (it != mForwardMap.end()) {
                ret = it->second;
            }
            pthread_mutex_unlock(&mBiDictMutex);
            return ret;
        }
        T getExtById(uint32_t id) {
            pthread_mutex_lock(&mBiDictMutex);
            T ret;
            memset(&ret, 0, sizeof(T));
            uint32_t session = mForwardMap[id];
            if (session > 0) {
                auto it = mExtMap.find(session);
                if (it != mExtMap.end()) {
                    ret = it->second;
                }
            }
            pthread_mutex_unlock(&mBiDictMutex);
            return ret;
        }
        T getExtBySession(uint32_t session) {
            pthread_mutex_lock(&mBiDictMutex);
            T ret;
            memset(&ret, 0, sizeof(T));
            auto it = mExtMap.find(session);
            if (it != mExtMap.end()) {
                ret = it->second;
            }
            pthread_mutex_unlock(&mBiDictMutex);
            return ret;
        }
        std::vector<uint32_t> getAllSessions() {
            std::vector<uint32_t> ret;
            pthread_mutex_lock(&mBiDictMutex);
            for (auto it = mBackwardMap.begin(); it != mBackwardMap.end(); it++) {
                ret.push_back(it->first);
            }
            pthread_mutex_unlock(&mBiDictMutex);
            return ret;
        }
    private:
        pthread_mutex_t mBiDictMutex;
        // mForwarMap mapping id->session
        std::map<uint32_t, uint32_t> mForwardMap;
        // mBackwardMap mapping session->id
        std::map<uint32_t, uint32_t> mBackwardMap;
        // mExtMap mapping session->ext
        std::map<uint32_t, T> mExtMap;
    };

    class StartTrackingRequest : public LocationAPIRequest {
    public:
        StartTrackingRequest(LocationAPIClientBase& API) : mAPI(API) {}
        inline void onResponse(LocationError error, uint32_t id) {
            if (error != LOCATION_ERROR_SUCCESS) {
                mAPI.removeSession(id);
            }
            mAPI.onStartTrackingCb(error);
        }
        LocationAPIClientBase& mAPI;
    };

    class StopTrackingRequest : public LocationAPIRequest {
    public:
        StopTrackingRequest(LocationAPIClientBase& API) : mAPI(API) {}
        inline void onResponse(LocationError error, uint32_t id) {
            mAPI.onStopTrackingCb(error);
            if (error == LOCATION_ERROR_SUCCESS) {
                mAPI.removeSession(id);
            }
        }
        LocationAPIClientBase& mAPI;
    };

    class UpdateTrackingOptionsRequest : public LocationAPIRequest {
    public:
        UpdateTrackingOptionsRequest(LocationAPIClientBase& API) : mAPI(API) {}
        inline void onResponse(LocationError error, uint32_t /*id*/) {
            mAPI.onUpdateTrackingOptionsCb(error);
        }
        LocationAPIClientBase& mAPI;
    };

    class StartBatchingRequest : public LocationAPIRequest {
    public:
        StartBatchingRequest(LocationAPIClientBase& API) : mAPI(API) {}
        inline void onResponse(LocationError error, uint32_t id) {
            if (error != LOCATION_ERROR_SUCCESS) {
                mAPI.removeSession(id);
            }
            mAPI.onStartBatchingCb(error);
        }
        LocationAPIClientBase& mAPI;
    };

    class StopBatchingRequest : public LocationAPIRequest {
    public:
        StopBatchingRequest(LocationAPIClientBase& API) : mAPI(API) {}
        inline void onResponse(LocationError error, uint32_t id) {
            mAPI.onStopBatchingCb(error);
            if (error == LOCATION_ERROR_SUCCESS) {
                mAPI.removeSession(id);
            }
        }
        LocationAPIClientBase& mAPI;
    };

    class UpdateBatchingOptionsRequest : public LocationAPIRequest {
    public:
        UpdateBatchingOptionsRequest(LocationAPIClientBase& API) : mAPI(API) {}
        inline void onResponse(LocationError error, uint32_t /*id*/) {
            mAPI.onUpdateBatchingOptionsCb(error);
        }
        LocationAPIClientBase& mAPI;
    };

    class GetBatchedLocationsRequest : public LocationAPIRequest {
    public:
        GetBatchedLocationsRequest(LocationAPIClientBase& API) : mAPI(API) {}
        inline void onResponse(LocationError error, uint32_t /*id*/) {
            mAPI.onGetBatchedLocationsCb(error);
        }
        LocationAPIClientBase& mAPI;
    };

    class AddGeofencesRequest : public LocationAPIRequest {
    public:
        AddGeofencesRequest(LocationAPIClientBase& API) : mAPI(API) {}
        inline void onCollectiveResponse(size_t count, LocationError* errors, uint32_t* sessions) {
            uint32_t *ids = (uint32_t*)malloc(sizeof(uint32_t) * count);
            for (size_t i = 0; i < count; i++) {
                ids[i] = mAPI.mGeofenceBiDict.getId(sessions[i]);
            }
            mAPI.onAddGeofencesCb(count, errors, ids);
            free(ids);
        }
        LocationAPIClientBase& mAPI;
    };

    class RemoveGeofencesRequest : public LocationAPIRequest {
    public:
        RemoveGeofencesRequest(LocationAPIClientBase& API,
                               BiDict<GeofenceBreachTypeMask>* removedGeofenceBiDict) :
                               mAPI(API), mRemovedGeofenceBiDict(removedGeofenceBiDict) {}
        inline void onCollectiveResponse(size_t count, LocationError* errors, uint32_t* sessions) {
            if (nullptr != mRemovedGeofenceBiDict) {
                uint32_t *ids = (uint32_t*)malloc(sizeof(uint32_t) * count);
                for (size_t i = 0; i < count; i++) {
                    ids[i] = mRemovedGeofenceBiDict->getId(sessions[i]);
                }
                mAPI.onRemoveGeofencesCb(count, errors, ids);
                free(ids);
                delete(mRemovedGeofenceBiDict);
            } else {
                LOC_LOGE("%s:%d] Unable to access removed geofences data.", __FUNCTION__, __LINE__);
            }
        }
        LocationAPIClientBase& mAPI;
        BiDict<GeofenceBreachTypeMask>* mRemovedGeofenceBiDict;
    };

    class ModifyGeofencesRequest : public LocationAPIRequest {
    public:
        ModifyGeofencesRequest(LocationAPIClientBase& API) : mAPI(API) {}
        inline void onCollectiveResponse(size_t count, LocationError* errors, uint32_t* sessions) {
            uint32_t *ids = (uint32_t*)malloc(sizeof(uint32_t) * count);
            for (size_t i = 0; i < count; i++) {
                ids[i] = mAPI.mGeofenceBiDict.getId(sessions[i]);
            }
            mAPI.onModifyGeofencesCb(count, errors, ids);
            free(ids);
        }
        LocationAPIClientBase& mAPI;
    };

    class PauseGeofencesRequest : public LocationAPIRequest {
    public:
        PauseGeofencesRequest(LocationAPIClientBase& API) : mAPI(API) {}
        inline void onCollectiveResponse(size_t count, LocationError* errors, uint32_t* sessions) {
            uint32_t *ids = (uint32_t*)malloc(sizeof(uint32_t) * count);
            for (size_t i = 0; i < count; i++) {
                ids[i] = mAPI.mGeofenceBiDict.getId(sessions[i]);
            }
            mAPI.onPauseGeofencesCb(count, errors, ids);
            free(ids);
        }
        LocationAPIClientBase& mAPI;
    };

    class ResumeGeofencesRequest : public LocationAPIRequest {
    public:
        ResumeGeofencesRequest(LocationAPIClientBase& API) : mAPI(API) {}
        inline void onCollectiveResponse(size_t count, LocationError* errors, uint32_t* sessions) {
            uint32_t *ids = (uint32_t*)malloc(sizeof(uint32_t) * count);
            for (size_t i = 0; i < count; i++) {
                ids[i] = mAPI.mGeofenceBiDict.getId(sessions[i]);
            }
            mAPI.onResumeGeofencesCb(count, errors, ids);
            free(ids);
        }
        LocationAPIClientBase& mAPI;
    };

    class GnssNiResponseRequest : public LocationAPIRequest {
    public:
        GnssNiResponseRequest(LocationAPIClientBase& API) : mAPI(API) {}
        inline void onResponse(LocationError error, uint32_t /*id*/) {
            mAPI.onGnssNiResponseCb(error);
        }
        LocationAPIClientBase& mAPI;
    };

private:
    pthread_mutex_t mMutex;

    geofenceBreachCallback mGeofenceBreachCallback;
    batchingStatusCallback mBatchingStatusCallback;

    LocationAPI* mLocationAPI;

    RequestQueue mRequestQueues[REQUEST_MAX];
    BiDict<GeofenceBreachTypeMask> mGeofenceBiDict;
    BiDict<SessionEntity> mSessionBiDict;
    int32_t mBatchSize;
    bool mTracking;
};

#endif /* LOCATION_API_CLINET_BASE_H */
