/*
 * Copyright (C) 2017 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 "VehiclePropertyStore"
#include <log/log.h>
#include <utils/SystemClock.h>

#include <common/include/vhal_v2_0/VehicleUtils.h>
#include "VehiclePropertyStore.h"

namespace android {
namespace hardware {
namespace automotive {
namespace vehicle {
namespace V2_0 {

bool VehiclePropertyStore::RecordId::operator==(const VehiclePropertyStore::RecordId& other) const {
    return prop == other.prop && area == other.area && token == other.token;
}

bool VehiclePropertyStore::RecordId::operator<(const VehiclePropertyStore::RecordId& other) const  {
    return prop < other.prop
           || (prop == other.prop && area < other.area)
           || (prop == other.prop && area == other.area && token < other.token);
}

void VehiclePropertyStore::registerProperty(const VehiclePropConfig& config,
                                            VehiclePropertyStore::TokenFunction tokenFunc) {
    MuxGuard g(mLock);
    mConfigs.insert({ config.prop, RecordConfig { config, tokenFunc } });
}

bool VehiclePropertyStore::writeValueLocked(const VehiclePropValue& propValue, bool updateStatus) {
    if (!mConfigs.count(propValue.prop)) return false;

    RecordId recId = getRecordIdLocked(propValue);
    VehiclePropValue* valueToUpdate = const_cast<VehiclePropValue*>(getValueOrNullLocked(recId));
    if (valueToUpdate == nullptr) {
        mPropertyValues.insert({ recId, propValue });
        return true;
    }

    // propValue is outdated and drops it.
    if (valueToUpdate->timestamp > propValue.timestamp) {
        return false;
    }
    // update the propertyValue.
    // The timestamp in propertyStore should only be updated by the server side. It indicates
    // the time when the event is generated by the server.
    valueToUpdate->timestamp = propValue.timestamp;
    valueToUpdate->value = propValue.value;
    if (updateStatus) {
        valueToUpdate->status = propValue.status;
    }
    return true;
}

bool VehiclePropertyStore::writeValue(const VehiclePropValue& propValue, bool updateStatus) {
    MuxGuard g(mLock);

    return writeValueLocked(propValue, updateStatus);
}

bool VehiclePropertyStore::writeValueWithCurrentTimestamp(VehiclePropValue* propValuePtr,
                                                          bool updateStatus) {
    MuxGuard g(mLock);

    propValuePtr->timestamp = elapsedRealtimeNano();
    return writeValueLocked(*propValuePtr, updateStatus);
}

std::unique_ptr<VehiclePropValue> VehiclePropertyStore::refreshTimestamp(int32_t propId,
                                                                         int32_t areaId) {
    MuxGuard g(mLock);
    RecordId recId = getRecordIdLocked(VehiclePropValue{
            .prop = propId,
            .areaId = areaId,
    });
    auto it = mPropertyValues.find(recId);
    if (it == mPropertyValues.end()) {
        return nullptr;
    }

    it->second.timestamp = elapsedRealtimeNano();
    return std::make_unique<VehiclePropValue>(it->second);
}

void VehiclePropertyStore::removeValue(const VehiclePropValue& propValue) {
    MuxGuard g(mLock);
    RecordId recId = getRecordIdLocked(propValue);
    auto it = mPropertyValues.find(recId);
    if (it != mPropertyValues.end()) {
        mPropertyValues.erase(it);
    }
}

void VehiclePropertyStore::removeValuesForProperty(int32_t propId) {
    MuxGuard g(mLock);
    auto range = findRangeLocked(propId);
    mPropertyValues.erase(range.first, range.second);
}

std::vector<VehiclePropValue> VehiclePropertyStore::readAllValues() const {
    MuxGuard g(mLock);
    std::vector<VehiclePropValue> allValues;
    allValues.reserve(mPropertyValues.size());
    for (auto&& it : mPropertyValues) {
        allValues.push_back(it.second);
    }
    return allValues;
}

std::vector<VehiclePropValue> VehiclePropertyStore::readValuesForProperty(int32_t propId) const {
    std::vector<VehiclePropValue> values;
    MuxGuard g(mLock);
    auto range = findRangeLocked(propId);
    for (auto it = range.first; it != range.second; ++it) {
        values.push_back(it->second);
    }

    return values;
}

std::unique_ptr<VehiclePropValue> VehiclePropertyStore::readValueOrNull(
        const VehiclePropValue& request) const {
    MuxGuard g(mLock);
    RecordId recId = getRecordIdLocked(request);
    const VehiclePropValue* internalValue = getValueOrNullLocked(recId);
    return internalValue ? std::make_unique<VehiclePropValue>(*internalValue) : nullptr;
}

std::unique_ptr<VehiclePropValue> VehiclePropertyStore::readValueOrNull(
        int32_t prop, int32_t area, int64_t token) const {
    RecordId recId = {prop, isGlobalProp(prop) ? 0 : area, token };
    MuxGuard g(mLock);
    const VehiclePropValue* internalValue = getValueOrNullLocked(recId);
    return internalValue ? std::make_unique<VehiclePropValue>(*internalValue) : nullptr;
}


std::vector<VehiclePropConfig> VehiclePropertyStore::getAllConfigs() const {
    MuxGuard g(mLock);
    std::vector<VehiclePropConfig> configs;
    configs.reserve(mConfigs.size());
    for (auto&& recordConfigIt: mConfigs) {
        configs.push_back(recordConfigIt.second.propConfig);
    }
    return configs;
}

const VehiclePropConfig* VehiclePropertyStore::getConfigOrNull(int32_t propId) const {
    MuxGuard g(mLock);
    auto recordConfigIt = mConfigs.find(propId);
    return recordConfigIt != mConfigs.end() ? &recordConfigIt->second.propConfig : nullptr;
}

const VehiclePropConfig* VehiclePropertyStore::getConfigOrDie(int32_t propId) const {
    auto cfg = getConfigOrNull(propId);
    if (!cfg) {
        ALOGW("%s: config not found for property: 0x%x", __func__, propId);
        abort();
    }
    return cfg;
}

VehiclePropertyStore::RecordId VehiclePropertyStore::getRecordIdLocked(
        const VehiclePropValue& valuePrototype) const {
    RecordId recId = {
        .prop = valuePrototype.prop,
        .area = isGlobalProp(valuePrototype.prop) ? 0 : valuePrototype.areaId,
        .token = 0
    };

    auto it = mConfigs.find(recId.prop);
    if (it == mConfigs.end()) return {};

    if (it->second.tokenFunction != nullptr) {
        recId.token = it->second.tokenFunction(valuePrototype);
    }
    return recId;
}

const VehiclePropValue* VehiclePropertyStore::getValueOrNullLocked(
        const VehiclePropertyStore::RecordId& recId) const  {
    auto it = mPropertyValues.find(recId);
    return it == mPropertyValues.end() ? nullptr : &it->second;
}

VehiclePropertyStore::PropertyMapRange VehiclePropertyStore::findRangeLocked(int32_t propId) const {
    // Based on the fact that mPropertyValues is a sorted map by RecordId.
    auto beginIt = mPropertyValues.lower_bound( RecordId { propId, INT32_MIN, 0 });
    auto endIt = mPropertyValues.lower_bound( RecordId { propId + 1, INT32_MIN, 0 });

    return  PropertyMapRange { beginIt, endIt };
}

}  // namespace V2_0
}  // namespace vehicle
}  // namespace automotive
}  // namespace hardware
}  // namespace android
