#include "dump_predef.h"

#include "angelscript/angelscript.h"
#include <cassert>
#include <fstream>
#include <string>
#include <string_view>

// Adapted from https://github.com/sashi0034/angel-lsp/blob/HEAD/examples/generate_as_predefined.cpp

namespace satdump
{
    namespace script
    {
        namespace
        {
            template <class Stream>
            void printEnumList(const asIScriptEngine *engine, Stream &stream)
            {
                for (int i = 0; i < engine->GetEnumCount(); i++)
                {
                    const auto e = engine->GetEnumByIndex(i);
                    if (!e)
                        continue;
                    const std::string_view ns = e->GetNamespace();
                    if (!ns.empty())
                        stream << "namespace " << ns << " {\n";  // std::format("namespace {} {{\n", ns);
                    stream << "enum " << e->GetName() << " {\n"; // std::format("enum {} {{\n", e->GetName());
                    for (int j = 0; j < e->GetEnumValueCount(); ++j)
                    {
                        stream << "\t" << e->GetEnumValueByIndex(j, nullptr); // std::format("\t{}", e->GetEnumValueByIndex(j, nullptr));
                        if (j < e->GetEnumValueCount() - 1)
                            stream << ",";
                        stream << "\n";
                    }
                    stream << "}\n";
                    if (!ns.empty())
                        stream << "}\n";
                }
            }

            template <class Stream>
            void printClassTypeList(const asIScriptEngine *engine, Stream &stream)
            {
                for (int i = 0; i < engine->GetObjectTypeCount(); i++)
                {
                    const auto t = engine->GetObjectTypeByIndex(i);
                    if (!t)
                        continue;

                    const std::string_view ns = t->GetNamespace();
                    if (!ns.empty())
                        stream << "namespace " << ns << " {\n"; // std::format("namespace {} {{\n", ns);

                    stream << "class " << t->GetName(); // std::format("class {}", t->GetName());
                    if (t->GetSubTypeCount() > 0)
                    {
                        stream << "<";
                        for (int sub = 0; sub < t->GetSubTypeCount(); ++sub)
                        {
                            if (sub < t->GetSubTypeCount() - 1)
                                stream << ", ";
                            const auto st = t->GetSubType(sub);
                            stream << st->GetName();
                        }

                        stream << ">";
                    }

                    stream << "{\n";
                    for (int j = 0; j < t->GetBehaviourCount(); ++j)
                    {
                        asEBehaviours behaviours;
                        const auto f = t->GetBehaviourByIndex(j, &behaviours);
                        if (behaviours == asBEHAVE_CONSTRUCT || behaviours == asBEHAVE_DESTRUCT)
                        {
                            stream << "\t" << f->GetDeclaration(false, true, true) << ";\n"; // std::format("\t{};\n", f->GetDeclaration(false, true, true));
                        }
                    }
                    for (int j = 0; j < t->GetMethodCount(); ++j)
                    {
                        const auto m = t->GetMethodByIndex(j);
                        stream << "\t" << m->GetDeclaration(false, true, true) << ";\n"; // std::format("\t{};\n", m->GetDeclaration(false, true, true));
                    }
                    for (int j = 0; j < t->GetPropertyCount(); ++j)
                    {
                        stream << "\t" << t->GetPropertyDeclaration(j, true) << ";\n"; // std::format("\t{};\n", t->GetPropertyDeclaration(j, true));
                    }
                    for (int j = 0; j < t->GetChildFuncdefCount(); ++j)
                    {
                        stream << "\tfuncdef " << t->GetChildFuncdef(j)->GetFuncdefSignature()->GetDeclaration(false)
                               << ";\n"; // std::format("\tfuncdef {};\n", t->GetChildFuncdef(j)->GetFuncdefSignature()->GetDeclaration(false));
                    }
                    stream << "}\n";
                    if (!ns.empty())
                        stream << "}\n";
                }
            }

            template <class Stream>
            void printGlobalFunctionList(const asIScriptEngine *engine, Stream &stream)
            {
                for (int i = 0; i < engine->GetGlobalFunctionCount(); i++)
                {
                    const auto f = engine->GetGlobalFunctionByIndex(i);
                    if (!f)
                        continue;
                    const std::string_view ns = f->GetNamespace();
                    if (!ns.empty())
                        stream << "namespace " << ns << " { ";              // std::format("namespace {} {{ ", ns);
                    stream << f->GetDeclaration(false, false, true) << ";"; // std::format("{};", f->GetDeclaration(false, false, true));
                    if (!ns.empty())
                        stream << " }";
                    stream << "\n";
                }
            }

            template <class Stream>
            void printGlobalPropertyList(const asIScriptEngine *engine, Stream &stream)
            {
                for (int i = 0; i < engine->GetGlobalPropertyCount(); i++)
                {
                    const char *name;
                    const char *ns0;
                    int type;
                    engine->GetGlobalPropertyByIndex(i, &name, &ns0, &type, nullptr, nullptr, nullptr, nullptr);

                    const std::string t = engine->GetTypeDeclaration(type, true);
                    if (t.empty())
                        continue;

                    std::string_view ns = ns0;
                    if (!ns.empty())
                        stream << "namespace " << ns << " { "; // std::format("namespace {} {{ ", ns);

                    stream << t << " " << name << ";"; // std::format("{} {};", t, name);
                    if (!ns.empty())
                        stream << " }";
                    stream << "\n";
                }
            }

            template <class Stream>
            void printGlobalTypedef(const asIScriptEngine *engine, Stream &stream)
            {
                for (int i = 0; i < engine->GetTypedefCount(); ++i)
                {
                    const auto type = engine->GetTypedefByIndex(i);
                    if (!type)
                        continue;
                    const std::string_view ns = type->GetNamespace();
                    if (!ns.empty())
                        stream << "namespace " << ns << " {\n"; // std::format("namespace {} {{\n", ns);
                    stream << "typedef " << engine->GetTypeDeclaration(type->GetTypedefTypeId()) << " " << type->GetName()
                           << ";\n"; // std::format("typedef {} {};\n", engine->GetTypeDeclaration(type->GetTypedefTypeId()), type->GetName());
                    if (!ns.empty())
                        stream << "}\n";
                }
            }
        } // namespace

        /// @brief Generate 'as.predefined' file, which contains all defined symbols in C++. It is used by the language server.
        void GenerateScriptPredefined(const asIScriptEngine *engine, const std::string &path)
        {
            //            assert(path.ends_with("as.predefined"));

            std::ofstream stream{path};

            printEnumList(engine, stream);

            printClassTypeList(engine, stream);

            printGlobalFunctionList(engine, stream);

            printGlobalPropertyList(engine, stream);

            printGlobalTypedef(engine, stream);
        }
    } // namespace script
} // namespace satdump