/*
 * 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_NDEBUG 0
#define LOG_TAG "ECOData"

#include "eco/ECOData.h"

#include <android/binder_parcel.h>
#include <android/binder_parcel_utils.h>
#include <inttypes.h>
#include <utils/Errors.h>
#include <utils/Log.h>

#include <string>

#include "eco/ECODataKey.h"
#include "eco/ECOUtils.h"

namespace aidl {
namespace android {
namespace media {
namespace eco {

using namespace ::android;

binder_status_t ECOData::readFromParcel(const AParcel* parcel) {
    if (parcel == nullptr) {
        ALOGE("readFromParcel failed. Parcel pointer can not be null");
        return BAD_VALUE;
    }

    // Reads the data type and time.
    RETURN_STATUS_IF_ERROR(AParcel_readInt32(parcel, &mDataType));
    RETURN_STATUS_IF_ERROR(AParcel_readInt64(parcel, &mDataTimeUs));

    // Reads the number of items.
    uint32_t numOfItems = 0;
    RETURN_STATUS_IF_ERROR(AParcel_readUint32(parcel, &numOfItems));

    // Reads the key-value pairs one by one.
    for (size_t i = 0; i < numOfItems; ++i) {
        // Reads the name of the key.
        std::string name;
        AParcel_readString(parcel, &name, ndk::AParcel_stdStringAllocator);

        int32_t type;
        RETURN_STATUS_IF_ERROR(AParcel_readInt32(parcel, &type));
        switch (static_cast<ValueType>(type)) {
        case kTypeInt32: {
            int32_t value32;
            RETURN_STATUS_IF_ERROR(AParcel_readInt32(parcel, &value32));
            setInt32(name, value32);
            break;
        }
        case kTypeInt64: {
            int64_t value64;
            RETURN_STATUS_IF_ERROR(AParcel_readInt64(parcel, &value64));
            setInt64(name, value64);
            break;
        }
        case kTypeSize: {
            int32_t valueSize;
            RETURN_STATUS_IF_ERROR(AParcel_readInt32(parcel, &valueSize));
            setInt32(name, valueSize);
            break;
        }
        case kTypeFloat: {
            float valueFloat;
            RETURN_STATUS_IF_ERROR(AParcel_readFloat(parcel, &valueFloat));
            setFloat(name, valueFloat);
            break;
        }
        case kTypeDouble: {
            double valueDouble;
            RETURN_STATUS_IF_ERROR(AParcel_readDouble(parcel, &valueDouble));
            setDouble(name, valueDouble);
            break;
        }
        case kTypeString: {
            std::string valueString;
            AParcel_readString(parcel, &valueString, ndk::AParcel_stdStringAllocator);
            setString(name, valueString);
            break;
        }
        case kTypeInt8: {
            int8_t value8;
            RETURN_STATUS_IF_ERROR(AParcel_readByte(parcel, &value8));
            setInt8(name, value8);
            break;
        }
        default: {
            return BAD_TYPE;
        }
        }
    }

    return NO_ERROR;
}

binder_status_t ECOData::writeToParcel(AParcel* parcel) const {
    if (parcel == nullptr) {
        ALOGE("writeToParcel failed. Parcel pointer can not be null");
        return BAD_VALUE;
    }

    // Writes out the data type and time.
    RETURN_STATUS_IF_ERROR(AParcel_writeInt32(parcel, mDataType));
    RETURN_STATUS_IF_ERROR(AParcel_writeInt64(parcel, mDataTimeUs));

    // Writes out number of items.
    RETURN_STATUS_IF_ERROR(AParcel_writeUint32(parcel, int32_t(mKeyValueStore.size())));

    // Writes out the key-value pairs one by one.
    for (const auto& it : mKeyValueStore) {
        // Writes out the key.
        RETURN_STATUS_IF_ERROR(AParcel_writeString(parcel, it.first.c_str(),
                                                   static_cast<int32_t>(it.first.size())));

        // Writes out the data type.
        const ECODataValueType& value = it.second;
        RETURN_STATUS_IF_ERROR(AParcel_writeInt32(parcel, static_cast<int32_t>(value.index())));
        switch (static_cast<ValueType>(value.index())) {
        case kTypeInt32:
            RETURN_STATUS_IF_ERROR(AParcel_writeInt32(parcel, std::get<int32_t>(it.second)));
            break;

        case kTypeInt64:
            RETURN_STATUS_IF_ERROR(AParcel_writeInt64(parcel, std::get<int64_t>(it.second)));
            break;

        case kTypeSize:
            RETURN_STATUS_IF_ERROR(AParcel_writeUint32(parcel, std::get<size_t>(it.second)));
            break;

        case kTypeFloat:
            RETURN_STATUS_IF_ERROR(AParcel_writeFloat(parcel, std::get<float>(it.second)));
            break;

        case kTypeDouble:
            RETURN_STATUS_IF_ERROR(AParcel_writeDouble(parcel, std::get<double>(it.second)));
            break;

        case kTypeString:
            RETURN_STATUS_IF_ERROR(AParcel_writeString(
                    parcel, std::get<std::string>(it.second).c_str(),
                    static_cast<int32_t>(std::get<std::string>(it.second).size())));
            break;

        case kTypeInt8:
            RETURN_STATUS_IF_ERROR(AParcel_writeByte(parcel, std::get<int8_t>(it.second)));
            break;

        default:
            return BAD_TYPE;
        }
    }

    return NO_ERROR;
}

int32_t ECOData::getDataType() const {
    return mDataType;
}

int64_t ECOData::getDataTimeUs() const {
    return mDataTimeUs;
}

// Inserts a new key into store if the key does not exist yet. Otherwise, this will override the
// existing key's value.
ECODataStatus ECOData::setString(const std::string& key, const std::string& value) {
    if (key.empty() || value.empty()) {
        return ECODataStatus::INVALID_ARGUMENT;
    }

    mKeyValueStore[key] = value;

    // TODO(hkuang): Check the valueType is valid for the key.
    return ECODataStatus::OK;
}

ECODataStatus ECOData::findString(const std::string& key, std::string* value) const {
    if (key.empty()) {
        return ECODataStatus::INVALID_ARGUMENT;
    }

    // Check if the key exists.
    if (mKeyValueStore.find(key) == mKeyValueStore.end()) {
        return ECODataStatus::KEY_NOT_EXIST;
    }

    // Safely access the value.
    const std::string& entryValue = std::get<std::string>(mKeyValueStore.at(key));
    value->assign(entryValue);

    return ECODataStatus::OK;
}

// Inserts a new key into store if the key does not exist yet. Otherwise, this will override the
// existing key's value.
template <typename T>
ECODataStatus ECOData::setValue(const std::string& key, T value) {
    if (key.empty()) {
        return ECODataStatus::INVALID_ARGUMENT;
    }

    mKeyValueStore[key] = value;
    return ECODataStatus::OK;
}

template <typename T>
ECODataStatus ECOData::findValue(const std::string& key, T* out) const {
    if (key.empty() || out == nullptr) {
        return ECODataStatus::INVALID_ARGUMENT;
    }

    if (mKeyValueStore.find(key) == mKeyValueStore.end()) {
        return ECODataStatus::KEY_NOT_EXIST;
    }

    // Safely access the value.
    *out = std::get<T>(mKeyValueStore.at(key));

    return ECODataStatus::OK;
}

ECODataStatus ECOData::setInt32(const std::string& key, int32_t value) {
    return setValue<int32_t>(key, value);
}

ECODataStatus ECOData::findInt32(const std::string& key, int32_t* out) const {
    return findValue<int32_t>(key, out);
}

ECODataStatus ECOData::setInt64(const std::string& key, int64_t value) {
    return setValue<int64_t>(key, value);
}

ECODataStatus ECOData::findInt64(const std::string& key, int64_t* out) const {
    return findValue<int64_t>(key, out);
}

ECODataStatus ECOData::setDouble(const std::string& key, double value) {
    return setValue<double>(key, value);
}

ECODataStatus ECOData::findDouble(const std::string& key, double* out) const {
    return findValue<double>(key, out);
}

ECODataStatus ECOData::setSize(const std::string& key, size_t value) {
    return setValue<size_t>(key, value);
}

ECODataStatus ECOData::findSize(const std::string& key, size_t* out) const {
    return findValue<size_t>(key, out);
}

ECODataStatus ECOData::setFloat(const std::string& key, float value) {
    return setValue<float>(key, value);
}

ECODataStatus ECOData::findFloat(const std::string& key, float* out) const {
    return findValue<float>(key, out);
}

ECODataStatus ECOData::setInt8(const std::string& key, int8_t value) {
    return setValue<int8_t>(key, value);
}

ECODataStatus ECOData::findInt8(const std::string& key, int8_t* out) const {
    return findValue<int8_t>(key, out);
}

ECODataStatus ECOData::set(const std::string& key, const ECOData::ECODataValueType& value) {
    if (key.empty()) {
        return ECODataStatus::INVALID_ARGUMENT;
    }
    mKeyValueStore[key] = value;
    return ECODataStatus::OK;
}

ECODataStatus ECOData::find(const std::string& key, ECOData::ECODataValueType* out) const {
    if (key.empty() || out == nullptr) {
        return ECODataStatus::INVALID_ARGUMENT;
    }

    if (mKeyValueStore.find(key) == mKeyValueStore.end()) {
        return ECODataStatus::KEY_NOT_EXIST;
    }

    // Safely access the value.
    *out = mKeyValueStore.at(key);

    return ECODataStatus::OK;
}

std::string ECOData::getDataTypeString() const {
    switch (mDataType) {
    case DATA_TYPE_UNKNOWN:
        return "DATA_TYPE_UNKNOWN";
    case DATA_TYPE_STATS:
        return "DATA_TYPE_STATS";
    case DATA_TYPE_INFO:
        return "DATA_TYPE_INFO";
    case DATA_TYPE_STATS_PROVIDER_CONFIG:
        return "DATA_TYPE_STATS_PROVIDER_CONFIG";
    case DATA_TYPE_INFO_LISTENER_CONFIG:
        return "DATA_TYPE_INFO_LISTENER_CONFIG";
    }
    return {};
}

// TODO(hkuang): Add test for this.
bool ECODataKeyValueIterator::hasNext() {
    if (mIterator == mKeyValueStore.end()) return false;

    if (!mBeginReturned) {
        // mIterator has been initialized to the beginning and
        // hasn't been returned. Do not advance:
        mBeginReturned = true;
    } else {
        std::advance(mIterator, 1);
    }
    return mIterator != mKeyValueStore.end();
}

// TODO(hkuang): Add test for this.
ECOData::ECODataKeyValuePair ECODataKeyValueIterator::next() const {
    return ECOData::ECODataKeyValuePair(mIterator->first, mIterator->second);
}

std::string ECOData::debugString() const {
    std::string s = "ECOData(type = ";

    std::string tmp;
    switch (mDataType) {
    case DATA_TYPE_UNKNOWN:
        tmp = "Unknown";
        break;
    case DATA_TYPE_STATS:
        tmp = "Stats";
        break;
    case DATA_TYPE_INFO:
        tmp = "Info";
        break;
    case DATA_TYPE_STATS_PROVIDER_CONFIG:
        tmp = "Stats provider config";
        break;
    case DATA_TYPE_INFO_LISTENER_CONFIG:
        tmp = "Info listener config";
        break;
    default:
        break;
    }
    s.append(tmp);
    s.append(") = {\n  ");

    // Writes out the key-value pairs one by one.
    for (const auto& it : mKeyValueStore) {
        const size_t SIZE = 100;
        char keyValue[SIZE];
        const ECODataValueType& value = it.second;
        switch (static_cast<ValueType>(value.index())) {
        case kTypeInt32:
            snprintf(keyValue, SIZE, "int32_t %s = %d, ", it.first.c_str(),
                     std::get<int32_t>(it.second));
            break;
        case kTypeInt64:
            snprintf(keyValue, SIZE, "int64_t %s = %" PRId64 ", ", it.first.c_str(),
                     std::get<int64_t>(it.second));
            break;
        case kTypeSize:
            snprintf(keyValue, SIZE, "size_t %s = %zu, ", it.first.c_str(),
                     std::get<size_t>(it.second));
            break;
        case kTypeFloat:
            snprintf(keyValue, SIZE, "float %s = %f, ", it.first.c_str(),
                     std::get<float>(it.second));
            break;
        case kTypeDouble:
            snprintf(keyValue, SIZE, "double %s = %f, ", it.first.c_str(),
                     std::get<double>(it.second));
            break;
        case kTypeString:
            snprintf(keyValue, SIZE, "string %s = %s, ", it.first.c_str(),
                     std::get<std::string>(it.second).c_str());
            break;
        case kTypeInt8:
            snprintf(keyValue, SIZE, "int8_t %s = %d, ", it.first.c_str(),
                     std::get<int8_t>(it.second));
            break;
        default:
            break;
        }
        s.append(keyValue);
    }

    s.append("\n }");

    return s;
}

std::string ECOData::toString() const {
    return debugString();
}

}  // namespace eco
}  // namespace media
}  // namespace android
}  // namespace aidl
