/*
 * Copyright (c) 2012-2025 Daniele Bartolini et al.
 * SPDX-License-Identifier: MIT
 */

#pragma once

#include "config.h"
#include "core/math/types.h"
#include "core/strings/string_id.h"
#include "core/types.h"
#include "resource/types.h"
#include "world/types.h"

struct lua_State;
typedef int (*lua_CFunction)(lua_State *L);

#ifndef LUA_OK
	#define LUA_OK 0
#endif

#define LIGHTDATA_TYPE_MASK      uintptr_t(0x3)
#define LIGHTDATA_TYPE_SHIFT     uintptr_t(0)

#define LIGHTDATA_POINTER_MARKER uintptr_t(0)

#define LIGHTDATA_UNIT_MARKER    uintptr_t(1)
#define LIGHTDATA_UNIT_ID_MASK   uintptr_t(0xfffffffc)
#define LIGHTDATA_UNIT_ID_SHIFT  uintptr_t(2)

#define LUA_VECTOR3_MARKER_MASK     uintptr_t(0x03)
#define LUA_VECTOR3_MARKER_SHIFT    uintptr_t(0)
#define LUA_QUATERNION_MARKER_MASK  uintptr_t(0x0f)
#define LUA_QUATERNION_MARKER_SHIFT uintptr_t(0)
#define LUA_MATRIX4X4_MARKER_MASK   uintptr_t(0x3f)
#define LUA_MATRIX4X4_MARKER_SHIFT  uintptr_t(0)

namespace crown
{
/// Enumerates generic lua argument types.
///
/// @ingroup Lua
struct ArgType
{
	enum Enum
	{
		NIL,
		BOOL,
		INT,
		FLOAT,
		STRING,
		STRING_ID,
		POINTER,
		FUNCTION,
		UNIT,
		ID,
		VECTOR3,
		QUATERNION,
		MATRIX4X4,

		COUNT
	};
};

/// Generic lua argument.
///
/// @ingroup Lua
union Arg
{
	bool bool_value;
	int int_value;
	f32 float_value;
	const char *string_value;
	u32 string_id_value;
	const void *pointer_value;
	lua_CFunction cfunction_value;
	UnitId unit_value;
	u32 id_value;
	Vector3 vector3_value;
	Quaternion quaternion_value;
	Matrix4x4 matrix4x4_value;
};

/// Wrapper to manipulate Lua stack.
///
/// @ingroup Lua
struct LuaStack
{
	lua_State *L;
#if CROWN_DEBUG
	int _size;
	int _end_size_delta;
#endif

	/// @a end_size_delta is used in debug to specify the expected (delta) size of the stack when
	/// this object goes out of scope. Use INT_MAX do disable; 0 means the stack must be balanced.
	LuaStack(lua_State *L, int end_size_delta = 0);

	///
	~LuaStack();

	/// Returns the number of elements in the stack.
	/// When called inside a function, it can be used to count
	/// the number of arguments passed to the function itself.
	int num_args();

	/// Removes the element at the given valid index, shifting down the elements
	/// above this index to fill the gap. Cannot be called with a pseudo-index,
	/// because a pseudo-index is not an actual stack position.
	void remove(int i);

	/// Pops @a n elements from the stack.
	void pop(int n);

	///
	bool is_nil(int i);

	///
	bool is_bool(int i);

	///
	bool is_number(int i);

	///
	bool is_string(int i);

	///
	bool is_pointer(int i);

	///
	bool is_function(int i);

	///
	bool is_table(int i);

	///
	bool is_vector3(int i);

	///
	bool is_quaternion(int i);

	///
	bool is_matrix4x4(int i);

	/// Wraps lua_type.
	int value_type(int i);

	///
	bool get_bool(int i);

	///
	int get_int(int i);

	///
	f32 get_float(int i);

	///
	const char *get_string(int i);

	///
	void *get_pointer(int i);

	///
	u32 get_id(int i);

	///
	StringId32 get_string_id_32(int i);

	///
	StringId64 get_string_id_64(int i);

	///
	StringId64 get_resource_name(int i);

	///
	Gui *get_gui(int i);

	///
	DebugLine *get_debug_line(int i);

	///
	ResourcePackage *get_resource_package(int i);

	///
	World *get_world(int i);

	///
	SceneGraph *get_scene_graph(int i);

	///
	Level *get_level(int i);

	///
	RenderWorld *get_render_world(int i);

	///
	PhysicsWorld *get_physics_world(int i);

	///
	SoundWorld *get_sound_world(int i);

	///
	ScriptWorld *get_script_world(int i);

	///
	AnimationStateMachine *get_animation_state_machine(int i);

	///
	bool is_unit(int i);

	///
	UnitId get_unit(int i);

	///
	CameraInstance get_camera_instance(int i);

	///
	TransformInstance get_transform_instance(int i);

	///
	MeshInstance get_mesh_instance(int i);

	///
	SpriteInstance get_sprite_instance(int i);

	///
	LightInstance get_light_instance(int i);

	///
	FogInstance get_fog_instance(int i);

	///
	StateMachineInstance get_state_machine_instance(int i);

	///
	Material *get_material(int i);

	///
	ActorInstance get_actor_instance(int i);

	///
	MoverInstance get_mover_instance(int i);

	///
	SoundInstanceId get_sound_instance_id(int i);

	///
	ScriptInstance get_script_instance(int i);

	///
	Vector2 get_vector2(int i);

	///
	Vector3 &get_vector3(int i);

	///
	Vector4 get_vector4(int i);

	///
	Quaternion &get_quaternion(int i);

	///
	Matrix4x4 &get_matrix4x4(int i);

	///
	Color4 get_color4(int i);

	///
	Vector2 &get_vector2box(int i);

	///
	Vector3 &get_vector3box(int i);

	///
	Quaternion &get_quaternionbox(int i);

	///
	Matrix4x4 &get_matrix4x4box(int i);

	///
	void push_nil();

	///
	void push_bool(bool value);

	///
	void push_int(int value);

	///
	void push_float(f32 value);

	///
	void push_string(const char *s);

	///
	void push_fstring(const char *fmt, ...);

	///
	void push_lstring(const char *s, u32 len);

	///
	void push_string_id(StringId32 value);

	///
	void push_pointer(void *p);

	///
	void push_function(lua_CFunction f);

	///
	void push_id(u32 value);

	/// Pushes an empty table onto the stack.
	/// When you want to set keys on the table, you have to use LuaStack::push_key_begin()
	/// and LuaStack::push_key_end() as in the following example:
	///
	/// LuaStack stack(L)
	/// stack.push_table()
	/// stack.push_key_begin("foo"); stack.push_foo(); stack.push_key_end()
	/// stack.push_key_begin("bar"); stack.push_bar(); stack.push_key_end()
	/// return 1;
	void push_table(int narr = 0, int nrec = 0);

	/// See Stack::push_table()
	void push_key_begin(const char *key);

	/// See Stack::push_table()
	void push_key_begin(int i);

	/// See Stack::push_table()
	void push_key_end();

	///
	int next(int i);

	///
	void push_unit(UnitId unit);

	///
	void push_sound_instance_id(SoundInstanceId id);

	///
	void push_vector2(const Vector2 &v);

	///
	void push_vector3(const Vector3 &v);

	///
	void push_matrix4x4(const Matrix4x4 &m);

	///
	void push_quaternion(const Quaternion &q);

	///
	void push_color4(const Color4 &c);

	///
	void push_vector2box(const Vector2 &v);

	///
	void push_vector3box(const Vector3 &v);

	///
	void push_quaternionbox(const Quaternion &q);

	///
	void push_matrix4x4box(const Matrix4x4 &m);

	///
	void push_value(int i);

	///
	void push_args(const ArgType::Enum *arg_types, const Arg *args, u32 num);

	///
	void call(int nresults);

	///
	Vector3 *check_temporary(int i, const Vector3 *p);

	///
	Quaternion *check_temporary(int i, const Quaternion *p);

	///
	Matrix4x4 *check_temporary(int i, const Matrix4x4 *p);

	///
	void check_marker(int i, const void *p, u32 type_marker, const char *type_name);
};

} // namespace crown
