/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * 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 "method_data_converter.h"

#include <array>
#include <cmath>
#include <cinttypes>
#include <cstdint>

#include "log.h"
#include "plugins/interfaces/native/inner_api/plugin_utils_napi.h"

namespace OHOS::Plugin::Bridge {
static constexpr int64_t MAX_INT_32 = static_cast<int64_t>(std::numeric_limits<int32_t>::max());
static constexpr int64_t MIN_INT_32 = static_cast<int64_t>(std::numeric_limits<int32_t>::min());
static constexpr double DOUBLE_MIN_VALUE = 0.00001;

CodecableValue MethodDataConverter::ConvertToCodecableValue(napi_env env, napi_value value)
{
    bool isArrayBuffer = PluginUtilsNApi::IsArrayBuffer(env, value);
    if (isArrayBuffer) {
        std::vector<uint8_t> vector;
        PluginUtilsNApi::GetArrayBuffer(env, value, vector);
        return CodecableValue(vector);
    }

    napi_valuetype valueType = PluginUtilsNApi::GetValueType(env, value);
    switch (valueType) {
        case napi_boolean: {
            return CodecableValue(PluginUtilsNApi::GetBool(env, value));
        }
        case napi_number: {
            int32_t int32Value = PluginUtilsNApi::GetCInt32(value, env);
            int64_t int64Value = PluginUtilsNApi::GetCInt64(value, env);
            double doubleValue = PluginUtilsNApi::GetDouble(env, value);

            const bool isDouble = std::fabs(doubleValue - static_cast<double>(int64Value)) >= DOUBLE_MIN_VALUE;
            if (isDouble) {
                return CodecableValue(doubleValue);
            }

            if (int64Value > MAX_INT_32 || int64Value < MIN_INT_32) {
                return CodecableValue(int64Value);
            }
            return CodecableValue(static_cast<int32_t>(int64Value));
        }
        case napi_string: {
            return CodecableValue(PluginUtilsNApi::GetStringFromValueUtf8(env, value));
        }
        case napi_object: {
            if (PluginUtilsNApi::IsArray(env, value)) {
                return GainListValue(env, value);
            }
            return GainMapValue(env, value);
        }
        default:
            break;
    }

    return CodecableValue();
}

CodecableValue MethodDataConverter::ConvertToCodecableValue(napi_env env, const size_t& argc, const napi_value* argv)
{
    if (argc == 0 || argv == nullptr) {
        CodecableList list;
        return CodecableValue(list);
    }

    CodecableList list;
    list.reserve(argc);
    for (size_t i = 0; i < argc; i++) {
        list.push_back(ConvertToCodecableValue(env, argv[i]));
    }

    return CodecableValue(list);
}

napi_value MethodDataConverter::ConvertToNapiValue(napi_env env, const CodecableValue& value)
{
    switch (static_cast<CodecableType>(value.index())) {
        case CodecableType::T_BOOL:
            return PluginUtilsNApi::CreateBoolean(env, std::get<bool>(value));
        case CodecableType::T_INT32:
            return PluginUtilsNApi::CreateInt32(env, std::get<int32_t>(value));
        case CodecableType::T_INT64: {
            int64_t temp = std::get<int64_t>(value);
            return PluginUtilsNApi::CreateDouble(env, static_cast<double>(temp));
        }
        case CodecableType::T_DOUBLE:
            return PluginUtilsNApi::CreateDouble(env, std::get<double>(value));
        case CodecableType::T_STRING:
            return PluginUtilsNApi::CreateStringUtf8(env, std::get<std::string>(value));
        case CodecableType::T_LIST_UINT8:
            return PluginUtilsNApi::CreateArrayBuffer(env, std::get<std::vector<uint8_t>>(value));
        case CodecableType::T_LIST_BOOL: {
            return CreateListBoolValue(env, value);
        }
        case CodecableType::T_LIST_INT32: {
            return CreateListInt32Value(env, value);
        }
        case CodecableType::T_LIST_INT64: {
            return CreateListInt64Value(env, value);
        }
        case CodecableType::T_LIST_DOUBLE: {
            return CreateListDoubleValue(env, value);
        }
        case CodecableType::T_LIST_STRING: {
            return CreateListStringValue(env, value);
        }
        case CodecableType::T_MAP: {
            return CreateMapValue(env, value);
        }
        default:
            return PluginUtilsNApi::CreateNull(env);
    }
}

napi_value MethodDataConverter::CreateListBoolValue(napi_env env, const CodecableValue& value)
{
    napi_value result = nullptr;
    result = PluginUtilsNApi::CreateArray(env);
    const auto& vector = std::get<std::vector<bool>>(value);
    int32_t i = 0;
    for (const auto& item : vector) {
        napi_value itemValue = PluginUtilsNApi::CreateBoolean(env, item);
        PluginUtilsNApi::SetSelementToArray(env, result, i, itemValue);
        i++;
    }
    return result;
}

napi_value MethodDataConverter::CreateListInt32Value(napi_env env, const CodecableValue& value)
{
    napi_value result = nullptr;
    result = PluginUtilsNApi::CreateArray(env);
    const auto& vector = std::get<std::vector<int32_t>>(value);
    int32_t i = 0;
    for (const auto& item : vector) {
        napi_value itemValue = PluginUtilsNApi::CreateInt32(env, item);
        PluginUtilsNApi::SetSelementToArray(env, result, i, itemValue);
        i++;
    }
    return result;
}

napi_value MethodDataConverter::CreateListInt64Value(napi_env env, const CodecableValue& value)
{
    napi_value result = nullptr;
    result = PluginUtilsNApi::CreateArray(env);
    const auto& vector = std::get<std::vector<int64_t>>(value);
    int32_t i = 0;
    for (const auto& item : vector) {
        napi_value itemValue = PluginUtilsNApi::CreateDouble(env, static_cast<double>(item));
        PluginUtilsNApi::SetSelementToArray(env, result, i, itemValue);
        i++;
    }
    return result;
}

napi_value MethodDataConverter::CreateListDoubleValue(napi_env env, const CodecableValue& value)
{
    napi_value result = nullptr;
    result = PluginUtilsNApi::CreateArray(env);
    const auto& vector = std::get<std::vector<double>>(value);
    int32_t i = 0;
    for (const auto& item : vector) {
        napi_value itemValue = PluginUtilsNApi::CreateDouble(env, item);
        PluginUtilsNApi::SetSelementToArray(env, result, i, itemValue);
        i++;
    }
    return result;
}

napi_value MethodDataConverter::CreateListStringValue(napi_env env, const CodecableValue& value)
{
    napi_value result = nullptr;
    result = PluginUtilsNApi::CreateArray(env);
    const auto& vector = std::get<std::vector<std::string>>(value);
    int32_t i = 0;
    for (const auto& item : vector) {
        napi_value itemValue = PluginUtilsNApi::CreateStringUtf8(env, item);
        PluginUtilsNApi::SetSelementToArray(env, result, i, itemValue);
        i++;
    }
    return result;
}

napi_value MethodDataConverter::CreateMapValue(napi_env env, const CodecableValue& value)
{
    napi_value result = nullptr;
    result = PluginUtilsNApi::CreateObject(env);
    const auto& map = std::get<CodecableMap>(value);
    for (const auto& pair : map) {
        PluginUtilsNApi::SetNamedProperty(
            env, result, std::get<std::string>(pair.first), ConvertToNapiValue(env, pair.second));
    }
    return result;
}

void MethodDataConverter::ConvertToNapiValues(napi_env env, const CodecableValue& value, size_t& argc, napi_value* argv)
{
    size_t argNum = 0;
    if (!std::holds_alternative<CodecableList>(value)) {
        return;
    }
    CodecableList list = std::get<CodecableList>(value);
    for (const auto& item : list) {
        argv[argNum] = ConvertToNapiValue(env, item);
        argNum++;
        if (argNum >= argc) {
            break;
        }
    }
    argc = argNum;
}

CodecableValue MethodDataConverter::GainListValue(napi_env env, napi_value value)
{
    uint32_t length = 0;
    napi_get_array_length(env, value, &length);
    napi_value firstValue = nullptr;
    napi_get_element(env, value, 0, &firstValue);
    napi_valuetype type = PluginUtilsNApi::GetValueType(env, firstValue);
    switch (type) {
        case napi_boolean: {
            return GainListBoolValue(env, value, length);
        }
        case napi_number: {
            int32_t intValue = PluginUtilsNApi::GetCInt32(firstValue, env);
            double numberValue = PluginUtilsNApi::GetDouble(env, firstValue);
            bool isDouble = numberValue - intValue > DOUBLE_MIN_VALUE;
            if (isDouble) {
                return GainListDoubleValue(env, value, length);
            } else {
                return GainListInt32Value(env, value, length);
            }
        }
        case napi_string: {
            return GainListStringValue(env, value, length);
        }
        default:
            return CodecableValue();
    }
}

CodecableValue MethodDataConverter::GainListBoolValue(napi_env env, napi_value value, uint32_t length)
{
    std::vector<bool> vector;
    vector.reserve(length);
    for (uint32_t i = 0; i < length; i++) {
        napi_value elementValue;
        napi_get_element(env, value, i, &elementValue);
        vector.push_back(PluginUtilsNApi::GetBool(env, elementValue));
    }
    return CodecableValue(vector);
}

CodecableValue MethodDataConverter::GainListStringValue(napi_env env, napi_value value, uint32_t length)
{
    std::vector<std::string> vector;
    vector.reserve(length);
    for (uint32_t i = 0; i < length; i++) {
        napi_value elementValue;
        napi_get_element(env, value, i, &elementValue);
        vector.push_back(PluginUtilsNApi::GetStringFromValueUtf8(env, elementValue));
    }
    return CodecableValue(vector);
}

CodecableValue MethodDataConverter::GainListInt32Value(napi_env env, napi_value value, uint32_t length)
{
    std::vector<int32_t> vector;
    vector.reserve(length);
    for (uint32_t i = 0; i < length; i++) {
        napi_value elementValue;
        napi_get_element(env, value, i, &elementValue);
        vector.push_back(PluginUtilsNApi::GetCInt32(elementValue, env));
    }
    return CodecableValue(vector);
}

CodecableValue MethodDataConverter::GainListDoubleValue(napi_env env, napi_value value, uint32_t length)
{
    std::vector<double> vector;
    vector.reserve(length);
    for (uint32_t i = 0; i < length; i++) {
        napi_value elementValue;
        napi_get_element(env, value, i, &elementValue);
        vector.push_back(PluginUtilsNApi::GetDouble(env, elementValue));
    }
    return CodecableValue(vector);
}

CodecableValue MethodDataConverter::GainMapValue(napi_env env, napi_value value)
{
    CodecableMap mapValue;
    std::vector<std::string> props;
    if (PluginUtilsNApi::GetPropertyNames(env, value, props)) {
        for (auto prop : props) {
            CodecableValue key = CodecableValue(prop);
            CodecableValue itemValue =
                ConvertToCodecableValue(env, PluginUtilsNApi::GetNamedProperty(env, value, prop));
            mapValue.emplace(std::move(key), std::move(itemValue));
        }
    }
    return CodecableValue(mapValue);
}
} // namespace OHOS::Plugin::Bridge