/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "SensorsHidlEnvironmentV1_0.h"

#include <log/log.h>

#include <vector>

using ::android::hardware::hidl_vec;
using ::android::hardware::sensors::V1_0::ISensors;
using ::android::hardware::sensors::V1_0::Result;
using ::android::hardware::sensors::V1_0::SensorInfo;

void SensorsHidlEnvironmentV1_0::TearDown() {
    mStopThread = true;
    if (mPollThread.joinable()) {
        mPollThread.detach();
    }
}

bool SensorsHidlEnvironmentV1_0::resetHal() {
    // wait upto 100ms * 10 = 1s for hidl service.
    constexpr auto RETRY_DELAY = std::chrono::milliseconds(100);

    std::string step;
    bool succeed = false;
    for (size_t retry = 10; retry > 0; --retry) {
        // this do ... while is for easy error handling
        do {
            step = "getService()";
            sensors = ISensors::getService(mServiceName);
            if (sensors == nullptr) {
                break;
            }

            step = "poll() check";
            // Poke ISensor service. If it has lingering connection from previous generation of
            // system server, it will kill itself. There is no intention to handle the poll result,
            // which will be done since the size is 0.
            if (!sensors->poll(0, [](auto, const auto&, const auto&) {}).isOk()) {
                break;
            }

            step = "getSensorList";
            std::vector<SensorInfo> sensorList;
            if (!sensors
                     ->getSensorsList([&](const hidl_vec<SensorInfo>& list) {
                         sensorList.reserve(list.size());
                         for (size_t i = 0; i < list.size(); ++i) {
                             sensorList.push_back(list[i]);
                         }
                     })
                     .isOk()) {
                break;
            }

            // stop each sensor individually
            step = "stop each sensor";
            bool ok = true;
            for (const auto& i : sensorList) {
                if (!sensors->activate(i.sensorHandle, false).isOk()) {
                    ok = false;
                    break;
                }
            }
            if (!ok) {
                break;
            }

            // mark it done
            step = "done";
            succeed = true;
        } while (0);

        if (succeed) {
            return true;
        }

        // Delay 100ms before retry, hidl service is expected to come up in short time after crash.
        ALOGI("%s unsuccessful, try again soon (remaining retry %zu).", step.c_str(), retry - 1);
        std::this_thread::sleep_for(RETRY_DELAY);
    }

    sensors = nullptr;
    return false;
}

void SensorsHidlEnvironmentV1_0::startPollingThread() {
    mStopThread = false;
    mPollThread = std::thread(pollingThread, this, std::ref(mStopThread));
    mEvents.reserve(128);
}

void SensorsHidlEnvironmentV1_0::pollingThread(SensorsHidlEnvironmentV1_0* env,
                                               std::atomic_bool& stop) {
    ALOGD("polling thread start");

    while (!stop) {
        if (!env->sensors
                     ->poll(64,
                            [&](auto result, const auto& events, const auto& dynamicSensorsAdded) {
                                if (result != Result::OK ||
                                    (events.size() == 0 && dynamicSensorsAdded.size() == 0) ||
                                    stop) {
                                    stop = true;
                                    return;
                                }

                                for (const auto& e : events) {
                                    env->addEvent(e);
                                }
                            })
                     .isOk()) {
            break;
        }
    }
    ALOGD("polling thread end");
}