/**
 * Copyright (c) 2021-2022 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 "meta.h"

#include <stdlib.h>

#include <algorithm>
#include <limits>

#include "utils/expected.h"

namespace panda::pandasm {

std::optional<Metadata::Error> Metadata::ValidateSize(std::string_view value) const
{
    constexpr size_t SIZE = 10;

    if (!std::all_of(value.cbegin(), value.cend(), ::isdigit)) {
        return Error("Unsigned integer value expected", Error::Type::INVALID_VALUE);
    }

    strtoul(value.data(), nullptr, SIZE);
    if (errno == ERANGE) {
        return Error("Value is out of range", Error::Type::INVALID_VALUE);
    }

    return {};
}

bool ItemMetadata::IsForeign() const
{
    return GetAttribute("external");
}

static panda::pandasm::Value::Type GetType(std::string_view value)
{
    using VType = panda::pandasm::Value::Type;
    static std::unordered_map<std::string_view, VType> types {
        {"u1", VType::U1},        {"i8", VType::I8},        {"u8", VType::U8},
        {"i16", VType::I16},      {"u16", VType::U16},      {"i32", VType::I32},
        {"u32", VType::U32},      {"i64", VType::I64},      {"u64", VType::U64},
        {"f32", VType::F32},      {"f64", VType::F64},      {"string", VType::STRING},
        {"class", VType::RECORD}, {"enum", VType::ENUM},    {"annotation", VType::ANNOTATION},
        {"array", VType::ARRAY},  {"method", VType::METHOD}};

    return types[value];
}

template <class T>
static T ConvertFromString(std::string_view value, char **end)
{
    static_assert(std::is_integral_v<T>, "T must be integral type");

    constexpr T MIN = std::numeric_limits<T>::min();
    constexpr T MAX = std::numeric_limits<T>::max();

    if constexpr (std::is_signed_v<T>) {
        auto v = ConvertFromString<int64_t>(value, end);
        if (v < MIN || v > MAX) {
            errno = ERANGE;
        }
        return static_cast<T>(v);
    }

    if constexpr (!std::is_signed_v<T>) {
        auto v = ConvertFromString<uint64_t>(value, end);
        if (v < MIN || v > MAX) {
            errno = ERANGE;
        }
        return static_cast<T>(v);
    }
}

template <>
int64_t ConvertFromString(std::string_view value, char **end)
{
    return static_cast<int64_t>(strtoll(value.data(), end, 0));
}

template <>
uint64_t ConvertFromString(std::string_view value, char **end)
{
    return static_cast<uint64_t>(strtoull(value.data(), end, 0));
}

template <>
float ConvertFromString(std::string_view value, char **end)
{
    return strtof(value.data(), end);
}

template <>
double ConvertFromString(std::string_view value, char **end)
{
    return strtod(value.data(), end);
}

template <class T>
static Expected<T, Metadata::Error> ConvertFromString(std::string_view value)
{
    static_assert(std::is_arithmetic_v<T>, "T must be arithmetic type");

    char *end = nullptr;
    auto v = ConvertFromString<T>(value, &end);

    if (end != value.data() + value.length()) {
        return Unexpected(Metadata::Error("Excepted integer literal", Metadata::Error::Type::INVALID_VALUE));
    }

    if (errno == ERANGE) {
        errno = 0;
        return Unexpected(Metadata::Error("Value is out of range", Metadata::Error::Type::INVALID_VALUE));
    }

    return static_cast<T>(v);
}

template <Value::Type type, class T = ValueTypeHelperT<type>>
static Expected<ScalarValue, Metadata::Error> CreatePrimitiveValue(std::string_view value,
                                                                   T max_value = std::numeric_limits<T>::max())
{
    auto res = ConvertFromString<T>(value);
    if (!res) {
        return Unexpected(res.Error());
    }

    auto converted = res.Value();

    if (converted > max_value) {
        return Unexpected(Metadata::Error("Value is out of range", Metadata::Error::Type::INVALID_VALUE));
    }

    return ScalarValue::Create<type>(converted);
}

static Expected<ScalarValue, Metadata::Error> CreateValue(
    Value::Type type, std::string_view value,
    const std::unordered_map<std::string, std::unique_ptr<AnnotationData>> &annotation_id_map = {})
{
    switch (type) {
        case Value::Type::U1: {
            return CreatePrimitiveValue<Value::Type::U1>(value, 1);
        }
        case Value::Type::I8: {
            return CreatePrimitiveValue<Value::Type::I8>(value);
        }
        case Value::Type::U8: {
            return CreatePrimitiveValue<Value::Type::U8>(value);
        }
        case Value::Type::I16: {
            return CreatePrimitiveValue<Value::Type::I16>(value);
        }
        case Value::Type::U16: {
            return CreatePrimitiveValue<Value::Type::U16>(value);
        }
        case Value::Type::I32: {
            return CreatePrimitiveValue<Value::Type::I32>(value);
        }
        case Value::Type::U32: {
            return CreatePrimitiveValue<Value::Type::U32>(value);
        }
        case Value::Type::I64: {
            return CreatePrimitiveValue<Value::Type::I64>(value);
        }
        case Value::Type::U64: {
            return CreatePrimitiveValue<Value::Type::U64>(value);
        }
        case Value::Type::F32: {
            return CreatePrimitiveValue<Value::Type::F32>(value);
        }
        case Value::Type::F64: {
            return CreatePrimitiveValue<Value::Type::F64>(value);
        }
        case Value::Type::STRING: {
            return ScalarValue::Create<Value::Type::STRING>(value);
        }
        case Value::Type::RECORD: {
            return ScalarValue::Create<Value::Type::RECORD>(Type::FromName(value));
        }
        case Value::Type::METHOD: {
            return ScalarValue::Create<Value::Type::METHOD>(value);
        }
        case Value::Type::ENUM: {
            return ScalarValue::Create<Value::Type::ENUM>(value);
        }
        case Value::Type::ANNOTATION: {
            auto it = annotation_id_map.find(std::string(value));
            if (it == annotation_id_map.cend()) {
                return Unexpected(Metadata::Error("Unknown annotation id", Metadata::Error::Type::INVALID_VALUE));
            }

            auto annotation_value = it->second.get();
            return ScalarValue::Create<Value::Type::ANNOTATION>(*annotation_value);
        }
        default: {
            break;
        }
    }

    UNREACHABLE();
}

std::optional<Metadata::Error> AnnotationMetadata::AnnotationElementBuilder::AddValue(
    std::string_view value, const std::unordered_map<std::string, std::unique_ptr<AnnotationData>> &annotation_id_map)
{
    ASSERT(type_.has_value());

    auto type = type_.value();

    if (type == Value::Type::ARRAY) {
        ASSERT(component_type_.has_value());
        type = component_type_.value();
    }

    auto res = CreateValue(type, value, annotation_id_map);
    if (!res) {
        return res.Error();
    }

    values_.push_back(res.Value());

    return {};
}

std::optional<Metadata::Error> AnnotationMetadata::Store(std::string_view attribute)
{
    if (IsParseAnnotationElement() && !annotation_element_builder_.IsCompleted()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Annotation element isn't completely defined",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    if (IsParseAnnotation()) {
        ResetAnnotationBuilder();
    }

    return Metadata::Store(attribute);
}

std::optional<Metadata::Error> AnnotationMetadata::MeetExpRecordAttribute(std::string_view attribute,
                                                                          std::string_view value)
{
    if (IsParseAnnotationElement() && !annotation_element_builder_.IsCompleted()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Annotation element isn't completely defined",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    InitializeAnnotationBuilder(value);

    return {};
}

std::optional<Metadata::Error> AnnotationMetadata::MeetExpIdAttribute(std::string_view attribute,
                                                                      std::string_view value)
{
    if (!IsParseAnnotation() || IsParseAnnotationElement()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Annotation record attribute must be defined first",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    if (annotation_builder_.HasId()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Annotation id attribute already defined",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    annotation_builder_.SetId(value);

    return {};
}

std::optional<Metadata::Error> AnnotationMetadata::MeetExpElementNameAttribute(std::string_view attribute,
                                                                               std::string_view value)
{
    if (!IsParseAnnotation()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Annotation record attribute must be defined first",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    if (IsParseAnnotationElement() && !annotation_element_builder_.IsCompleted()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Previous annotation element isn't defined completely",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    InitializeAnnotationElementBuilder(value);

    return {};
}

std::optional<Metadata::Error> AnnotationMetadata::MeetExpElementTypeAttribute(std::string_view attribute,
                                                                               std::string_view value)
{
    if (!IsParseAnnotationElement()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Annotation element name attribute must be defined first",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    if (annotation_element_builder_.IsTypeSet()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Annotation element type attribute already defined",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    annotation_element_builder_.SetType(GetType(value));

    return {};
}

std::optional<Metadata::Error> AnnotationMetadata::MeetExpElementArrayComponentTypeAttribute(std::string_view attribute,
                                                                                             std::string_view value)
{
    if (!IsParseAnnotationElement()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Annotation element name attribute must be defined first",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    if (!annotation_element_builder_.IsArray()) {
        return Error(std::string("Unexpected attribute '").append(attribute) + "'. Annotation element type isn't array",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    if (annotation_element_builder_.IsComponentTypeSet()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Annotation element array component type attribute already defined",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    annotation_element_builder_.SetComponentType(GetType(value));

    return {};
}

std::optional<Metadata::Error> AnnotationMetadata::MeetExpElementValueAttribute(std::string_view attribute,
                                                                                std::string_view value)
{
    if (!IsParseAnnotationElement()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Annotation element name attribute must be defined first",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    if (!annotation_element_builder_.IsTypeSet()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Annotation element type attribute isn't defined",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    if (annotation_element_builder_.IsArray() && !annotation_element_builder_.IsComponentTypeSet()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Annotation element array component type attribute isn't defined",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    if (!annotation_element_builder_.IsArray() && annotation_element_builder_.IsCompleted()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Annotation element is completely defined",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    return annotation_element_builder_.AddValue(value, id_map_);
}

std::optional<Metadata::Error> AnnotationMetadata::StoreValue(std::string_view attribute, std::string_view value)
{
    auto err = Metadata::StoreValue(attribute, value);
    if (err) {
        return err;
    }

    if (IsAnnotationRecordAttribute(attribute)) {
        return MeetExpRecordAttribute(attribute, value);
    }

    if (IsAnnotationIdAttribute(attribute)) {
        return MeetExpIdAttribute(attribute, value);
    }

    if (IsAnnotationElementNameAttribute(attribute)) {
        return MeetExpElementNameAttribute(attribute, value);
    }

    if (IsAnnotationElementTypeAttribute(attribute)) {
        return MeetExpElementTypeAttribute(attribute, value);
    }

    if (IsAnnotationElementArrayComponentTypeAttribute(attribute)) {
        return MeetExpElementArrayComponentTypeAttribute(attribute, value);
    }

    if (IsAnnotationElementValueAttribute(attribute)) {
        return MeetExpElementValueAttribute(attribute, value);
    }

    if (IsParseAnnotationElement() && !annotation_element_builder_.IsCompleted()) {
        return Error(std::string("Unexpected attribute '").append(attribute) +
                         "'. Annotation element isn't completely defined",
                     Error::Type::UNEXPECTED_ATTRIBUTE);
    }

    if (IsParseAnnotation()) {
        ResetAnnotationBuilder();
    }

    return {};
}

std::optional<Metadata::Error> AnnotationMetadata::ValidateData()
{
    if (IsParseAnnotationElement() && !annotation_element_builder_.IsCompleted()) {
        return Error("Annotation element isn't completely defined", Error::Type::MISSING_ATTRIBUTE);
    }

    if (IsParseAnnotation()) {
        ResetAnnotationBuilder();
    }

    return Metadata::ValidateData();
}

std::string RecordMetadata::GetBase() const
{
    return "";
}

std::vector<std::string> RecordMetadata::GetInterfaces() const
{
    return {};
}

bool RecordMetadata::IsAnnotation() const
{
    return false;
}

bool RecordMetadata::IsRuntimeAnnotation() const
{
    return false;
}

bool RecordMetadata::IsTypeAnnotation() const
{
    return false;
}

bool RecordMetadata::IsRuntimeTypeAnnotation() const
{
    return false;
}

bool FunctionMetadata::IsCtor() const
{
    return GetAttribute("ctor");
}

bool FunctionMetadata::IsCctor() const
{
    return GetAttribute("cctor");
}

std::optional<Metadata::Error> FieldMetadata::StoreValue(std::string_view attribute, std::string_view value)
{
    auto err = ItemMetadata::StoreValue(attribute, value);
    if (err) {
        return err;
    }

    if (IsValueAttribute(attribute)) {
        Value::Type value_type;
        if (!field_type_.IsObject()) {
            value_type = GetType(field_type_.GetName());
        } else {
            value_type = Value::Type::STRING;
        }

        auto res = CreateValue(value_type, value);
        if (!res) {
            return res.Error();
        }

        value_ = res.Value();
    }

    return {};
}

#include <meta_gen.h>

}  // namespace panda::pandasm