/*
 * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
 * Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/Debug.h>
#include <LibIDL/Types.h>

namespace IDL {

Interface& Context::add_interface(NonnullOwnPtr<Interface> interface)
{
    auto& interface_ref = *interface;

    if (!interface_ref.name.is_empty())
        interfaces.set(interface_ref.name, &interface_ref);

    owned_interfaces.append(move(interface));
    return interface_ref;
}

Interface& Context::add_mixin(NonnullOwnPtr<Interface> interface)
{
    auto& interface_ref = *interface;
    mixins.set(interface_ref.name, &interface_ref);

    for (auto& partial_mixin : partial_mixins) {
        if (partial_mixin->name == interface_ref.name)
            interface_ref.extend_with_partial_interface(*partial_mixin);
    }

    owned_mixins.append(move(interface));
    return interface_ref;
}

Module& Context::add_module(NonnullOwnPtr<Module> module)
{
    auto& module_ref = *module;
    owned_modules.append(move(module));
    return module_ref;
}

Module* Context::find_parsed_module(ByteString const& module_path)
{
    for (auto const& module : owned_modules) {
        if (module->module_own_path == module_path)
            return module.ptr();
    }

    return nullptr;
}

ParameterizedType const& Type::as_parameterized() const
{
    return as<ParameterizedType const>(*this);
}

ParameterizedType& Type::as_parameterized()
{
    return as<ParameterizedType>(*this);
}

UnionType const& Type::as_union() const
{
    return as<UnionType const>(*this);
}

UnionType& Type::as_union()
{
    return as<UnionType>(*this);
}

// https://webidl.spec.whatwg.org/#dfn-includes-a-nullable-type
bool Type::includes_nullable_type() const
{
    // A type includes a nullable type if:
    // - the type is a nullable type, or
    if (is_nullable())
        return true;

    // FIXME: - the type is an annotated type and its inner type is a nullable type, or

    // - the type is a union type and its number of nullable member types is 1.
    if (is_union() && as_union().number_of_nullable_member_types() == 1)
        return true;

    return false;
}

// https://webidl.spec.whatwg.org/#dfn-includes-undefined
bool Type::includes_undefined() const
{
    // A type includes undefined if:
    // - the type is undefined, or
    if (is_undefined())
        return true;

    // - the type is a nullable type and its inner type includes undefined, or
    //   NOTE: We don't treat nullable as its own type, so this is handled by the other cases.

    // FIXME: - the type is an annotated type and its inner type includes undefined, or

    // - the type is a union type and one of its member types includes undefined.
    if (is_union())
        return as_union().member_types().contains([](auto& type) { return type->includes_undefined(); });

    return false;
}

// https://webidl.spec.whatwg.org/#dfn-distinguishable
bool Type::is_distinguishable_from(IDL::Interface const& interface, IDL::Type const& other) const
{
    // 1. If one type includes a nullable type and the other type either includes a nullable type,
    //    is a union type with flattened member types including a dictionary type, or is a dictionary type,
    //    return false.
    if (includes_nullable_type() && (other.includes_nullable_type() || (other.is_union() && any_of(other.as_union().flattened_member_types(), [&interface](auto const& type) { return interface.context.dictionaries.contains(type->name()); })) || interface.context.dictionaries.contains(other.name())))
        return false;

    // 2. If both types are either a union type or nullable union type, return true if each member type
    //    of the one is distinguishable with each member type of the other, or false otherwise.
    if (is_union() && other.is_union()) {
        auto const& this_union = as_union();
        auto const& other_union = other.as_union();

        for (auto& this_member_type : this_union.member_types()) {
            for (auto& other_member_type : other_union.member_types()) {
                if (!this_member_type->is_distinguishable_from(interface, other_member_type))
                    return false;
            }
        }
        return true;
    }

    // 3. If one type is a union type or nullable union type, return true if each member type of the union
    //    type is distinguishable with the non-union type, or false otherwise.
    if (is_union() || other.is_union()) {
        auto const& the_union = is_union() ? as_union() : other.as_union();
        auto const& non_union = is_union() ? other : *this;

        for (auto& member_type : the_union.member_types()) {
            if (!non_union.is_distinguishable_from(interface, member_type))
                return false;
        }
        return true;
    }

    // 4. Consider the two "innermost" types derived by taking each type’s inner type if it is an annotated type,
    //    and then taking its inner type inner type if the result is a nullable type. If these two innermost types
    //    appear or are in categories appearing in the following table and there is a “●” mark in the corresponding
    //    entry or there is a letter in the corresponding entry and the designated additional requirement below the
    //    table is satisfied, then return true. Otherwise return false.
    auto const& this_innermost_type = innermost_type();
    auto const& other_innermost_type = other.innermost_type();

    enum class DistinguishabilityCategory {
        Undefined,
        Boolean,
        Numeric,
        BigInt,
        String,
        Object,
        Symbol,
        InterfaceLike,
        CallbackFunction,
        DictionaryLike,
        SequenceLike,
        __Count
    };

    // See https://webidl.spec.whatwg.org/#distinguishable-table
    // clang-format off
    static constexpr bool table[to_underlying(DistinguishabilityCategory::__Count)][to_underlying(DistinguishabilityCategory::__Count)] {
        {false,  true,  true,  true,  true,  true,  true,  true,  true, false,  true},
        { true, false,  true,  true,  true,  true,  true,  true,  true,  true,  true},
        { true,  true, false,  true,  true,  true,  true,  true,  true,  true,  true},
        { true,  true,  true, false,  true,  true,  true,  true,  true,  true,  true},
        { true,  true,  true,  true, false,  true,  true,  true,  true,  true,  true},
        { true,  true,  true,  true,  true, false,  true, false, false, false, false},
        { true,  true,  true,  true,  true,  true, false,  true,  true,  true,  true},
        { true,  true,  true,  true,  true, false,  true, false,  true,  true,  true},
        { true,  true,  true,  true,  true, false,  true,  true, false, false,  true},
        {false,  true,  true,  true,  true, false,  true,  true, false, false,  true},
        { true,  true,  true,  true,  true, false,  true,  true,  true,  true, false},
    };
    // clang-format on

    auto determine_category = [&interface](Type const& type) -> DistinguishabilityCategory {
        if (type.is_undefined())
            return DistinguishabilityCategory::Undefined;
        if (type.is_boolean())
            return DistinguishabilityCategory::Boolean;
        if (type.is_numeric())
            return DistinguishabilityCategory::Numeric;
        if (type.is_bigint())
            return DistinguishabilityCategory::BigInt;
        if (type.is_string())
            return DistinguishabilityCategory::String;
        if (type.is_object())
            return DistinguishabilityCategory::Object;
        if (type.is_symbol())
            return DistinguishabilityCategory::Symbol;
        // FIXME: InterfaceLike - see below
        // FIXME: CallbackFunction
        // DictionaryLike
        // * Dictionary Types
        // * Record Types
        // FIXME: * Callback Interface Types
        if (interface.context.dictionaries.contains(type.name()) || (type.is_parameterized() && type.name() == "record"sv))
            return DistinguishabilityCategory::DictionaryLike;
        // FIXME: Frozen array types are included in "sequence-like"
        if (type.is_sequence())
            return DistinguishabilityCategory::SequenceLike;

        // FIXME: For lack of a better way of determining if something is an interface type, this just assumes anything we don't recognise is one.
        dbgln_if(IDL_DEBUG, "Unable to determine category for type named '{}', assuming it's an interface type.", type.name());
        return DistinguishabilityCategory::InterfaceLike;
    };

    auto this_distinguishability = determine_category(this_innermost_type);
    auto other_distinguishability = determine_category(other_innermost_type);

    if (this_distinguishability == DistinguishabilityCategory::InterfaceLike && other_distinguishability == DistinguishabilityCategory::InterfaceLike) {
        // The two identified interface-like types are not the same, and
        // FIXME: no single platform object implements both interface-like types.
        return this_innermost_type.name() != other_innermost_type.name();
    }

    return table[to_underlying(this_distinguishability)][to_underlying(other_distinguishability)];
}

// https://webidl.spec.whatwg.org/#dfn-json-types
bool Type::is_json(Context const& context) const
{
    // The JSON types are:
    // - numeric types,
    if (is_numeric())
        return true;

    // - boolean,
    if (is_boolean())
        return true;

    // - string types,
    if (is_string() || context.enumerations.contains(m_name))
        return true;

    // - object,
    if (is_object())
        return true;

    // - nullable types whose inner type is a JSON type,
    // - annotated types whose inner type is a JSON type,
    // NOTE: We don't separate nullable and annotated into separate types.

    // - union types whose member types are JSON types,
    if (is_union()) {
        auto const& union_type = as_union();

        for (auto const& type : union_type.member_types()) {
            if (!type->is_json(context))
                return false;
        }

        return true;
    }

    // - typedefs whose type being given a new name is a JSON type,
    auto typedef_iterator = context.typedefs.find(m_name);
    if (typedef_iterator != context.typedefs.end())
        return typedef_iterator->value.type->is_json(context);

    // - sequence types whose parameterized type is a JSON type,
    // - frozen array types whose parameterized type is a JSON type,
    // - records where all of their values are JSON types,
    if (is_parameterized() && m_name.is_one_of("sequence", "FrozenArray", "record")) {
        auto const& parameterized_type = as_parameterized();

        for (auto const& parameter : parameterized_type.parameters()) {
            if (!parameter->is_json(context))
                return false;
        }

        return true;
    }

    // - dictionary types where the types of all members declared on the dictionary and all its inherited dictionaries are JSON types,
    auto dictionary_iterator = context.dictionaries.find(m_name);
    if (dictionary_iterator != context.dictionaries.end()) {
        auto const& dictionary = dictionary_iterator->value;
        for (auto const& member : dictionary.members) {
            if (!member.type->is_json(context))
                return false;
        }

        return true;
    }

    // - interface types that have a toJSON operation declared on themselves or one of their inherited interfaces.
    auto current_interface = context.interfaces.get(m_name);
    while (current_interface.has_value()) {
        auto to_json_iterator = current_interface.value()->functions.find_if([](IDL::Function const& function) {
            return function.name == "toJSON"sv;
        });

        if (to_json_iterator != current_interface.value()->functions.end())
            return true;

        if (current_interface.value()->parent_name.is_empty())
            break;

        current_interface = context.interfaces.get(current_interface.value()->parent_name);
        VERIFY(current_interface.has_value());
    }

    return false;
}

bool Interface::will_generate_code() const
{
    return !is_partial
        && (!name.is_empty()
            || !own_dictionaries.is_empty()
            || !own_enumerations.is_empty());
}

void EffectiveOverloadSet::remove_all_other_entries()
{
    Vector<Item> new_items;
    new_items.append(m_items[*m_last_matching_item_index]);
    m_items = move(new_items);
}

void Interface::dump()
{
    dbgln("Attributes:");
    for (auto& attribute : attributes) {
        dbgln("  {}{}{}{} {}",
            attribute.inherit ? "inherit " : "",
            attribute.readonly ? "readonly " : "",
            attribute.type->name(),
            attribute.type->is_nullable() ? "?" : "",
            attribute.name);
    }

    dbgln("Functions:");
    for (auto& function : functions) {
        dbgln("  {}{} {}",
            function.return_type->name(),
            function.return_type->is_nullable() ? "?" : "",
            function.name);
        for (auto& parameter : function.parameters) {
            dbgln("    {}{} {}",
                parameter.type->name(),
                parameter.type->is_nullable() ? "?" : "",
                parameter.name);
        }
    }

    dbgln("Static Functions:");
    for (auto& function : static_functions) {
        dbgln("  static {}{} {}",
            function.return_type->name(),
            function.return_type->is_nullable() ? "?" : "",
            function.name);
        for (auto& parameter : function.parameters) {
            dbgln("    {}{} {}",
                parameter.type->name(),
                parameter.type->is_nullable() ? "?" : "",
                parameter.name);
        }
    }
}

void Interface::extend_with_partial_interface(Interface const& partial)
{
    for (auto const& attribute : partial.attributes) {
        auto attribute_copy = attribute;
        attribute_copy.extended_attributes.update(partial.extended_attributes);
        attributes.append(move(attribute_copy));
    }

    for (auto const& static_attribute : partial.static_attributes) {
        auto static_attribute_copy = static_attribute;
        static_attribute_copy.extended_attributes.update(partial.extended_attributes);
        static_attributes.append(move(static_attribute_copy));
    }

    constants.extend(partial.constants);

    for (auto const& function : partial.functions) {
        auto function_copy = function;
        function_copy.extended_attributes.update(partial.extended_attributes);
        functions.append(move(function_copy));
    }

    for (auto const& static_function : partial.static_functions) {
        auto static_function_copy = static_function;
        static_function_copy.extended_attributes.update(partial.extended_attributes);
        static_functions.append(move(static_function_copy));
    }
}

}
