/* Copyright (c) 2013-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_GeofenceAdapter"

#include <GeofenceAdapter.h>
#include "loc_log.h"
#include <log_util.h>
#include <string>

using namespace loc_core;

GeofenceAdapter::GeofenceAdapter() :
    LocAdapterBase(0,
                   LocContext::getLocContext(LocContext::mLocationHalName),
                   true /*isMaster*/)
{
    LOC_LOGD("%s]: Constructor", __func__);
}

void
GeofenceAdapter::stopClientSessions(LocationAPI* client)
{
    LOC_LOGD("%s]: client %p", __func__, client);


    for (auto it = mGeofenceIds.begin(); it != mGeofenceIds.end();) {
        uint32_t hwId = it->second;
        GeofenceKey key(it->first);
        if (client == key.client) {
            it = mGeofenceIds.erase(it);
            mLocApi->removeGeofence(hwId, key.id,
                    new LocApiResponse(*getContext(),
                    [this, hwId] (LocationError err) {
                if (LOCATION_ERROR_SUCCESS == err) {
                    auto it2 = mGeofences.find(hwId);
                    if (it2 != mGeofences.end()) {
                        mGeofences.erase(it2);
                    } else {
                        LOC_LOGE("%s]:geofence item to erase not found. hwId %u", __func__, hwId);
                    }
                }
            }));
            continue;
        }
        ++it; // increment only when not erasing an iterator
    }

}

void
GeofenceAdapter::updateClientsEventMask()
{
    LOC_API_ADAPTER_EVENT_MASK_T mask = 0;
    for (auto it=mClientData.begin(); it != mClientData.end(); ++it) {
        if (it->second.geofenceBreachCb != nullptr) {
            mask |= LOC_API_ADAPTER_BIT_BATCHED_GENFENCE_BREACH_REPORT;
            mask |= LOC_API_ADAPTER_BIT_REPORT_GENFENCE_DWELL;
        }
        if (it->second.geofenceStatusCb != nullptr) {
            mask |= LOC_API_ADAPTER_BIT_GEOFENCE_GEN_ALERT;
        }
    }
    updateEvtMask(mask, LOC_REGISTRATION_MASK_SET);
}

LocationError
GeofenceAdapter::getHwIdFromClient(LocationAPI* client, uint32_t clientId, uint32_t& hwId)
{
    GeofenceKey key(client, clientId);
    auto it = mGeofenceIds.find(key);
    if (it != mGeofenceIds.end()) {
        hwId = it->second;
        return LOCATION_ERROR_SUCCESS;
    }
    return LOCATION_ERROR_ID_UNKNOWN;
}

LocationError
GeofenceAdapter::getGeofenceKeyFromHwId(uint32_t hwId, GeofenceKey& key)
{
    auto it = mGeofences.find(hwId);
    if (it != mGeofences.end()) {
        key = it->second.key;
        return LOCATION_ERROR_SUCCESS;
    }
    return LOCATION_ERROR_ID_UNKNOWN;
}

void
GeofenceAdapter::handleEngineUpEvent()
{
    struct MsgSSREvent : public LocMsg {
        GeofenceAdapter& mAdapter;
        inline MsgSSREvent(GeofenceAdapter& adapter) :
            LocMsg(),
            mAdapter(adapter) {}
        virtual void proc() const {
            mAdapter.setEngineCapabilitiesKnown(true);
            mAdapter.broadcastCapabilities(mAdapter.getCapabilities());
            mAdapter.restartGeofences();
            for (auto msg: mAdapter.mPendingMsgs) {
                mAdapter.sendMsg(msg);
            }
            mAdapter.mPendingMsgs.clear();
        }
    };

    sendMsg(new MsgSSREvent(*this));
}

void
GeofenceAdapter::restartGeofences()
{
    if (mGeofences.empty()) {
        return;
    }

    GeofencesMap oldGeofences(mGeofences);
    mGeofences.clear();
    mGeofenceIds.clear();

    for (auto it = oldGeofences.begin(); it != oldGeofences.end(); it++) {
        GeofenceObject object = it->second;
        GeofenceOption options = {sizeof(GeofenceOption),
                                   object.breachMask,
                                   object.responsiveness,
                                   object.dwellTime};
        GeofenceInfo info = {sizeof(GeofenceInfo),
                             object.latitude,
                             object.longitude,
                             object.radius};
        mLocApi->addGeofence(object.key.id,
                              options,
                              info,
                              new LocApiResponseData<LocApiGeofenceData>(*getContext(),
                [this, object, options, info] (LocationError err, LocApiGeofenceData data) {
            if (LOCATION_ERROR_SUCCESS == err) {
                if (true == object.paused) {
                    mLocApi->pauseGeofence(data.hwId, object.key.id,
                            new LocApiResponse(*getContext(), [] (LocationError err ) {}));
                }
                saveGeofenceItem(object.key.client, object.key.id, data.hwId, options, info);
            }
        }));
    }
}

void
GeofenceAdapter::reportResponse(LocationAPI* client, size_t count, LocationError* errs,
        uint32_t* ids)
{
    IF_LOC_LOGD {
        std::string idsString = "[";
        std::string errsString = "[";
        if (NULL != ids && NULL != errs) {
            for (size_t i=0; i < count; ++i) {
                idsString += std::to_string(ids[i]) + " ";
                errsString += std::to_string(errs[i]) + " ";
            }
        }
        idsString += "]";
        errsString += "]";

        LOC_LOGD("%s]: client %p ids %s errs %s",
                 __func__, client, idsString.c_str(), errsString.c_str());
    }

    auto it = mClientData.find(client);
    if (it != mClientData.end() && it->second.collectiveResponseCb != nullptr) {
        it->second.collectiveResponseCb(count, errs, ids);
    } else {
        LOC_LOGE("%s]: client %p response not found in info", __func__, client);
    }
}

uint32_t*
GeofenceAdapter::addGeofencesCommand(LocationAPI* client, size_t count, GeofenceOption* options,
        GeofenceInfo* infos)
{
    LOC_LOGD("%s]: client %p count %zu", __func__, client, count);

    struct MsgAddGeofences : public LocMsg {
        GeofenceAdapter& mAdapter;
        LocApiBase& mApi;
        LocationAPI* mClient;
        size_t mCount;
        uint32_t* mIds;
        GeofenceOption* mOptions;
        GeofenceInfo* mInfos;
        inline MsgAddGeofences(GeofenceAdapter& adapter,
                               LocApiBase& api,
                               LocationAPI* client,
                               size_t count,
                               uint32_t* ids,
                               GeofenceOption* options,
                               GeofenceInfo* infos) :
            LocMsg(),
            mAdapter(adapter),
            mApi(api),
            mClient(client),
            mCount(count),
            mIds(ids),
            mOptions(options),
            mInfos(infos) {}
        inline virtual void proc() const {
            LocationError* errs = new LocationError[mCount];
            if (nullptr == errs) {
                LOC_LOGE("%s]: new failed to allocate errs", __func__);
                return;
            }
            for (size_t i=0; i < mCount; ++i) {
                if (NULL == mIds || NULL == mOptions || NULL == mInfos) {
                    errs[i] = LOCATION_ERROR_INVALID_PARAMETER;
                } else {
                    mApi.addToCallQueue(new LocApiResponse(*mAdapter.getContext(),
                            [&mAdapter = mAdapter, mCount = mCount, mClient = mClient,
                            mOptions = mOptions, mInfos = mInfos, mIds = mIds, &mApi = mApi,
                            errs, i] (LocationError err ) {
                        mApi.addGeofence(mIds[i], mOptions[i], mInfos[i],
                        new LocApiResponseData<LocApiGeofenceData>(*mAdapter.getContext(),
                        [&mAdapter = mAdapter, mOptions = mOptions, mClient = mClient,
                        mCount = mCount, mIds = mIds, mInfos = mInfos, errs, i]
                        (LocationError err, LocApiGeofenceData data) {
                            if (LOCATION_ERROR_SUCCESS == err) {
                                mAdapter.saveGeofenceItem(mClient,
                                mIds[i],
                                data.hwId,
                                mOptions[i],
                                mInfos[i]);
                            }
                            errs[i] = err;

                            // Send aggregated response on last item and cleanup
                            if (i == mCount-1) {
                                mAdapter.reportResponse(mClient, mCount, errs, mIds);
                                delete[] errs;
                                delete[] mIds;
                                delete[] mOptions;
                                delete[] mInfos;
                            }
                        }));
                    }));
                }
            }
        }
    };

    if (0 == count) {
        return NULL;
    }
    uint32_t* ids = new uint32_t[count];
    if (nullptr == ids) {
        LOC_LOGE("%s]: new failed to allocate ids", __func__);
        return NULL;
    }
    if (NULL != ids) {
        for (size_t i=0; i < count; ++i) {
            ids[i] = generateSessionId();
        }
    }
    GeofenceOption* optionsCopy;
    if (options == NULL) {
        optionsCopy = NULL;
    } else {
        optionsCopy = new GeofenceOption[count];
        if (nullptr == optionsCopy) {
            LOC_LOGE("%s]: new failed to allocate optionsCopy", __func__);
            return NULL;
        }
        COPY_IF_NOT_NULL(optionsCopy, options, count);
    }
    GeofenceInfo* infosCopy;
    if (infos == NULL) {
        infosCopy = NULL;
    } else {
        infosCopy = new GeofenceInfo[count];
        if (nullptr == infosCopy) {
            LOC_LOGE("%s]: new failed to allocate infosCopy", __func__);
            return NULL;
        }
        COPY_IF_NOT_NULL(infosCopy, infos, count);
    }

    sendMsg(new MsgAddGeofences(*this, *mLocApi, client, count, ids, optionsCopy, infosCopy));
    return ids;
}

void
GeofenceAdapter::removeGeofencesCommand(LocationAPI* client, size_t count, uint32_t* ids)
{
    LOC_LOGD("%s]: client %p count %zu", __func__, client, count);

    struct MsgRemoveGeofences : public LocMsg {
        GeofenceAdapter& mAdapter;
        LocApiBase& mApi;
        LocationAPI* mClient;
        size_t mCount;
        uint32_t* mIds;
        inline MsgRemoveGeofences(GeofenceAdapter& adapter,
                                  LocApiBase& api,
                                  LocationAPI* client,
                                  size_t count,
                                  uint32_t* ids) :
            LocMsg(),
            mAdapter(adapter),
            mApi(api),
            mClient(client),
            mCount(count),
            mIds(ids) {}
        inline virtual void proc() const  {
            LocationError* errs = new LocationError[mCount];
            if (nullptr == errs) {
                LOC_LOGE("%s]: new failed to allocate errs", __func__);
                return;
            }
            for (size_t i=0; i < mCount; ++i) {
                mApi.addToCallQueue(new LocApiResponse(*mAdapter.getContext(),
                        [&mAdapter = mAdapter, mCount = mCount, mClient = mClient, mIds = mIds,
                        &mApi = mApi, errs, i] (LocationError err ) {
                    uint32_t hwId = 0;
                    errs[i] = mAdapter.getHwIdFromClient(mClient, mIds[i], hwId);
                    if (LOCATION_ERROR_SUCCESS == errs[i]) {
                        mApi.removeGeofence(hwId, mIds[i],
                        new LocApiResponse(*mAdapter.getContext(),
                        [&mAdapter = mAdapter, mCount = mCount, mClient = mClient, mIds = mIds,
                        hwId, errs, i] (LocationError err ) {
                            if (LOCATION_ERROR_SUCCESS == err) {
                                mAdapter.removeGeofenceItem(hwId);
                            }
                            errs[i] = err;

                            // Send aggregated response on last item and cleanup
                            if (i == mCount-1) {
                                mAdapter.reportResponse(mClient, mCount, errs, mIds);
                                delete[] errs;
                                delete[] mIds;
                            }
                        }));
                    } else {
                        // Send aggregated response on last item and cleanup
                        if (i == mCount-1) {
                            mAdapter.reportResponse(mClient, mCount, errs, mIds);
                            delete[] errs;
                            delete[] mIds;
                        }
                    }
                }));
            }
        }
    };

    if (0 == count) {
        return;
    }
    uint32_t* idsCopy = new uint32_t[count];
    if (nullptr == idsCopy) {
        LOC_LOGE("%s]: new failed to allocate idsCopy", __func__);
        return;
    }
    COPY_IF_NOT_NULL(idsCopy, ids, count);
    sendMsg(new MsgRemoveGeofences(*this, *mLocApi, client, count, idsCopy));
}

void
GeofenceAdapter::pauseGeofencesCommand(LocationAPI* client, size_t count, uint32_t* ids)
{
    LOC_LOGD("%s]: client %p count %zu", __func__, client, count);

    struct MsgPauseGeofences : public LocMsg {
        GeofenceAdapter& mAdapter;
        LocApiBase& mApi;
        LocationAPI* mClient;
        size_t mCount;
        uint32_t* mIds;
        inline MsgPauseGeofences(GeofenceAdapter& adapter,
                                 LocApiBase& api,
                                 LocationAPI* client,
                                 size_t count,
                                 uint32_t* ids) :
            LocMsg(),
            mAdapter(adapter),
            mApi(api),
            mClient(client),
            mCount(count),
            mIds(ids) {}
        inline virtual void proc() const  {
            LocationError* errs = new LocationError[mCount];
            if (nullptr == errs) {
                LOC_LOGE("%s]: new failed to allocate errs", __func__);
                return;
            }
            for (size_t i=0; i < mCount; ++i) {
                mApi.addToCallQueue(new LocApiResponse(*mAdapter.getContext(),
                        [&mAdapter = mAdapter, mCount = mCount, mClient = mClient, mIds = mIds,
                        &mApi = mApi, errs, i] (LocationError err ) {
                    uint32_t hwId = 0;
                    errs[i] = mAdapter.getHwIdFromClient(mClient, mIds[i], hwId);
                    if (LOCATION_ERROR_SUCCESS == errs[i]) {
                        mApi.pauseGeofence(hwId, mIds[i], new LocApiResponse(*mAdapter.getContext(),
                        [&mAdapter = mAdapter, mCount = mCount, mClient = mClient, mIds = mIds,
                        hwId, errs, i] (LocationError err ) {
                            if (LOCATION_ERROR_SUCCESS == err) {
                                mAdapter.pauseGeofenceItem(hwId);
                            }
                            errs[i] = err;

                            // Send aggregated response on last item and cleanup
                            if (i == mCount-1) {
                                mAdapter.reportResponse(mClient, mCount, errs, mIds);
                                delete[] errs;
                                delete[] mIds;
                            }
                        }));
                    } else {
                        // Send aggregated response on last item and cleanup
                        if (i == mCount-1) {
                            mAdapter.reportResponse(mClient, mCount, errs, mIds);
                            delete[] errs;
                            delete[] mIds;
                        }
                    }
                }));
            }
        }
    };

    if (0 == count) {
        return;
    }
    uint32_t* idsCopy = new uint32_t[count];
    if (nullptr == idsCopy) {
        LOC_LOGE("%s]: new failed to allocate idsCopy", __func__);
        return;
    }
    COPY_IF_NOT_NULL(idsCopy, ids, count);
    sendMsg(new MsgPauseGeofences(*this, *mLocApi, client, count, idsCopy));
}

void
GeofenceAdapter::resumeGeofencesCommand(LocationAPI* client, size_t count, uint32_t* ids)
{
    LOC_LOGD("%s]: client %p count %zu", __func__, client, count);

    struct MsgResumeGeofences : public LocMsg {
        GeofenceAdapter& mAdapter;
        LocApiBase& mApi;
        LocationAPI* mClient;
        size_t mCount;
        uint32_t* mIds;
        inline MsgResumeGeofences(GeofenceAdapter& adapter,
                                  LocApiBase& api,
                                  LocationAPI* client,
                                  size_t count,
                                  uint32_t* ids) :
            LocMsg(),
            mAdapter(adapter),
            mApi(api),
            mClient(client),
            mCount(count),
            mIds(ids) {}
        inline virtual void proc() const  {
            LocationError* errs = new LocationError[mCount];
            if (nullptr == errs) {
                LOC_LOGE("%s]: new failed to allocate errs", __func__);
                return;
            }
            for (size_t i=0; i < mCount; ++i) {
                mApi.addToCallQueue(new LocApiResponse(*mAdapter.getContext(),
                        [&mAdapter = mAdapter, mCount = mCount, mClient = mClient, mIds = mIds,
                        &mApi = mApi, errs, i] (LocationError err ) {
                    uint32_t hwId = 0;
                    errs[i] = mAdapter.getHwIdFromClient(mClient, mIds[i], hwId);
                    if (LOCATION_ERROR_SUCCESS == errs[i]) {
                        mApi.resumeGeofence(hwId, mIds[i],
                                new LocApiResponse(*mAdapter.getContext(),
                                [&mAdapter = mAdapter, mCount = mCount, mClient = mClient, hwId,
                                errs, mIds = mIds, i] (LocationError err ) {
                            if (LOCATION_ERROR_SUCCESS == err) {
                                errs[i] = err;

                                mAdapter.resumeGeofenceItem(hwId);
                                // Send aggregated response on last item and cleanup
                                if (i == mCount-1) {
                                    mAdapter.reportResponse(mClient, mCount, errs, mIds);
                                    delete[] errs;
                                    delete[] mIds;
                                }
                            }
                        }));
                    } else {
                        // Send aggregated response on last item and cleanup
                        if (i == mCount-1) {
                            mAdapter.reportResponse(mClient, mCount, errs, mIds);
                            delete[] errs;
                            delete[] mIds;
                        }
                    }
                }));
            }
        }
    };

    if (0 == count) {
        return;
    }
    uint32_t* idsCopy = new uint32_t[count];
    if (nullptr == idsCopy) {
        LOC_LOGE("%s]: new failed to allocate idsCopy", __func__);
        return;
    }
    COPY_IF_NOT_NULL(idsCopy, ids, count);
    sendMsg(new MsgResumeGeofences(*this, *mLocApi, client, count, idsCopy));
}

void
GeofenceAdapter::modifyGeofencesCommand(LocationAPI* client, size_t count, uint32_t* ids,
        GeofenceOption* options)
{
    LOC_LOGD("%s]: client %p count %zu", __func__, client, count);

    struct MsgModifyGeofences : public LocMsg {
        GeofenceAdapter& mAdapter;
        LocApiBase& mApi;
        LocationAPI* mClient;
        size_t mCount;
        uint32_t* mIds;
        GeofenceOption* mOptions;
        inline MsgModifyGeofences(GeofenceAdapter& adapter,
                                  LocApiBase& api,
                                  LocationAPI* client,
                                  size_t count,
                                  uint32_t* ids,
                                  GeofenceOption* options) :
            LocMsg(),
            mAdapter(adapter),
            mApi(api),
            mClient(client),
            mCount(count),
            mIds(ids),
            mOptions(options) {}
        inline virtual void proc() const  {
            LocationError* errs = new LocationError[mCount];
            if (nullptr == errs) {
                LOC_LOGE("%s]: new failed to allocate errs", __func__);
                return;
            }
            for (size_t i=0; i < mCount; ++i) {
                if (NULL == mIds || NULL == mOptions) {
                    errs[i] = LOCATION_ERROR_INVALID_PARAMETER;
                } else {
                    mApi.addToCallQueue(new LocApiResponse(*mAdapter.getContext(),
                            [&mAdapter = mAdapter, mCount = mCount, mClient = mClient, mIds = mIds,
                            &mApi = mApi, mOptions = mOptions, errs, i] (LocationError err ) {
                        uint32_t hwId = 0;
                        errs[i] = mAdapter.getHwIdFromClient(mClient, mIds[i], hwId);
                        if (LOCATION_ERROR_SUCCESS == errs[i]) {
                            mApi.modifyGeofence(hwId, mIds[i], mOptions[i],
                                    new LocApiResponse(*mAdapter.getContext(),
                                    [&mAdapter = mAdapter, mCount = mCount, mClient = mClient,
                                    mIds = mIds, mOptions = mOptions, hwId, errs, i]
                                    (LocationError err ) {
                                if (LOCATION_ERROR_SUCCESS == err) {
                                    errs[i] = err;

                                    mAdapter.modifyGeofenceItem(hwId, mOptions[i]);
                                }
                                // Send aggregated response on last item and cleanup
                                if (i == mCount-1) {
                                    mAdapter.reportResponse(mClient, mCount, errs, mIds);
                                    delete[] errs;
                                    delete[] mIds;
                                    delete[] mOptions;
                                }
                            }));
                        } else {
                            // Send aggregated response on last item and cleanup
                            if (i == mCount-1) {
                                mAdapter.reportResponse(mClient, mCount, errs, mIds);
                                delete[] errs;
                                delete[] mIds;
                                delete[] mOptions;
                            }
                        }
                    }));
                }
            }
        }
    };

    if (0 == count) {
        return;
    }
    uint32_t* idsCopy = new uint32_t[count];
    if (nullptr == idsCopy) {
        LOC_LOGE("%s]: new failed to allocate idsCopy", __func__);
        return;
    }
    COPY_IF_NOT_NULL(idsCopy, ids, count);
    GeofenceOption* optionsCopy;
    if (options == NULL) {
        optionsCopy = NULL;
    } else {
        optionsCopy = new GeofenceOption[count];
        if (nullptr == optionsCopy) {
            LOC_LOGE("%s]: new failed to allocate optionsCopy", __func__);
            return;
        }
        COPY_IF_NOT_NULL(optionsCopy, options, count);
    }

    sendMsg(new MsgModifyGeofences(*this, *mLocApi, client, count, idsCopy, optionsCopy));
}

void
GeofenceAdapter::saveGeofenceItem(LocationAPI* client, uint32_t clientId, uint32_t hwId,
        const GeofenceOption& options, const GeofenceInfo& info)
{
    LOC_LOGD("%s]: hwId %u client %p clientId %u", __func__, hwId, client, clientId);
    GeofenceKey key(client, clientId);
    GeofenceObject object = {key,
                             options.breachTypeMask,
                             options.responsiveness,
                             options.dwellTime,
                             info.latitude,
                             info.longitude,
                             info.radius,
                             false};
    mGeofences[hwId] = object;
    mGeofenceIds[key] = hwId;
    dump();
}

void
GeofenceAdapter::removeGeofenceItem(uint32_t hwId)
{
    GeofenceKey key;
    LocationError err = getGeofenceKeyFromHwId(hwId, key);
    if (LOCATION_ERROR_SUCCESS != err) {
        LOC_LOGE("%s]: can not find the key for hwId %u", __func__, hwId);
    } else {
        auto it1 = mGeofenceIds.find(key);
        if (it1 != mGeofenceIds.end()) {
            mGeofenceIds.erase(it1);

            auto it2 = mGeofences.find(hwId);
            if (it2 != mGeofences.end()) {
                mGeofences.erase(it2);
                dump();
            } else {
                LOC_LOGE("%s]:geofence item to erase not found. hwId %u", __func__, hwId);
            }
        } else {
            LOC_LOGE("%s]: geofence item to erase not found. hwId %u", __func__, hwId);
        }
    }
}

void
GeofenceAdapter::pauseGeofenceItem(uint32_t hwId)
{
    auto it = mGeofences.find(hwId);
    if (it != mGeofences.end()) {
        it->second.paused = true;
        dump();
    } else {
        LOC_LOGE("%s]: geofence item to pause not found. hwId %u", __func__, hwId);
    }
}

void
GeofenceAdapter::resumeGeofenceItem(uint32_t hwId)
{
    auto it = mGeofences.find(hwId);
    if (it != mGeofences.end()) {
        it->second.paused = false;
        dump();
    } else {
        LOC_LOGE("%s]: geofence item to resume not found. hwId %u", __func__, hwId);
    }
}

void
GeofenceAdapter::modifyGeofenceItem(uint32_t hwId, const GeofenceOption& options)
{
    auto it = mGeofences.find(hwId);
    if (it != mGeofences.end()) {
        it->second.breachMask = options.breachTypeMask;
        it->second.responsiveness = options.responsiveness;
        it->second.dwellTime = options.dwellTime;
        dump();
    } else {
        LOC_LOGE("%s]: geofence item to modify not found. hwId %u", __func__, hwId);
    }
}


void
GeofenceAdapter::geofenceBreachEvent(size_t count, uint32_t* hwIds, Location& location,
        GeofenceBreachType breachType, uint64_t timestamp)
{

    IF_LOC_LOGD {
        std::string idsString = "[";
        if (NULL != hwIds) {
            for (size_t i=0; i < count; ++i) {
                idsString += std::to_string(hwIds[i]) + " ";
            }
        }
        idsString += "]";
        LOC_LOGD("%s]: breachType %u count %zu ids %s",
                 __func__, breachType, count, idsString.c_str());
    }

    if (0 == count || NULL == hwIds)
        return;

    struct MsgGeofenceBreach : public LocMsg {
        GeofenceAdapter& mAdapter;
        size_t mCount;
        uint32_t* mHwIds;
        Location mLocation;
        GeofenceBreachType mBreachType;
        uint64_t mTimestamp;
        inline MsgGeofenceBreach(GeofenceAdapter& adapter,
                                 size_t count,
                                 uint32_t* hwIds,
                                 Location& location,
                                 GeofenceBreachType breachType,
                                 uint64_t timestamp) :
            LocMsg(),
            mAdapter(adapter),
            mCount(count),
            mHwIds(new uint32_t[count]),
            mLocation(location),
            mBreachType(breachType),
            mTimestamp(timestamp)
        {
            if (nullptr == mHwIds) {
                LOC_LOGE("%s]: new failed to allocate mHwIds", __func__);
                return;
            }
            COPY_IF_NOT_NULL(mHwIds, hwIds, mCount);
        }
        inline virtual ~MsgGeofenceBreach() {
            delete[] mHwIds;
        }
        inline virtual void proc() const {
            mAdapter.geofenceBreach(mCount, mHwIds, mLocation, mBreachType, mTimestamp);
        }
    };

    sendMsg(new MsgGeofenceBreach(*this, count, hwIds, location, breachType, timestamp));

}

void
GeofenceAdapter::geofenceBreach(size_t count, uint32_t* hwIds, const Location& location,
        GeofenceBreachType breachType, uint64_t timestamp)
{

    for (auto it = mClientData.begin(); it != mClientData.end(); ++it) {
        uint32_t* clientIds = new uint32_t[count];
        if (nullptr == clientIds) {
            return;
        }
        uint32_t index = 0;
        for (size_t i=0; i < count; ++i) {
            GeofenceKey key;
            LocationError err = getGeofenceKeyFromHwId(hwIds[i], key);
            if (LOCATION_ERROR_SUCCESS == err) {
                if (key.client == it->first) {
                    clientIds[index++] = key.id;
                }
            }
        }
        if (index > 0 && it->second.geofenceBreachCb != nullptr) {
            GeofenceBreachNotification notify = {sizeof(GeofenceBreachNotification),
                                                 index,
                                                 clientIds,
                                                 location,
                                                 breachType,
                                                 timestamp};

            it->second.geofenceBreachCb(notify);
        }
        delete[] clientIds;
    }
}

void
GeofenceAdapter::geofenceStatusEvent(GeofenceStatusAvailable available)
{
    LOC_LOGD("%s]: available %u ", __func__, available);

    struct MsgGeofenceStatus : public LocMsg {
        GeofenceAdapter& mAdapter;
        GeofenceStatusAvailable mAvailable;
        inline MsgGeofenceStatus(GeofenceAdapter& adapter,
                                 GeofenceStatusAvailable available) :
            LocMsg(),
            mAdapter(adapter),
            mAvailable(available) {}
        inline virtual void proc() const {
            mAdapter.geofenceStatus(mAvailable);
        }
    };

    sendMsg(new MsgGeofenceStatus(*this, available));
}

void
GeofenceAdapter::geofenceStatus(GeofenceStatusAvailable available)
{
    for (auto it = mClientData.begin(); it != mClientData.end(); ++it) {
        if (it->second.geofenceStatusCb != nullptr) {
            GeofenceStatusNotification notify = {sizeof(GeofenceStatusNotification),
                                                 available,
                                                 LOCATION_TECHNOLOGY_TYPE_GNSS};
            it->second.geofenceStatusCb(notify);
        }
    }
}

void
GeofenceAdapter::dump()
{
    IF_LOC_LOGV {
        LOC_LOGV(
            "HAL | hwId  | mask | respon | latitude | longitude | radius | paused |  Id  | client");
        for (auto it = mGeofences.begin(); it != mGeofences.end(); ++it) {
            uint32_t hwId = it->first;
            GeofenceObject object = it->second;
            LOC_LOGV("    | %5u | %4u | %6u | %8.2f | %9.2f | %6.2f | %6u | %04x | %p ",
                    hwId, object.breachMask, object.responsiveness,
                    object.latitude, object.longitude, object.radius,
                    object.paused, object.key.id, object.key.client);
        }
    }
}