#include <Evaluator/Value/interface.hpp>
#include <Evaluator/Value/structType.hpp>
#include <Evaluator/Value/value_forward.hpp>
#include <Evaluator/Value/Type.hpp>
#include <Evaluator/Value/value.hpp>
#include <Evaluator/Context/context.hpp>

// #include <iostream>

namespace Fig
{

    TypeInfo::TypeInfo() : // only allow use in evaluate time !! <---- dynamic type system requirement
        id(1), name(FString(u8"Any"))
    {
    }
    TypeInfo::TypeInfo(const FString &_name, bool reg)
    {
        static size_t id_count = 0;
        name = _name;
        // std::cerr << "TypeInfo constructor called for type name: " << name.toBasicString() << "\n";
        if (reg)
        {
            getTypeMap()[name] = ++id_count;
            id = id_count;
        }
        else
        {
            if (!getTypeMap().contains(_name))
            {
                throw RuntimeError(FString(std::format("No type named '{}'", _name.toBasicString())));
                // *this = ValueType::String;
            }
            id = getTypeMap().at(name); // may throw
        }
    }

    size_t ValueKeyHash::operator()(const ValueKey &key) const
    {
        {
            ObjectPtr value = key.value;
            const TypeInfo &type = value->getTypeInfo();

            if (type == ValueType::Int) { return std::hash<ValueType::IntClass>{}(value->as<ValueType::IntClass>()); }
            if (type == ValueType::Double)
            {
                return std::hash<ValueType::DoubleClass>{}(value->as<ValueType::DoubleClass>());
            }
            if (type == ValueType::String)
            {
                return std::hash<ValueType::StringClass>{}(value->as<ValueType::StringClass>());
            }
            if (type == ValueType::Bool)
            {
                return std::hash<ValueType::BoolClass>{}(value->as<ValueType::BoolClass>());
            }
            if (type == ValueType::StructType)
            {
                auto HashFields = [](std::vector<Field> fields) {
                    size_t r = 0;
                    for (auto &f : fields) { r += std::hash<Field>{}(f); }
                    return r;
                };
                const StructType &st = value->as<StructType>();
                return std::hash<TypeInfo>{}(st.type) + HashFields(st.fields);
            }
            if (type == ValueType::StructInstance)
            {
                const StructInstance &si = value->as<StructInstance>();
                return std::hash<TypeInfo>{}(si.parentType)
                       + std::hash<uint64_t>{}(reinterpret_cast<uint64_t>(std::addressof(*si.localContext)));
            }
            assert(false);
            throw ""; // ignore warning
        }
    }

    TypeInfo actualType(std::shared_ptr<const Object> obj)
    {
        auto t = obj->getTypeInfo();
        
        // dispatch builtin struct types (like Int{}, List{} e.g...)
        if (t == ValueType::StructType)
        {
            return obj->as<StructType>().type;
        }

        if (t == ValueType::InterfaceType)
        {
            return obj->as<InterfaceType>().type;
        }

        if (t == ValueType::StructInstance) return obj->as<StructInstance>().parentType;
        return t;
    }
    FString prettyType(std::shared_ptr<const Object> obj)
    {
        return actualType(obj).toString();
    }

    const TypeInfo ValueType::Any(FString(u8"Any"), true);                       // id: 1
    const TypeInfo ValueType::Null(FString(u8"Null"), true);                     // id: 2
    const TypeInfo ValueType::Int(FString(u8"Int"), true);                       // id: 3
    const TypeInfo ValueType::String(FString(u8"String"), true);                 // id: 4
    const TypeInfo ValueType::Bool(FString(u8"Bool"), true);                     // id: 5
    const TypeInfo ValueType::Double(FString(u8"Double"), true);                 // id: 6
    const TypeInfo ValueType::Function(FString(u8"Function"), true);             // id: 7
    const TypeInfo ValueType::StructType(FString(u8"StructType"), true);         // id: 8
    const TypeInfo ValueType::StructInstance(FString(u8"StructInstance"), true); // id: 9
    const TypeInfo ValueType::List(FString(u8"List"), true);                     // id: 10
    const TypeInfo ValueType::Map(FString(u8"Map"), true);                       // id: 11
    const TypeInfo ValueType::Module(FString(u8"Module"), true);                 // id: 12
    const TypeInfo ValueType::InterfaceType(FString(u8"InterfaceType"), true);   // id: 13

    bool implements(const TypeInfo &structType, const TypeInfo &interfaceType, ContextPtr ctx)
    {
        return ctx->hasImplRegisted(structType, interfaceType);
    }

    bool isTypeMatch(const TypeInfo &expected, ObjectPtr obj, ContextPtr ctx)
    {
        if (expected == ValueType::Any) return true;

        TypeInfo actual = obj->getTypeInfo();

        if (obj->is<StructType>())
        {
            const StructType &t = obj->as<StructType>();
            if (expected == t.type) // the StructType typeinfo
            {
                return true;
            }
        }
        else if (obj->is<StructInstance>())
        {
            const StructInstance &si = obj->as<StructInstance>();
            if (si.parentType == expected) { return true; }
            if (implements(si.parentType, expected, ctx)) { return true; }
        }
        return expected == actual;
    }

} // namespace Fig