/*
 * This file is part of OpenTTD.
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
 */

/** @file squirrel_helper.hpp Declarations and parts of the implementation of the class for convert code. */

#ifndef SQUIRREL_HELPER_HPP
#define SQUIRREL_HELPER_HPP

#include "squirrel.hpp"
#include "../string_func.h"
#include "../tile_type.h"
#include "../core/convertible_through_base.hpp"
#include "squirrel_helper_type.hpp"

/**
 * Helper to push the class name of a script type onto the Squirrel stack
 * @param vm The virtual machine to push to.
 * @return The number of stack places used.
 */
template <class CL, ScriptType ST> SQInteger PushClassName(HSQUIRRELVM vm);

/**
 * The Squirrel convert routines
 */
namespace SQConvert {
	/**
	 * To return a value to squirrel, we use this helper class. It converts to the right format.
	 * We use a class instead of a plain function to allow us to use partial template specializations.
	 */
	template <typename T> struct Return;

	template <> struct Return<uint8_t>        { static inline int Set(HSQUIRRELVM vm, uint8_t res)       { sq_pushinteger(vm, (int32_t)res); return 1; } };
	template <> struct Return<uint16_t>       { static inline int Set(HSQUIRRELVM vm, uint16_t res)      { sq_pushinteger(vm, (int32_t)res); return 1; } };
	template <> struct Return<uint32_t>       { static inline int Set(HSQUIRRELVM vm, uint32_t res)      { sq_pushinteger(vm, (int32_t)res); return 1; } };
	template <> struct Return<int8_t>         { static inline int Set(HSQUIRRELVM vm, int8_t res)        { sq_pushinteger(vm, res); return 1; } };
	template <> struct Return<int16_t>        { static inline int Set(HSQUIRRELVM vm, int16_t res)       { sq_pushinteger(vm, res); return 1; } };
	template <> struct Return<int32_t>        { static inline int Set(HSQUIRRELVM vm, int32_t res)       { sq_pushinteger(vm, res); return 1; } };
	template <> struct Return<int64_t>        { static inline int Set(HSQUIRRELVM vm, int64_t res)       { sq_pushinteger(vm, res); return 1; } };
	template <> struct Return<TileIndex>    { static inline int Set(HSQUIRRELVM vm, TileIndex res)   { sq_pushinteger(vm, (int32_t)res.base()); return 1; } };
	template <> struct Return<bool>         { static inline int Set(HSQUIRRELVM vm, bool res)        { sq_pushbool   (vm, res); return 1; } };
	template <> struct Return<char *>       { /* Do not use char *, use std::optional<std::string> instead. */ };
	template <> struct Return<const char *> { /* Do not use const char *, use std::optional<std::string> instead. */ };
	template <> struct Return<HSQOBJECT>    { static inline int Set(HSQUIRRELVM vm, HSQOBJECT res)   { sq_pushobject(vm, res); return 1; } };

	template <typename T> requires std::is_enum_v<T> struct Return<T> {
		static inline int Set(HSQUIRRELVM vm, T res)
		{
			sq_pushinteger(vm, to_underlying(res));
			return 1;
		}
	};

	template <ConvertibleThroughBase T> struct Return<T> {
		static inline int Set(HSQUIRRELVM vm, T res)
		{
			sq_pushinteger(vm, res.base());
			return 1;
		}
	};

	template <> struct Return<std::optional<std::string>> {
		static inline int Set(HSQUIRRELVM vm, std::optional<std::string> res)
		{
			if (res.has_value()) {
				sq_pushstring(vm, res.value());
			} else {
				sq_pushnull(vm);
			}
			return 1;
		}
	};

	/**
	 * To get a param from squirrel, we use this helper class. It converts to the right format.
	 * We use a class instead of a plain function to allow us to use partial template specializations.
	 */
	template <typename T> struct Param;

	template <> struct Param<uint8_t>        { static inline uint8_t       Get(HSQUIRRELVM vm, int index) { SQInteger     tmp; sq_getinteger    (vm, index, &tmp); return tmp; } };
	template <> struct Param<uint16_t>       { static inline uint16_t      Get(HSQUIRRELVM vm, int index) { SQInteger     tmp; sq_getinteger    (vm, index, &tmp); return tmp; } };
	template <> struct Param<uint32_t>       { static inline uint32_t      Get(HSQUIRRELVM vm, int index) { SQInteger     tmp; sq_getinteger    (vm, index, &tmp); return tmp; } };
	template <> struct Param<int8_t>         { static inline int8_t        Get(HSQUIRRELVM vm, int index) { SQInteger     tmp; sq_getinteger    (vm, index, &tmp); return tmp; } };
	template <> struct Param<int16_t>        { static inline int16_t       Get(HSQUIRRELVM vm, int index) { SQInteger     tmp; sq_getinteger    (vm, index, &tmp); return tmp; } };
	template <> struct Param<int32_t>        { static inline int32_t       Get(HSQUIRRELVM vm, int index) { SQInteger     tmp; sq_getinteger    (vm, index, &tmp); return tmp; } };
	template <> struct Param<int64_t>        { static inline int64_t       Get(HSQUIRRELVM vm, int index) { SQInteger     tmp; sq_getinteger    (vm, index, &tmp); return tmp; } };
	template <> struct Param<TileIndex>    { static inline TileIndex   Get(HSQUIRRELVM vm, int index) { SQInteger     tmp; sq_getinteger    (vm, index, &tmp); return TileIndex((uint32_t)(int32_t)tmp); } };
	template <> struct Param<bool>         { static inline bool        Get(HSQUIRRELVM vm, int index) { SQBool        tmp; sq_getbool       (vm, index, &tmp); return tmp != 0; } };
	template <> struct Param<const char *> { /* Do not use const char *, use std::string& instead. */ };

	template <typename T> requires std::is_enum_v<T> struct Param<T> {
		static inline T Get(HSQUIRRELVM vm, int index)
		{
			SQInteger tmp;
			sq_getinteger(vm, index, &tmp);
			return static_cast<T>(tmp);
		}
	};

	template <ConvertibleThroughBase T> struct Param<T> {
		static inline T Get(HSQUIRRELVM vm, int index)
		{
			SQInteger tmp;
			sq_getinteger(vm, index, &tmp);
			return T{static_cast<T::BaseType>(tmp)};
		}
	};

	template <> struct Param<const std::string &> {
		static inline const std::string Get(HSQUIRRELVM vm, int index)
		{
			/* Convert what-ever there is as parameter to a string */
			sq_tostring(vm, index);

			std::string_view view;
			sq_getstring(vm, -1, view);
			std::string result = StrMakeValid(view);
			sq_poptop(vm);
			return result;
		}
	};

	template <typename Titem>
	struct Param<Array<Titem> &&> {
		static inline Array<Titem> Get(HSQUIRRELVM vm, int index)
		{
			/* Sanity check of the size. */
			if (sq_getsize(vm, index) > UINT16_MAX) throw sq_throwerror(vm, "an array used as parameter to a function is too large");

			SQObject obj;
			sq_getstackobj(vm, index, &obj);
			sq_pushobject(vm, obj);
			sq_pushnull(vm);

			Array<Titem> data;

			while (SQ_SUCCEEDED(sq_next(vm, -2))) {
				data.emplace_back(Param<Titem>::Get(vm, -1));
				sq_pop(vm, 2);
			}
			sq_pop(vm, 2);

			return data;
		}
	};

	/**
	 * Helper class to recognize the function type (retval type, args) and use the proper specialization
	 * for SQ callback. The partial specializations for the second arg (Tis_void_retval) are not possible
	 * on the function. Therefore the class is used instead.
	 */
	template <typename Tfunc> struct HelperT;

	/**
	 * The real C++ caller for functions.
	 */
	template <typename Tretval, typename... Targs>
	struct HelperT<Tretval (*)(Targs...)> {
		static int SQCall(void *instance, Tretval(*func)(Targs...), HSQUIRRELVM vm)
		{
			return SQCall(instance, func, vm, std::index_sequence_for<Targs...>{});
		}

	private:
		template <size_t... i>
		static int SQCall(void *, Tretval(*func)(Targs...), [[maybe_unused]] HSQUIRRELVM vm, std::index_sequence<i...>)
		{
			if constexpr (std::is_void_v<Tretval>) {
				(*func)(
					Param<Targs>::Get(vm, 2 + i)...
				);
				return 0;
			} else {
				Tretval ret = (*func)(
					Param<Targs>::Get(vm, 2 + i)...
				);
				return Return<Tretval>::Set(vm, std::move(ret));
			}
		}
	};

	/**
	 * The real C++ caller for methods.
	 */
	template <class Tcls, typename Tretval, typename... Targs>
	struct HelperT<Tretval(Tcls:: *)(Targs...)> {
		static int SQCall(Tcls *instance, auto func, HSQUIRRELVM vm)
		{
			return SQCall(instance, func, vm, std::index_sequence_for<Targs...>{});
		}

		static Tcls *SQConstruct(HSQUIRRELVM vm)
		{
			return SQConstruct(vm, std::index_sequence_for<Targs...>{});
		}

	private:
		template <size_t... i>
		static int SQCall(Tcls *instance, auto func, [[maybe_unused]] HSQUIRRELVM vm, std::index_sequence<i...>)
		{
			if constexpr (std::is_void_v<Tretval>) {
				(instance->*func)(
					Param<Targs>::Get(vm, 2 + i)...
				);
				return 0;
			} else {
				Tretval ret = (instance->*func)(
					Param<Targs>::Get(vm, 2 + i)...
				);
				return Return<Tretval>::Set(vm, std::move(ret));
			}
		}

		template <size_t... i>
		static Tcls *SQConstruct([[maybe_unused]] HSQUIRRELVM vm, std::index_sequence<i...>)
		{
			Tcls *inst = new Tcls(
				Param<Targs>::Get(vm, 2 + i)...
			);

			return inst;
		}
	};

	/**
	 * The real C++ caller for const methods.
	 */
	template <class Tcls, typename Tretval, typename... Targs>
	struct HelperT<Tretval(Tcls:: *)(Targs...) const> : HelperT<Tretval(Tcls:: *)(Targs...)> {};


	/**
	 * A general template for all non-static method callbacks from Squirrel.
	 *  In here the function_proc is recovered, and the SQCall is called that
	 *  can handle this exact amount of params.
	 * @param vm The virtual machine to create the callback in.
	 * @return \c 0 upon success, or any other number upon failure.
	 */
	template <typename Tcls, typename Tmethod, ScriptType Ttype>
	inline SQInteger DefSQNonStaticCallback(HSQUIRRELVM vm)
	{
		/* Find the amount of params we got */
		int nparam = sq_gettop(vm);
		SQUserPointer ptr = nullptr;
		SQUserPointer real_instance = nullptr;
		HSQOBJECT instance;

		/* Get the 'SQ' instance of this class */
		Squirrel::GetInstance(vm, &instance);

		/* Protect against calls to a non-static method in a static way */
		sq_pushroottable(vm);
		PushClassName<Tcls, Ttype>(vm);
		sq_get(vm, -2);
		sq_pushobject(vm, instance);
		if (sq_instanceof(vm) != SQTrue) return sq_throwerror(vm, "class method is non-static");
		sq_pop(vm, 3);

		/* Get the 'real' instance of this class */
		sq_getinstanceup(vm, 1, &real_instance, nullptr);
		/* Get the real function pointer */
		sq_getuserdata(vm, nparam, &ptr, nullptr);
		if (real_instance == nullptr) return sq_throwerror(vm, "couldn't detect real instance of class for non-static call");
		/* Remove the userdata from the stack */
		sq_pop(vm, 1);

		try {
			/* Delegate it to a template that can handle this specific function */
			auto cls_instance = static_cast<Tcls *>(real_instance);
			auto method = *static_cast<Tmethod *>(ptr);
			return HelperT<Tmethod>::SQCall(cls_instance, method, vm);
		} catch (SQInteger &e) {
			return e;
		}
	}

	/**
	 * A general template for all non-static advanced method callbacks from Squirrel.
	 *  In here the function_proc is recovered, and the SQCall is called that
	 *  can handle this exact amount of params.
	 * @param vm The virtual machine to create the callback in.
	 * @return \c 0 upon success, or any other number upon failure.
	 */
	template <typename Tcls, typename Tmethod, ScriptType Ttype>
	inline SQInteger DefSQAdvancedNonStaticCallback(HSQUIRRELVM vm)
	{
		/* Find the amount of params we got */
		int nparam = sq_gettop(vm);
		SQUserPointer ptr = nullptr;
		SQUserPointer real_instance = nullptr;
		HSQOBJECT instance;

		/* Get the 'SQ' instance of this class */
		Squirrel::GetInstance(vm, &instance);

		/* Protect against calls to a non-static method in a static way */
		sq_pushroottable(vm);
		PushClassName<Tcls, Ttype>(vm);
		sq_get(vm, -2);
		sq_pushobject(vm, instance);
		if (sq_instanceof(vm) != SQTrue) return sq_throwerror(vm, "class method is non-static");
		sq_pop(vm, 3);

		/* Get the 'real' instance of this class */
		sq_getinstanceup(vm, 1, &real_instance, nullptr);
		/* Get the real function pointer */
		sq_getuserdata(vm, nparam, &ptr, nullptr);
		if (real_instance == nullptr) return sq_throwerror(vm, "couldn't detect real instance of class for non-static call");
		/* Remove the userdata from the stack */
		sq_pop(vm, 1);

		try {
			/* Call the function, which its only param is always the VM */
			auto cls_instance = static_cast<Tcls *>(real_instance);
			auto method = *static_cast<Tmethod *>(ptr);
			return static_cast<SQInteger>((cls_instance->*method)(vm));
		} catch (SQInteger &e) {
			return e;
		}
	}

	/**
	 * A general template for all function/static method callbacks from Squirrel.
	 *  In here the function_proc is recovered, and the SQCall is called that
	 *  can handle this exact amount of params.
	 * @param vm The virtual machine to create the callback in.
	 * @return \c 0 upon success, or any other number upon failure.
	 */
	template <typename Tcls, typename Tmethod>
	inline SQInteger DefSQStaticCallback(HSQUIRRELVM vm)
	{
		/* Find the amount of params we got */
		int nparam = sq_gettop(vm);
		SQUserPointer ptr = nullptr;

		/* Get the real function pointer */
		sq_getuserdata(vm, nparam, &ptr, nullptr);

		try {
			/* Delegate it to a template that can handle this specific function */
			auto cls_instance = static_cast<Tcls *>(nullptr);
			auto method = *static_cast<Tmethod *>(ptr);
			return HelperT<Tmethod>::SQCall(cls_instance, method, vm);
		} catch (SQInteger &e) {
			return e;
		}
	}


	/**
	 * A general template for all static advanced method callbacks from Squirrel.
	 *  In here the function_proc is recovered, and the SQCall is called that
	 *  can handle this exact amount of params.
	 * @param vm The virtual machine to create the callback in.
	 * @return \c 0 upon success, or any other number upon failure.
	 */
	template <typename Tcls, typename Tmethod>
	inline SQInteger DefSQAdvancedStaticCallback(HSQUIRRELVM vm)
	{
		/* Find the amount of params we got */
		int nparam = sq_gettop(vm);
		SQUserPointer ptr = nullptr;

		/* Get the real function pointer */
		sq_getuserdata(vm, nparam, &ptr, nullptr);
		/* Remove the userdata from the stack */
		sq_pop(vm, 1);

		try {
			/* Call the function, which its only param is always the VM */
			auto method = *static_cast<Tmethod *>(ptr);
			return static_cast<SQInteger>((*method)(vm));
		} catch (SQInteger &e) {
			return e;
		}
	}

	/**
	 * A general template for the destructor of SQ instances. This is needed
	 *  here as it has to be in the same scope as DefSQConstructorCallback.
	 * @param p Pointer to the instance to release.
	 * @return \c 0 upon success. Has a return type due to this being passed as a parameter to another function.
	 */
	template <typename Tcls>
	static SQInteger DefSQDestructorCallback(SQUserPointer p, SQInteger)
	{
		/* Remove the real instance too */
		if (p != nullptr) ((Tcls *)p)->Release();
		return 0;
	}

	/**
	 * A general template to handle creating of instance with any amount of
	 *  params. It creates the instance in C++, and it sets all the needed
	 *  settings in SQ to register the instance.
	 * @param vm The virtual machine to create the callback in.
	 * @return \c 0 upon success, or any other number upon failure.
	 */
	template <typename Tcls, typename Tmethod>
	inline SQInteger DefSQConstructorCallback(HSQUIRRELVM vm)
	{
		try {
			/* Find the amount of params we got */
			int nparam = sq_gettop(vm);

			/* Create the real instance */
			Tcls *instance = HelperT<Tmethod>::SQConstruct(vm);
			sq_setinstanceup(vm, -nparam, instance);
			sq_setreleasehook(vm, -nparam, DefSQDestructorCallback<Tcls>);
			instance->AddRef();
			return 0;
		} catch (SQInteger &e) {
			return e;
		}
	}

	/**
	 * A general template to handle creating of an instance with a complex
	 *  constructor.
	 * @param vm The virtual machine to create the callback in.
	 * @return \c 0 upon success, or any other number upon failure.
	 */
	template <typename Tcls>
	inline SQInteger DefSQAdvancedConstructorCallback(HSQUIRRELVM vm)
	{
		try {
			/* Find the amount of params we got */
			int nparam = sq_gettop(vm);

			/* Create the real instance */
			Tcls *instance = new Tcls(vm);
			sq_setinstanceup(vm, -nparam, instance);
			sq_setreleasehook(vm, -nparam, DefSQDestructorCallback<Tcls>);
			instance->AddRef();
			return 0;
		} catch (SQInteger &e) {
			return e;
		}
	}

} // namespace SQConvert

#endif /* SQUIRREL_HELPER_HPP */
