/* Copyright (c) 2017-2019, 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_NDEBUG 0
#define LOG_TAG "LocSvc_BatchingAdapter"

#include <loc_pla.h>
#include <log_util.h>
#include <LocContext.h>
#include <BatchingAdapter.h>

using namespace loc_core;

BatchingAdapter::BatchingAdapter() :
    LocAdapterBase(0,
                    LocContext::getLocContext(
                        NULL,
                        NULL,
                        LocContext::mLocationHalName,
                        false)),
    mOngoingTripDistance(0),
    mOngoingTripTBFInterval(0),
    mTripWithOngoingTBFDropped(false),
    mTripWithOngoingTripDistanceDropped(false),
    mBatchingTimeout(0),
    mBatchingAccuracy(1),
    mBatchSize(0),
    mTripBatchSize(0)
{
    LOC_LOGD("%s]: Constructor", __func__);
    readConfigCommand();
    setConfigCommand();
}

void
BatchingAdapter::readConfigCommand()
{
    LOC_LOGD("%s]: ", __func__);

    struct MsgReadConfig : public LocMsg {
        BatchingAdapter& mAdapter;
        inline MsgReadConfig(BatchingAdapter& adapter) :
            LocMsg(),
            mAdapter(adapter) {}
        inline virtual void proc() const {
            uint32_t batchingTimeout = 0;
            uint32_t batchingAccuracy = 0;
            uint32_t batchSize = 0;
            uint32_t tripBatchSize = 0;
            static const loc_param_s_type flp_conf_param_table[] =
            {
                {"BATCH_SIZE", &batchSize, NULL, 'n'},
                {"OUTDOOR_TRIP_BATCH_SIZE", &tripBatchSize, NULL, 'n'},
                {"BATCH_SESSION_TIMEOUT", &batchingTimeout, NULL, 'n'},
                {"ACCURACY", &batchingAccuracy, NULL, 'n'},
            };
            UTIL_READ_CONF(LOC_PATH_FLP_CONF, flp_conf_param_table);

            LOC_LOGD("%s]: batchSize %u tripBatchSize %u batchingAccuracy %u batchingTimeout %u ",
                     __func__, batchSize, tripBatchSize, batchingAccuracy, batchingTimeout);

             mAdapter.setBatchSize(batchSize);
             mAdapter.setTripBatchSize(tripBatchSize);
             mAdapter.setBatchingTimeout(batchingTimeout);
             mAdapter.setBatchingAccuracy(batchingAccuracy);
        }
    };

    sendMsg(new MsgReadConfig(*this));

}

void
BatchingAdapter::setConfigCommand()
{
    LOC_LOGD("%s]: ", __func__);

    struct MsgSetConfig : public LocMsg {
        BatchingAdapter& mAdapter;
        LocApiBase& mApi;
        inline MsgSetConfig(BatchingAdapter& adapter,
                            LocApiBase& api) :
            LocMsg(),
            mAdapter(adapter),
            mApi(api) {}
        inline virtual void proc() const {
            mApi.setBatchSize(mAdapter.getBatchSize());
            mApi.setTripBatchSize(mAdapter.getTripBatchSize());
        }
    };

    sendMsg(new MsgSetConfig(*this, *mLocApi));
}

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

    typedef struct pairKeyBatchMode {
        LocationAPI* client;
        uint32_t id;
        BatchingMode batchingMode;
        inline pairKeyBatchMode(LocationAPI* _client, uint32_t _id, BatchingMode _bMode) :
            client(_client), id(_id), batchingMode(_bMode) {}
    } pairKeyBatchMode;
    std::vector<pairKeyBatchMode> vBatchingClient;
    for (auto it : mBatchingSessions) {
        if (client == it.first.client) {
            vBatchingClient.emplace_back(it.first.client, it.first.id, it.second.batchingMode);
        }
    }
    for (auto keyBatchingMode : vBatchingClient) {
        if (keyBatchingMode.batchingMode != BATCHING_MODE_TRIP) {
            stopBatching(keyBatchingMode.client, keyBatchingMode.id);
        } else {
            stopTripBatchingMultiplex(keyBatchingMode.client, keyBatchingMode.id);
        }
    }
}

void
BatchingAdapter::updateClientsEventMask()
{
    LOC_API_ADAPTER_EVENT_MASK_T mask = 0;
    for (auto it=mClientData.begin(); it != mClientData.end(); ++it) {
        // we don't register LOC_API_ADAPTER_BIT_BATCH_FULL until we
        // start batching with ROUTINE or TRIP option
        if (it->second.batchingCb != nullptr) {
            mask |= LOC_API_ADAPTER_BIT_BATCH_STATUS;
        }
    }
    if (autoReportBatchingSessionsCount() > 0) {
        mask |= LOC_API_ADAPTER_BIT_BATCH_FULL;
    }
    updateEvtMask(mask, LOC_REGISTRATION_MASK_SET);
}

void
BatchingAdapter::handleEngineUpEvent()
{
    struct MsgSSREvent : public LocMsg {
        BatchingAdapter& mAdapter;
        LocApiBase& mApi;
        inline MsgSSREvent(BatchingAdapter& adapter,
                           LocApiBase& api) :
            LocMsg(),
            mAdapter(adapter),
            mApi(api) {}
        virtual void proc() const {
            mAdapter.setEngineCapabilitiesKnown(true);
            mAdapter.broadcastCapabilities(mAdapter.getCapabilities());
            mApi.setBatchSize(mAdapter.getBatchSize());
            mApi.setTripBatchSize(mAdapter.getTripBatchSize());
            mAdapter.restartSessions();
            for (auto msg: mAdapter.mPendingMsgs) {
                mAdapter.sendMsg(msg);
            }
            mAdapter.mPendingMsgs.clear();
        }
    };

    sendMsg(new MsgSSREvent(*this, *mLocApi));
}

void
BatchingAdapter::restartSessions()
{
    LOC_LOGD("%s]: ", __func__);

    if (autoReportBatchingSessionsCount() > 0) {
        updateEvtMask(LOC_API_ADAPTER_BIT_BATCH_FULL,
                      LOC_REGISTRATION_MASK_ENABLED);
    }
    for (auto it = mBatchingSessions.begin();
              it != mBatchingSessions.end(); ++it) {
        if (it->second.batchingMode != BATCHING_MODE_TRIP) {
            mLocApi->startBatching(it->first.id, it->second,
                                    getBatchingAccuracy(), getBatchingTimeout(),
                                    new LocApiResponse(*getContext(),
                                    [] (LocationError /*err*/) {}));
        }
    }

    if (mTripSessions.size() > 0) {
        // restart outdoor trip batching session if any.
        mOngoingTripDistance = 0;
        mOngoingTripTBFInterval = 0;

        // record the min trip distance and min tbf interval of all ongoing sessions
        for (auto tripSession : mTripSessions) {

            TripSessionStatus &tripSessStatus = tripSession.second;

            if ((0 == mOngoingTripDistance) ||
                (mOngoingTripDistance >
                 (tripSessStatus.tripDistance - tripSessStatus.accumulatedDistanceThisTrip))) {
                mOngoingTripDistance = tripSessStatus.tripDistance -
                    tripSessStatus.accumulatedDistanceThisTrip;
            }

            if ((0 == mOngoingTripTBFInterval) ||
                (mOngoingTripTBFInterval > tripSessStatus.tripTBFInterval)) {
                mOngoingTripTBFInterval = tripSessStatus.tripTBFInterval;
            }

            // reset the accumulatedDistanceOngoingBatch for each session
            tripSessStatus.accumulatedDistanceOngoingBatch = 0;

        }

        mLocApi->startOutdoorTripBatching(mOngoingTripDistance, mOngoingTripTBFInterval,
                getBatchingTimeout(), new LocApiResponse(*getContext(), [this] (LocationError err) {
            if (LOCATION_ERROR_SUCCESS != err) {
                mOngoingTripDistance = 0;
                mOngoingTripTBFInterval = 0;
            }
            printTripReport();
        }));
    }
}

bool
BatchingAdapter::hasBatchingCallback(LocationAPI* client)
{
    auto it = mClientData.find(client);
    return (it != mClientData.end() && it->second.batchingCb);
}

bool
BatchingAdapter::isBatchingSession(LocationAPI* client, uint32_t sessionId)
{
    LocationSessionKey key(client, sessionId);
    return (mBatchingSessions.find(key) != mBatchingSessions.end());
}

bool
BatchingAdapter::isTripSession(uint32_t sessionId) {
    return (mTripSessions.find(sessionId) != mTripSessions.end());
}

void
BatchingAdapter::saveBatchingSession(LocationAPI* client, uint32_t sessionId,
        const BatchingOptions& batchingOptions)
{
    LocationSessionKey key(client, sessionId);
    mBatchingSessions[key] = batchingOptions;
}

void
BatchingAdapter::eraseBatchingSession(LocationAPI* client, uint32_t sessionId)
{
    LocationSessionKey key(client, sessionId);
    auto it = mBatchingSessions.find(key);
    if (it != mBatchingSessions.end()) {
        mBatchingSessions.erase(it);
    }
}

void
BatchingAdapter::reportResponse(LocationAPI* client, LocationError err, uint32_t sessionId)
{
    LOC_LOGD("%s]: client %p id %u err %u", __func__, client, sessionId, err);

    auto it = mClientData.find(client);
    if (it != mClientData.end() &&
        it->second.responseCb != nullptr) {
        it->second.responseCb(err, sessionId);
    } else {
        LOC_LOGE("%s]: client %p id %u not found in data", __func__, client, sessionId);
    }
}

uint32_t
BatchingAdapter::autoReportBatchingSessionsCount()
{
    uint32_t count = 0;
    for (auto batchingSession: mBatchingSessions) {
        if (batchingSession.second.batchingMode != BATCHING_MODE_NO_AUTO_REPORT) {
            count++;
        }
    }
    count += mTripSessions.size();
    return count;
}

uint32_t
BatchingAdapter::startBatchingCommand(
        LocationAPI* client, BatchingOptions& batchOptions)
{
    uint32_t sessionId = generateSessionId();
    LOC_LOGD("%s]: client %p id %u minInterval %u minDistance %u mode %u Batching Mode %d",
             __func__, client, sessionId, batchOptions.minInterval, batchOptions.minDistance,
             batchOptions.mode,batchOptions.batchingMode);

    struct MsgStartBatching : public LocMsg {
        BatchingAdapter& mAdapter;
        LocApiBase& mApi;
        LocationAPI* mClient;
        uint32_t mSessionId;
        BatchingOptions mBatchingOptions;
        inline MsgStartBatching(BatchingAdapter& adapter,
                               LocApiBase& api,
                               LocationAPI* client,
                               uint32_t sessionId,
                               BatchingOptions batchOptions) :
            LocMsg(),
            mAdapter(adapter),
            mApi(api),
            mClient(client),
            mSessionId(sessionId),
            mBatchingOptions(batchOptions) {}
        inline virtual void proc() const {
            if (!mAdapter.isEngineCapabilitiesKnown()) {
                mAdapter.mPendingMsgs.push_back(new MsgStartBatching(*this));
                return;
            }
            LocationError err = LOCATION_ERROR_SUCCESS;

            if (!mAdapter.hasBatchingCallback(mClient)) {
                err = LOCATION_ERROR_CALLBACK_MISSING;
            } else if (0 == mBatchingOptions.size) {
                err = LOCATION_ERROR_INVALID_PARAMETER;
            } else if (!ContextBase::isMessageSupported(
                       LOC_API_ADAPTER_MESSAGE_DISTANCE_BASE_LOCATION_BATCHING)) {
                err = LOCATION_ERROR_NOT_SUPPORTED;
            }
            if (LOCATION_ERROR_SUCCESS == err) {
                if (mBatchingOptions.batchingMode == BATCHING_MODE_ROUTINE ||
                    mBatchingOptions.batchingMode == BATCHING_MODE_NO_AUTO_REPORT) {
                    mAdapter.startBatching(mClient, mSessionId, mBatchingOptions);
                } else if (mBatchingOptions.batchingMode == BATCHING_MODE_TRIP) {
                    mAdapter.startTripBatchingMultiplex(mClient, mSessionId, mBatchingOptions);
                } else {
                    mAdapter.reportResponse(mClient, LOCATION_ERROR_INVALID_PARAMETER, mSessionId);
                }
            }
        }
    };

    sendMsg(new MsgStartBatching(*this, *mLocApi, client, sessionId, batchOptions));

    return sessionId;
}

void
BatchingAdapter::startBatching(LocationAPI* client, uint32_t sessionId,
        const BatchingOptions& batchingOptions)
{
    if (batchingOptions.batchingMode != BATCHING_MODE_NO_AUTO_REPORT &&
        0 == autoReportBatchingSessionsCount()) {
        // if there is currenty no batching sessions interested in batch full event, then this
        // new session will need to register for batch full event
        updateEvtMask(LOC_API_ADAPTER_BIT_BATCH_FULL,
                      LOC_REGISTRATION_MASK_ENABLED);
    }

    // Assume start will be OK, remove session if not
    saveBatchingSession(client, sessionId, batchingOptions);
    mLocApi->startBatching(sessionId, batchingOptions, getBatchingAccuracy(), getBatchingTimeout(),
            new LocApiResponse(*getContext(),
            [this, client, sessionId, batchingOptions] (LocationError err) {
        if (LOCATION_ERROR_SUCCESS != err) {
            eraseBatchingSession(client, sessionId);
        }

        if (LOCATION_ERROR_SUCCESS != err &&
            batchingOptions.batchingMode != BATCHING_MODE_NO_AUTO_REPORT &&
            0 == autoReportBatchingSessionsCount()) {
            // if we fail to start batching and we have already registered batch full event
            // we need to undo that since no sessions are now interested in batch full event
            updateEvtMask(LOC_API_ADAPTER_BIT_BATCH_FULL,
                          LOC_REGISTRATION_MASK_DISABLED);
        }

        reportResponse(client, err, sessionId);
    }));
}

void
BatchingAdapter::updateBatchingOptionsCommand(LocationAPI* client, uint32_t id,
        BatchingOptions& batchOptions)
{
    LOC_LOGD("%s]: client %p id %u minInterval %u minDistance %u mode %u batchMode %u",
             __func__, client, id, batchOptions.minInterval,
             batchOptions.minDistance, batchOptions.mode,
             batchOptions.batchingMode);

    struct MsgUpdateBatching : public LocMsg {
        BatchingAdapter& mAdapter;
        LocApiBase& mApi;
        LocationAPI* mClient;
        uint32_t mSessionId;
        BatchingOptions mBatchOptions;
        inline MsgUpdateBatching(BatchingAdapter& adapter,
                                LocApiBase& api,
                                LocationAPI* client,
                                uint32_t sessionId,
                                BatchingOptions batchOptions) :
            LocMsg(),
            mAdapter(adapter),
            mApi(api),
            mClient(client),
            mSessionId(sessionId),
            mBatchOptions(batchOptions) {}
        inline virtual void proc() const {
            if (!mAdapter.isEngineCapabilitiesKnown()) {
                mAdapter.mPendingMsgs.push_back(new MsgUpdateBatching(*this));
                return;
            }
            LocationError err = LOCATION_ERROR_SUCCESS;
            if (!mAdapter.isBatchingSession(mClient, mSessionId)) {
                err = LOCATION_ERROR_ID_UNKNOWN;
            } else if ((0 == mBatchOptions.size) ||
                       (mBatchOptions.batchingMode > BATCHING_MODE_NO_AUTO_REPORT)) {
                err = LOCATION_ERROR_INVALID_PARAMETER;
            }
            if (LOCATION_ERROR_SUCCESS == err) {
                if (!mAdapter.isTripSession(mSessionId)) {
                    mAdapter.stopBatching(mClient, mSessionId, true, mBatchOptions);
                } else {
                    mAdapter.stopTripBatchingMultiplex(mClient, mSessionId, true, mBatchOptions);
                }
           }
        }
    };

    sendMsg(new MsgUpdateBatching(*this, *mLocApi, client, id, batchOptions));
}

void
BatchingAdapter::stopBatchingCommand(LocationAPI* client, uint32_t id)
{
    LOC_LOGD("%s]: client %p id %u", __func__, client, id);

    struct MsgStopBatching : public LocMsg {
        BatchingAdapter& mAdapter;
        LocApiBase& mApi;
        LocationAPI* mClient;
        uint32_t mSessionId;
        inline MsgStopBatching(BatchingAdapter& adapter,
                               LocApiBase& api,
                               LocationAPI* client,
                               uint32_t sessionId) :
            LocMsg(),
            mAdapter(adapter),
            mApi(api),
            mClient(client),
            mSessionId(sessionId) {}
        inline virtual void proc() const {
            if (!mAdapter.isEngineCapabilitiesKnown()) {
                mAdapter.mPendingMsgs.push_back(new MsgStopBatching(*this));
                return;
            }
            LocationError err = LOCATION_ERROR_SUCCESS;
            if (!mAdapter.isBatchingSession(mClient, mSessionId)) {
                err = LOCATION_ERROR_ID_UNKNOWN;
            }
            if (LOCATION_ERROR_SUCCESS == err) {
                if (mAdapter.isTripSession(mSessionId)) {
                    mAdapter.stopTripBatchingMultiplex(mClient, mSessionId);
                } else {
                    mAdapter.stopBatching(mClient, mSessionId);
                }
            }
        }
    };

    sendMsg(new MsgStopBatching(*this, *mLocApi, client, id));
}

void
BatchingAdapter::stopBatching(LocationAPI* client, uint32_t sessionId, bool restartNeeded,
        const BatchingOptions& batchOptions)
{
    LocationSessionKey key(client, sessionId);
    auto it = mBatchingSessions.find(key);
    if (it != mBatchingSessions.end()) {
        auto flpOptions = it->second;
        // Assume stop will be OK, restore session if not
        eraseBatchingSession(client, sessionId);
        mLocApi->stopBatching(sessionId,
                new LocApiResponse(*getContext(),
                [this, client, sessionId, flpOptions, restartNeeded, batchOptions]
                (LocationError err) {
            if (LOCATION_ERROR_SUCCESS != err) {
                saveBatchingSession(client, sessionId, batchOptions);
            } else {
                // if stopBatching is success, unregister for batch full event if this was the last
                // batching session that is interested in batch full event
                if (0 == autoReportBatchingSessionsCount() &&
                    flpOptions.batchingMode != BATCHING_MODE_NO_AUTO_REPORT) {
                    updateEvtMask(LOC_API_ADAPTER_BIT_BATCH_FULL,
                                  LOC_REGISTRATION_MASK_DISABLED);
                }

                if (restartNeeded) {
                    if (batchOptions.batchingMode == BATCHING_MODE_ROUTINE ||
                            batchOptions.batchingMode == BATCHING_MODE_NO_AUTO_REPORT) {
                        startBatching(client, sessionId, batchOptions);
                    } else if (batchOptions.batchingMode == BATCHING_MODE_TRIP) {
                        startTripBatchingMultiplex(client, sessionId, batchOptions);
                    }
                }
            }
            reportResponse(client, err, sessionId);
        }));
    }
}

void
BatchingAdapter::getBatchedLocationsCommand(LocationAPI* client, uint32_t id, size_t count)
{
    LOC_LOGD("%s]: client %p id %u count %zu", __func__, client, id, count);

    struct MsgGetBatchedLocations : public LocMsg {
        BatchingAdapter& mAdapter;
        LocApiBase& mApi;
        LocationAPI* mClient;
        uint32_t mSessionId;
        size_t mCount;
        inline MsgGetBatchedLocations(BatchingAdapter& adapter,
                                     LocApiBase& api,
                                     LocationAPI* client,
                                     uint32_t sessionId,
                                     size_t count) :
            LocMsg(),
            mAdapter(adapter),
            mApi(api),
            mClient(client),
            mSessionId(sessionId),
            mCount(count) {}
        inline virtual void proc() const {
            if (!mAdapter.isEngineCapabilitiesKnown()) {
                mAdapter.mPendingMsgs.push_back(new MsgGetBatchedLocations(*this));
                return;
            }
            LocationError err = LOCATION_ERROR_SUCCESS;
            if (!mAdapter.hasBatchingCallback(mClient)) {
                err = LOCATION_ERROR_CALLBACK_MISSING;
            } else if (!mAdapter.isBatchingSession(mClient, mSessionId)) {
                err = LOCATION_ERROR_ID_UNKNOWN;
            }
            if (LOCATION_ERROR_SUCCESS == err) {
                if (mAdapter.isTripSession(mSessionId)) {
                    mApi.getBatchedTripLocations(mCount, 0,
                            new LocApiResponse(*mAdapter.getContext(),
                            [&mAdapter = mAdapter, mSessionId = mSessionId,
                            mClient = mClient] (LocationError err) {
                        mAdapter.reportResponse(mClient, err, mSessionId);
                    }));
                } else {
                    mApi.getBatchedLocations(mCount, new LocApiResponse(*mAdapter.getContext(),
                            [&mAdapter = mAdapter, mSessionId = mSessionId,
                            mClient = mClient] (LocationError err) {
                        mAdapter.reportResponse(mClient, err, mSessionId);
                    }));
                }
            } else {
                mAdapter.reportResponse(mClient, err, mSessionId);
            }
        }
    };

    sendMsg(new MsgGetBatchedLocations(*this, *mLocApi, client, id, count));
}

void
BatchingAdapter::reportLocationsEvent(const Location* locations, size_t count,
        BatchingMode batchingMode)
{
    LOC_LOGD("%s]: count %zu batchMode %d", __func__, count, batchingMode);

    struct MsgReportLocations : public LocMsg {
        BatchingAdapter& mAdapter;
        Location* mLocations;
        size_t mCount;
        BatchingMode mBatchingMode;
        inline MsgReportLocations(BatchingAdapter& adapter,
                                  const Location* locations,
                                  size_t count,
                                  BatchingMode batchingMode) :
            LocMsg(),
            mAdapter(adapter),
            mLocations(new Location[count]),
            mCount(count),
            mBatchingMode(batchingMode)
        {
            if (nullptr == mLocations) {
                LOC_LOGE("%s]: new failed to allocate mLocations", __func__);
                return;
            }
            for (size_t i=0; i < mCount; ++i) {
                mLocations[i] = locations[i];
            }
        }
        inline virtual ~MsgReportLocations() {
            if (nullptr != mLocations)
                delete[] mLocations;
        }
        inline virtual void proc() const {
            mAdapter.reportLocations(mLocations, mCount, mBatchingMode);
        }
    };

    sendMsg(new MsgReportLocations(*this, locations, count, batchingMode));
}

void
BatchingAdapter::reportLocations(Location* locations, size_t count, BatchingMode batchingMode)
{
    BatchingOptions batchOptions = {sizeof(BatchingOptions), batchingMode};

    for (auto it=mClientData.begin(); it != mClientData.end(); ++it) {
        if (nullptr != it->second.batchingCb) {
            it->second.batchingCb(count, locations, batchOptions);
        }
    }
}

void
BatchingAdapter::reportCompletedTripsEvent(uint32_t accumulated_distance)
{
    struct MsgReportCompletedTrips : public LocMsg {
        BatchingAdapter& mAdapter;
        uint32_t mAccumulatedDistance;
        inline MsgReportCompletedTrips(BatchingAdapter& adapter,
                                  uint32_t accumulated_distance) :
            LocMsg(),
            mAdapter(adapter),
            mAccumulatedDistance(accumulated_distance)
        {
        }
        inline virtual ~MsgReportCompletedTrips() {
        }
        inline virtual void proc() const {

            // Check if any trips are completed
            std::list<uint32_t> completedTripsList;
            completedTripsList.clear();

            for(auto itt = mAdapter.mTripSessions.begin(); itt != mAdapter.mTripSessions.end();)
            {
                TripSessionStatus &tripSession = itt->second;

                tripSession.accumulatedDistanceThisTrip =
                        tripSession.accumulatedDistanceOnTripRestart
                        + (mAccumulatedDistance - tripSession.accumulatedDistanceOngoingBatch);
                if (tripSession.tripDistance <= tripSession.accumulatedDistanceThisTrip) {
                    // trip is completed
                    completedTripsList.push_back(itt->first);
                    itt = mAdapter.mTripSessions.erase(itt);

                    if (tripSession.tripTBFInterval == mAdapter.mOngoingTripTBFInterval) {
                        // trip with ongoing TBF interval is completed
                        mAdapter.mTripWithOngoingTBFDropped = true;
                    }

                    if (tripSession.tripDistance == mAdapter.mOngoingTripDistance) {
                        // trip with ongoing trip distance is completed
                        mAdapter.mTripWithOngoingTripDistanceDropped = true;
                    }
                } else {
                    itt++;
                }
            }

            if (completedTripsList.size() > 0) {
                mAdapter.reportBatchStatusChange(BATCHING_STATUS_TRIP_COMPLETED,
                        completedTripsList);
                mAdapter.restartTripBatching(false, mAccumulatedDistance, 0);
            } else {
                mAdapter.printTripReport();
            }
        }
    };

    LOC_LOGD("%s]: Accumulated Distance so far: %u",
               __func__,  accumulated_distance);

    sendMsg(new MsgReportCompletedTrips(*this, accumulated_distance));
}

void
BatchingAdapter::reportBatchStatusChange(BatchingStatus batchStatus,
        std::list<uint32_t> & completedTripsList)
{
    BatchingStatusInfo batchStatusInfo =
            {sizeof(BatchingStatusInfo), batchStatus};

    for (auto it=mClientData.begin(); it != mClientData.end(); ++it) {
        if (nullptr != it->second.batchingStatusCb) {
            it->second.batchingStatusCb(batchStatusInfo, completedTripsList);
        }
    }
}

void
BatchingAdapter::reportBatchStatusChangeEvent(BatchingStatus batchStatus)
{
    struct MsgReportBatchStatus : public LocMsg {
        BatchingAdapter& mAdapter;
        BatchingStatus mBatchStatus;
        inline MsgReportBatchStatus(BatchingAdapter& adapter,
                BatchingStatus batchStatus) :
            LocMsg(),
            mAdapter(adapter),
            mBatchStatus(batchStatus)
        {
        }
        inline virtual ~MsgReportBatchStatus() {
        }
        inline virtual void proc() const {
            std::list<uint32_t> tempList;
            tempList.clear();
            mAdapter.reportBatchStatusChange(mBatchStatus, tempList);
        }
    };

    sendMsg(new MsgReportBatchStatus(*this, batchStatus));
}

void
BatchingAdapter::startTripBatchingMultiplex(LocationAPI* client, uint32_t sessionId,
        const BatchingOptions& batchingOptions)
{
    if (mTripSessions.size() == 0) {
        // if there is currenty no batching sessions interested in batch full event, then this
        // new session will need to register for batch full event
        if (0 == autoReportBatchingSessionsCount()) {
            updateEvtMask(LOC_API_ADAPTER_BIT_BATCH_FULL,
                          LOC_REGISTRATION_MASK_ENABLED);
        }

        // Assume start will be OK, remove session if not
        saveBatchingSession(client, sessionId, batchingOptions);

        mTripSessions[sessionId] = { 0, 0, 0, batchingOptions.minDistance,
                batchingOptions.minInterval};
        mLocApi->startOutdoorTripBatching(batchingOptions.minDistance,
                batchingOptions.minInterval, getBatchingTimeout(), new LocApiResponse(*getContext(),
                [this, client, sessionId, batchingOptions] (LocationError err) {
            if (err == LOCATION_ERROR_SUCCESS) {
                mOngoingTripDistance = batchingOptions.minDistance;
                mOngoingTripTBFInterval = batchingOptions.minInterval;
                LOC_LOGD("%s] New Trip started ...", __func__);
                printTripReport();
            } else {
                eraseBatchingSession(client, sessionId);
                mTripSessions.erase(sessionId);
                // if we fail to start batching and we have already registered batch full event
                // we need to undo that since no sessions are now interested in batch full event
                if (0 == autoReportBatchingSessionsCount()) {
                    updateEvtMask(LOC_API_ADAPTER_BIT_BATCH_FULL,
                                  LOC_REGISTRATION_MASK_DISABLED);
                }
            }
            reportResponse(client, err, sessionId);
        }));
    } else {
        // query accumulated distance
        mLocApi->queryAccumulatedTripDistance(
                new LocApiResponseData<LocApiBatchData>(*getContext(),
                [this, batchingOptions, sessionId, client]
                (LocationError err, LocApiBatchData data) {
            uint32_t accumulatedDistanceOngoingBatch = 0;
            uint32_t numOfBatchedPositions = 0;
            uint32_t ongoingTripDistance = mOngoingTripDistance;
            uint32_t ongoingTripInterval = mOngoingTripTBFInterval;
            bool needsRestart = false;

            // check if TBF of new session is lesser than ongoing TBF interval
            if (ongoingTripInterval > batchingOptions.minInterval) {
                ongoingTripInterval = batchingOptions.minInterval;
                needsRestart = true;
            }
            accumulatedDistanceOngoingBatch = data.accumulatedDistance;
            numOfBatchedPositions = data.numOfBatchedPositions;
            TripSessionStatus newTripSession = { accumulatedDistanceOngoingBatch, 0, 0,
                                                 batchingOptions.minDistance,
                                                 batchingOptions.minInterval};
            if (err != LOCATION_ERROR_SUCCESS) {
                // unable to query accumulated distance, assume remaining distance in
                // ongoing batch is mongoingTripDistance.
                if (batchingOptions.minDistance < ongoingTripDistance) {
                    ongoingTripDistance = batchingOptions.minDistance;
                    needsRestart = true;
                }
            } else {
                // compute the remaining distance
                uint32_t ongoing_trip_remaining_distance = ongoingTripDistance -
                        accumulatedDistanceOngoingBatch;

                // check if new trip distance is lesser than the ongoing batch remaining distance
                if (batchingOptions.minDistance < ongoing_trip_remaining_distance) {
                    ongoingTripDistance = batchingOptions.minDistance;
                    needsRestart = true;
                } else if (needsRestart == true) {
                    // needsRestart is anyways true , may be because of lesser TBF of new session.
                    ongoingTripDistance = ongoing_trip_remaining_distance;
                }
                mTripSessions[sessionId] = newTripSession;
                LOC_LOGD("%s] New Trip started ...", __func__);
                printTripReport();
            }

            if (needsRestart) {
                mOngoingTripDistance = ongoingTripDistance;
                mOngoingTripTBFInterval = ongoingTripInterval;

                // reset the accumulatedDistanceOngoingBatch for each session,
                // and record the total accumulated distance so far for the session.
                for (auto itt = mTripSessions.begin(); itt != mTripSessions.end(); itt++) {
                    TripSessionStatus &tripSessStatus = itt->second;
                    tripSessStatus.accumulatedDistanceOngoingBatch = 0;
                    tripSessStatus.accumulatedDistanceOnTripRestart =
                            tripSessStatus.accumulatedDistanceThisTrip;
                }
                mLocApi->reStartOutdoorTripBatching(ongoingTripDistance, ongoingTripInterval,
                        getBatchingTimeout(), new LocApiResponse(*getContext(),
                        [this, client, sessionId] (LocationError err) {
                    if (err != LOCATION_ERROR_SUCCESS) {
                        LOC_LOGE("%s] New Trip restart failed!", __func__);
                    }
                    reportResponse(client, err, sessionId);
                }));
            } else {
                reportResponse(client, LOCATION_ERROR_SUCCESS, sessionId);
            }
        }));
    }
}

void
BatchingAdapter::stopTripBatchingMultiplex(LocationAPI* client, uint32_t sessionId,
        bool restartNeeded, const BatchingOptions& batchOptions)
{
    LocationError err = LOCATION_ERROR_SUCCESS;

    if (mTripSessions.size() == 1) {
        mLocApi->stopOutdoorTripBatching(true, new LocApiResponse(*getContext(),
                [this, restartNeeded, client, sessionId, batchOptions]
                (LocationError err) {
            if (LOCATION_ERROR_SUCCESS == err) {
                // if stopOutdoorTripBatching is success, unregister for batch full event if this
                // was the last batching session that is interested in batch full event
                if (1 == autoReportBatchingSessionsCount()) {
                    updateEvtMask(LOC_API_ADAPTER_BIT_BATCH_FULL,
                                  LOC_REGISTRATION_MASK_DISABLED);
                }
            }
            stopTripBatchingMultiplexCommon(err, client, sessionId, restartNeeded, batchOptions);
        }));
        return;
    }

    stopTripBatchingMultiplexCommon(err, client, sessionId, restartNeeded, batchOptions);
}

void
BatchingAdapter::stopTripBatchingMultiplexCommon(LocationError err, LocationAPI* client,
        uint32_t sessionId, bool restartNeeded, const BatchingOptions& batchOptions)
{
    auto itt = mTripSessions.find(sessionId);
    TripSessionStatus tripSess = itt->second;
    if (tripSess.tripTBFInterval == mOngoingTripTBFInterval) {
        // trip with ongoing trip interval is stopped
        mTripWithOngoingTBFDropped = true;
    }

    if (tripSess.tripDistance == mOngoingTripDistance) {
        // trip with ongoing trip distance is stopped
        mTripWithOngoingTripDistanceDropped = true;
    }

    mTripSessions.erase(sessionId);

    if (mTripSessions.size() == 0) {
        mOngoingTripDistance = 0;
        mOngoingTripTBFInterval = 0;
    } else {
        restartTripBatching(true);
    }

    if (restartNeeded) {
        eraseBatchingSession(client, sessionId);
        if (batchOptions.batchingMode == BATCHING_MODE_ROUTINE ||
                batchOptions.batchingMode == BATCHING_MODE_NO_AUTO_REPORT) {
            startBatching(client, sessionId, batchOptions);
        } else if (batchOptions.batchingMode == BATCHING_MODE_TRIP) {
            startTripBatchingMultiplex(client, sessionId, batchOptions);
        }
    }
    reportResponse(client, err, sessionId);
}


void
BatchingAdapter::restartTripBatching(bool queryAccumulatedDistance, uint32_t accDist,
        uint32_t numbatchedPos)
{
    // does batch need restart with new trip distance / TBF interval
    uint32_t minRemainingDistance = 0;
    uint32_t minTBFInterval = 0;

    // if no more trips left, stop the ongoing trip
    if (mTripSessions.size() == 0) {
        mLocApi->stopOutdoorTripBatching(true, new LocApiResponse(*getContext(),
                                               [] (LocationError /*err*/) {}));
        mOngoingTripDistance = 0;
        mOngoingTripTBFInterval = 0;
        // unregister for batch full event if there are no more
        // batching session that is interested in batch full event
        if (0 == autoReportBatchingSessionsCount()) {
                updateEvtMask(LOC_API_ADAPTER_BIT_BATCH_FULL,
                              LOC_REGISTRATION_MASK_DISABLED);
        }
        return;
    }

    // record the min trip distance and min tbf interval of all ongoing sessions
    for (auto itt = mTripSessions.begin(); itt != mTripSessions.end(); itt++) {

        TripSessionStatus tripSessStatus = itt->second;

        if ((minRemainingDistance == 0) ||
                (minRemainingDistance > (tripSessStatus.tripDistance
                - tripSessStatus.accumulatedDistanceThisTrip))) {
            minRemainingDistance = tripSessStatus.tripDistance -
                    tripSessStatus.accumulatedDistanceThisTrip;
        }

        if ((minTBFInterval == 0) ||
            (minTBFInterval > tripSessStatus.tripTBFInterval)) {
            minTBFInterval = tripSessStatus.tripTBFInterval;
        }
    }

    mLocApi->queryAccumulatedTripDistance(
            new LocApiResponseData<LocApiBatchData>(*getContext(),
            [this, queryAccumulatedDistance, minRemainingDistance, minTBFInterval, accDist,
            numbatchedPos] (LocationError /*err*/, LocApiBatchData data) {
        bool needsRestart = false;

        uint32_t ongoingTripDistance = mOngoingTripDistance;
        uint32_t ongoingTripInterval = mOngoingTripTBFInterval;
        uint32_t accumulatedDistance = accDist;
        uint32_t numOfBatchedPositions = numbatchedPos;

        if (queryAccumulatedDistance) {
            accumulatedDistance = data.accumulatedDistance;
            numOfBatchedPositions = data.numOfBatchedPositions;
        }

        if ((!mTripWithOngoingTripDistanceDropped) &&
                (ongoingTripDistance - accumulatedDistance != 0)) {
            // if ongoing trip is already not completed still,
            // check the min distance against the remaining distance
            if (minRemainingDistance <
                    (ongoingTripDistance - accumulatedDistance)) {
                ongoingTripDistance = minRemainingDistance;
                needsRestart = true;
            }
        } else if (minRemainingDistance != 0) {
            // else if ongoing trip is already completed / dropped,
            // use the minRemainingDistance of ongoing sessions
            ongoingTripDistance = minRemainingDistance;
            needsRestart = true;
        }

         if ((minTBFInterval < ongoingTripInterval) ||
                    ((minTBFInterval != ongoingTripInterval) &&
                    (mTripWithOngoingTBFDropped))) {
            ongoingTripInterval = minTBFInterval;
            needsRestart = true;
        }

        if (needsRestart) {
            mLocApi->reStartOutdoorTripBatching(ongoingTripDistance, ongoingTripInterval,
                    getBatchingTimeout(), new LocApiResponse(*getContext(),
                    [this, accumulatedDistance, ongoingTripDistance, ongoingTripInterval]
                    (LocationError err) {

                if (err == LOCATION_ERROR_SUCCESS) {
                    for(auto itt = mTripSessions.begin(); itt != mTripSessions.end(); itt++) {
                        TripSessionStatus &tripSessStatus = itt->second;
                        tripSessStatus.accumulatedDistanceThisTrip =
                                tripSessStatus.accumulatedDistanceOnTripRestart +
                                (accumulatedDistance -
                                 tripSessStatus.accumulatedDistanceOngoingBatch);

                        tripSessStatus.accumulatedDistanceOngoingBatch = 0;
                        tripSessStatus.accumulatedDistanceOnTripRestart =
                                tripSessStatus.accumulatedDistanceThisTrip;
                    }

                    mOngoingTripDistance = ongoingTripDistance;
                    mOngoingTripTBFInterval = ongoingTripInterval;
                }
            }));
        }
    }));
}

void
BatchingAdapter::printTripReport()
{
    IF_LOC_LOGD {
        LOC_LOGD("Ongoing Trip Distance = %u, Ongoing Trip TBF Interval = %u",
                mOngoingTripDistance, mOngoingTripTBFInterval);

        for (auto itt = mTripSessions.begin(); itt != mTripSessions.end(); itt++) {
            TripSessionStatus tripSessStatus = itt->second;

            LOC_LOGD("tripDistance:%u tripTBFInterval:%u"
                    " trip accumulated Distance:%u"
                    " trip accumualted distance ongoing batch:%u"
                    " trip accumulated distance on trip restart %u \r\n",
                    tripSessStatus.tripDistance, tripSessStatus.tripTBFInterval,
                    tripSessStatus.accumulatedDistanceThisTrip,
                    tripSessStatus.accumulatedDistanceOngoingBatch,
                    tripSessStatus.accumulatedDistanceOnTripRestart);
        }
    }
}