
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// THIS FILE IS A MODIFIED VERSION
// https://github.com/sashi0034/angel-lsp/blob/main/examples/OpenSiv3D/make_predefined.cpp
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "DeerStudio/AngelScriptEngine.h"
#include "angelscript.h"

#include <iso646.h>
#include <sstream>
#include <string>
#include <string_view>

#include <angelscript.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <string_view>

#include "DeerRender/DataStore.h"
#include "DeerRender/Tools/Path.h"

static std::stringstream stream;
static std::string str;

void printFuncList(const asIScriptEngine& engine) {
	for (int i = 0; i < engine.GetFuncdefCount(); ++i) {
		asITypeInfo* t = engine.GetFuncdefByIndex(i);
		if (!t)
			continue;

		asIScriptFunction* func = t->GetFuncdefSignature();
		if (!func || func->GetSubType())
			continue;

		std::string decl = func->GetDeclaration(true, true, true);

		// Detect class-scoped funcdefs by presence of "::" in the declaration
		size_t scopePos = decl.find("::");
		if (scopePos != std::string::npos)
			continue;

		stream << "funcdef " << decl << ";\n";
	}
}

void printEnumList(const asIScriptEngine& engine) {
	for (int i = 0; i < engine.GetEnumCount(); ++i) {
		const auto e = engine.GetEnumByIndex(i);
		if (!e)
			continue;

		std::string_view ns = e->GetNamespace();
		if (!ns.empty())
			stream << "namespace " << ns << " {\n";

		stream << "enum " << e->GetName() << " {\n";
		for (int j = 0; j < e->GetEnumValueCount(); ++j) {
			int value;
			stream << "\t" << e->GetEnumValueByIndex(j, &value) << " = "
			       << value;
			if (j < e->GetEnumValueCount() - 1)
				stream << ",";
			stream << "\n";
		}
		stream << "}\n";
		if (!ns.empty())
			stream << "}\n\n";
	}
}

void printClassTypeList(const asIScriptEngine& engine) {
	for (int i = 0; i < engine.GetObjectTypeCount(); ++i) {
		asITypeInfo* t = engine.GetObjectTypeByIndex(i);
		if (!t)
			continue;

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

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

		stream << " {\n";

		for (int i = 0; i < t->GetChildFuncdefCount(); ++i) {
			asITypeInfo* ftype = t->GetChildFuncdef(i);

			asIScriptFunction* func = ftype->GetFuncdefSignature();

			std::string decl = func->GetDeclaration(false, false, true);
			stream << "\tfuncdef " << decl << ";\n";
		}

		for (int j = 0; j < t->GetFactoryCount(); ++j) {
			asIScriptFunction* f = t->GetFactoryByIndex(j);

			stream << "\t" << f->GetDeclaration(false, false, true) << ";\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 ||
			    behaviours == asBEHAVE_FACTORY)
				stream << "\t" << f->GetDeclaration(false, false, true)
				       << ";\n";
		}

		for (int j = 0; j < t->GetMethodCount(); ++j) {
			const auto m = t->GetMethodByIndex(j);
			stream << "\t" << m->GetDeclaration(false, false, true);

			if (m->IsProperty())
				stream << " property";
			stream << ";\n";
		}

		for (int j = 0; j < t->GetPropertyCount(); ++j) {
			stream << "\t" << t->GetPropertyDeclaration(j, false) << ";\n";
		}

		for (int j = 0; j < t->GetChildFuncdefCount(); ++j) {
			stream
			    << "\tfuncdef "
			    << t->GetChildFuncdef(j)->GetFuncdefSignature()->GetDeclaration(
			           false)
			    << ";\n";
		}

		stream << "}\n";
		if (!ns.empty())
			stream << "}\n\n";
	}
}

void printGlobalFunctionList(const asIScriptEngine& engine) {
	for (int i = 0; i < engine.GetGlobalFunctionCount(); ++i) {
		const auto f = engine.GetGlobalFunctionByIndex(i);
		if (!f)
			continue;

		std::string_view ns = f->GetNamespace();
		if (!ns.empty())
			stream << "namespace " << ns << " { ";
		stream << f->GetDeclaration(false, false, true) << ";";
		if (!ns.empty())
			stream << " }";
		stream << "\n";
	}
}

void printGlobalPropertyList(const asIScriptEngine& engine) {
	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);

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

		std::string_view ns = ns0;
		if (!ns.empty())
			stream << "namespace " << ns << " { ";
		stream << t << " " << name << ";";
		if (!ns.empty())
			stream << " }";
		stream << "\n";
	}
}

void printGlobalTypedef(const asIScriptEngine& engine) {
	for (int i = 0; i < engine.GetTypedefCount(); ++i) {
		const auto type = engine.GetTypedefByIndex(i);
		if (!type)
			continue;

		std::string_view ns = type->GetNamespace();
		if (!ns.empty())
			stream << "namespace " << ns << " {\n";
		stream << "typedef "
		       << engine.GetTypeDeclaration(type->GetTypedefTypeId()) << " "
		       << type->GetName() << ";\n";
		if (!ns.empty())
			stream << "}\n";
	}
}

void printAngelInfo(const asIScriptEngine& engine) {
	printFuncList(engine);

	printEnumList(engine);

	printClassTypeList(engine);

	printGlobalFunctionList(engine);

	printGlobalPropertyList(engine);

	printGlobalTypedef(engine);
}

namespace Deer {
	void AngelScriptEngine::generateAngelscriptPredefined() {
		stream.clear();
		stream << "//This file was generated automatically\n";

		printAngelInfo(*Deer::AngelScriptEngine::scriptEngine);

		str = stream.str();
	}

	void AngelScriptEngine::saveAngelscriptPredefined(const Path& path) {
		Deer::Path filePath = path / "as.predefined";

		std::ofstream file(filePath, std::ios::out | std::ios::binary);
		file.write(reinterpret_cast<const char*>(str.c_str()), str.size());

		file.close();
	}
} // namespace Deer