/*
 * Copyright (C) 2019 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.
 */
#define LOG_TAG "libpixelpowerstats"

#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <pixelpowerstats/AidlStateResidencyDataProvider.h>
#include <time.h>
using android::base::StringPrintf;

static const uint64_t MAX_LATENCY_US = 2000;

namespace android {
namespace hardware {
namespace google {
namespace pixel {
namespace powerstats {

void AidlStateResidencyDataProvider::addEntity(uint32_t id, std::string entityName,
                                               std::vector<std::string> stateNames) {
    std::lock_guard<std::mutex> lock(mLock);
    // Create a new entry in the map of power entities
    mEntityInfos.emplace(entityName, StateSpace{.powerEntityId = id, .stateInfos = {}});

    // Create an entry for each state and assign an Id.
    uint32_t stateId = 0;
    auto &stateInfos = mEntityInfos.at(entityName).stateInfos;
    for (auto stateName : stateNames) {
        stateInfos.emplace(stateName, stateId++);
    }
}

binderStatus AidlStateResidencyDataProvider::unregisterCallbackInternal(
        const sp<IBinder> &callback) {
    if (callback == nullptr) {
        // Callback pointer is null. Return an error.
        return binderStatus::fromExceptionCode(binderStatus::EX_NULL_POINTER, "callback is null");
    }

    bool removed = false;
    std::lock_guard<std::mutex> lock(mLock);

    // Iterate over collection of callbacks and remove the one that matches
    for (auto it = mCallbacks.begin(); it != mCallbacks.end();) {
        if (asBinder(it->second) == callback) {
            LOG(INFO) << "Unregistering callback for " << it->first;
            it = mCallbacks.erase(it);
            removed = true;
        } else {
            it++;
        }
    }
    (void)callback->unlinkToDeath(this);  // ignore errors
    return removed ? binderStatus::ok()
                   : binderStatus::fromExceptionCode(binderStatus::EX_ILLEGAL_ARGUMENT,
                            "callback not found");
}

void AidlStateResidencyDataProvider::binderDied(const wp<IBinder> &who) {
    binderStatus status = unregisterCallbackInternal(who.promote());
    if (!status.isOk()) {
        LOG(ERROR) << __func__ << "failed to unregister callback " << status.toString8();
    }
}

binderStatus AidlStateResidencyDataProvider::unregisterCallback(
        const sp<IPixelPowerStatsCallback> &callback) {
    return unregisterCallbackInternal(asBinder(callback));
}

binderStatus AidlStateResidencyDataProvider::registerCallback(
        const std::string &entityName, const sp<IPixelPowerStatsCallback> &callback) {
    LOG(INFO) << "Registering callback for " << entityName;
    if (callback == nullptr) {
        // Callback pointer is null. Return an error.
        LOG(ERROR) << __func__ << ": "
                   << "Invalid callback. Callback is null";
        return binderStatus::fromExceptionCode(
                binderStatus::EX_NULL_POINTER, "Invalid callback. Callback is null");
    }

    std::lock_guard<std::mutex> lock(mLock);
    if (mEntityInfos.find(entityName) == mEntityInfos.end()) {
        // Could not find the entity associated with this callback. Return an error.
        LOG(ERROR) << __func__ << ": "
                   << "Invalid entity";
        return binderStatus::fromExceptionCode(binderStatus::EX_ILLEGAL_ARGUMENT, "Invalid entity");
    }

    mCallbacks.emplace(entityName, callback);

    // death recipient
    auto linkRet = asBinder(callback)->linkToDeath(this, 0u /* cookie */);
    if (linkRet != android::OK) {
        LOG(WARNING) << __func__ << "Cannot link to death: " << linkRet;
        // ignore the error
    }

    return binderStatus::ok();
}

static binderStatus getStatsTimed(
        const std::pair<std::string, sp<IPixelPowerStatsCallback>> &cb,
        std::vector<android::vendor::powerstats::StateResidencyData> &stats) {
    struct timespec then;
    struct timespec now;

    clock_gettime(CLOCK_BOOTTIME, &then);
    binderStatus status = cb.second->getStats(&stats);
    clock_gettime(CLOCK_BOOTTIME, &now);

    uint64_t time_elapsed_us =
            ((now.tv_sec - then.tv_sec) * 1000000) + ((now.tv_nsec - then.tv_nsec) / 1000);
    if (time_elapsed_us > MAX_LATENCY_US) {
        LOG(WARNING) << "getStats for " << cb.first << " exceeded time allowed: " << time_elapsed_us
                     << "us";
    }
    return status;
}

bool AidlStateResidencyDataProvider::buildResult(
        std::string entityName,
        const std::vector<android::vendor::powerstats::StateResidencyData> &stats,
        PowerEntityStateResidencyResult &result) {
    auto infosEntry = mEntityInfos.find(entityName);
    if (infosEntry == mEntityInfos.end()) {
        LOG(ERROR) << __func__ << " failed: " << entityName << " is not registered.";
        return false;
    }
    auto stateSpace = infosEntry->second;
    result.powerEntityId = stateSpace.powerEntityId;
    size_t numStates = stateSpace.stateInfos.size();
    result.stateResidencyData.resize(numStates);
    size_t numStatesFound = 0;
    for (auto stat = stats.begin(); (numStatesFound < numStates) && (stat != stats.end()); stat++) {
        auto stateInfosEntry = stateSpace.stateInfos.find(stat->state);

        if (stateInfosEntry != stateSpace.stateInfos.end()) {
            PowerEntityStateResidencyData &data = result.stateResidencyData[numStatesFound++];
            data.powerEntityStateId = stateInfosEntry->second;
            data.totalTimeInStateMs = static_cast<uint64_t>(stat->totalTimeInStateMs);
            data.totalStateEntryCount = static_cast<uint64_t>(stat->totalStateEntryCount);
            data.lastEntryTimestampMs = static_cast<uint64_t>(stat->lastEntryTimestampMs);
        } else {
            LOG(WARNING) << "getStats for " << entityName << " returned data for unknown state "
                         << stat->state;
        }
    }

    return (numStatesFound == numStates);
}

bool AidlStateResidencyDataProvider::getResults(
        std::unordered_map<uint32_t, PowerEntityStateResidencyResult> &results) {
    std::lock_guard<std::mutex> lock(mLock);
    // TODO (b/126260512): return cached results if time elapsed isn't large
    size_t numResultsFound = 0;
    size_t numResults = mEntityInfos.size();
    for (auto cb : mCallbacks) {
        std::vector<android::vendor::powerstats::StateResidencyData> stats;

        // Get stats for the current callback
        binderStatus status = getStatsTimed(cb, stats);
        if (!status.isOk()) {
            LOG(ERROR) << "getStats for " << cb.first << " failed: " << status.toString8();
        }

        PowerEntityStateResidencyResult result;
        if (buildResult(cb.first, stats, result)) {
            results.emplace(result.powerEntityId, result);
            numResultsFound++;
        } else {
            LOG(ERROR) << "State residency data missing for " << cb.first;
        }
    }
    bool ret = (numResultsFound == numResults);

    // TODO (b/126260512): Cache results of the call, the return value, and the timestamp.
    return ret;
}

std::vector<PowerEntityStateSpace> AidlStateResidencyDataProvider::getStateSpaces() {
    std::lock_guard<std::mutex> lock(mLock);
    std::vector<PowerEntityStateSpace> stateSpaces;
    stateSpaces.reserve(mEntityInfos.size());

    // Return state space information for every entity for which this is configured to provide data
    for (auto info : mEntityInfos) {
        PowerEntityStateSpace statespace = {
                .powerEntityId = info.second.powerEntityId, .states = {}};
        statespace.states.resize(info.second.stateInfos.size());
        size_t i = 0;
        for (auto state : info.second.stateInfos) {
            statespace.states[i++] = {
                    .powerEntityStateId = state.second, .powerEntityStateName = state.first};
        }
        stateSpaces.emplace_back(statespace);
    }
    return stateSpaces;
}

}  // namespace powerstats
}  // namespace pixel
}  // namespace google
}  // namespace hardware
}  // namespace android