#pragma once

#include "misc.qh"
#include "nil.qh"
#include "static.qh"

// pure entities: not linked to the area grid
// see: sv_areagrid_link_SOLID_NOT and cl_areagrid_link_SOLID_NOT
.bool pure_data;

#ifdef GAMEQC
	/** direct use is @deprecated use new_pure or NEW(class) */
	#define make_pure(e) \
		(e).pure_data = true; \
		(e).solid = SOLID_NOT;
	#define is_pure(e) ((e).pure_data && (e).solid == SOLID_NOT)
#else
	#define make_pure(e) \
		(e).pure_data = true;
	#define is_pure(e) ((e).pure_data)
#endif // GAMEQC
#define make_impure(e) \
	(e).pure_data = false;

.string classname;
/** Location entity was spawned from in source */
.string sourceLoc;
entity _spawn();

#ifndef SPAWN_PURE
#define SPAWN_PURE 0
#endif

#if SPAWN_PURE
entity spawn_pure() = #600;
#else
#define spawn_pure() _spawn()
#endif

entity __spawn(string _classname, string _sourceLoc, bool pure)
{
	entity this = pure ? spawn_pure() : _spawn();
	this.classname = _classname;
	this.sourceLoc = _sourceLoc;
	if (pure) {
		make_pure(this);
	}
	return this;
}


#define entityclass(...) EVAL_entityclass(OVERLOAD_(entityclass, __VA_ARGS__))
#define EVAL_entityclass(...) __VA_ARGS__
#define entityclass_1(name) entityclass_2(name, Object)
#ifndef QCC_SUPPORT_ENTITYCLASS
	#define entityclass_2(name, base) USING(name, entity)
	#define classfield(name)
	#define _new(class, pure) __spawn(#class, __FILE__ ":" STR(__LINE__), pure)
#else
	#define entityclass_2(name, base) entityclass name : base {}
	#define classfield(name) [[class(name)]]
	#define _new(class, pure) ((class) __spawn(#class, __FILE__ ":" STR(__LINE__), pure))
#endif
/** entities you care about collisions with */
#define new(class) _new(class, false)
/** purely logical entities (not linked to the area grid) */
#define new_pure(class) _new(class, true)
#define spawn() __spawn("entity", __FILE__ ":" STR(__LINE__), false)

ACCUMULATE void ONREMOVE(entity this) {}

#ifndef SVQC
	#define delete_fn builtin_remove
#endif

.void(entity this) dtor;
#define delete(this) MACRO_BEGIN \
    entity _this = (this); \
    void(entity) _dtor = _this.dtor; \
    ONREMOVE(this); \
    if (_dtor) _dtor(_this); else delete_fn(_this); \
    /* this = NULL; */  \
MACRO_END

void IL_REMOVE_RAW(entity it);
void copyentity_qc(entity src, entity dst)
{
	copyentity(src, dst); // builtin function
	IL_REMOVE_RAW(dst);
}

entity _clearentity_ent;
STATIC_INIT(clearentity)
{
	_clearentity_ent = new_pure(clearentity);
}
void clearentity(entity e)
{
#ifdef CSQC
		int n = e.entnum;
#endif
	bool was_pure = is_pure(e);
	copyentity_qc(_clearentity_ent, e);
	if (!was_pure) make_impure(e);
#ifdef CSQC
		e.entnum = n;
#endif
}

// Classes have a `spawn##cname(entity)` constructor
// The parameter is used across ACCUMULATE functions

.bool transmute;

// Macros to hide this implementation detail:
#ifdef __STDC__
	#define NEW(cname, ...) \
		OVERLOAD_(spawn##cname, new_pure(cname) P99_IF_EMPTY(__VA_ARGS__)()(, __VA_ARGS__))

    #define _TRANSMUTE(cname, this, ...) \
        OVERLOAD_(spawn##cname, this P99_IF_EMPTY(__VA_ARGS__)()(, __VA_ARGS__))

	#define CONSTRUCT(cname, ...) \
		OVERLOAD_(spawn##cname, this P99_IF_EMPTY(__VA_ARGS__)()(, __VA_ARGS__))
#else
	#define NEW(cname, ...) \
		OVERLOAD(spawn##cname, new_pure(cname),##__VA_ARGS__)

    #define _TRANSMUTE(cname, this, ...) \
        OVERLOAD(spawn##cname, this,##__VA_ARGS__)

	#define CONSTRUCT(cname, ...) \
		OVERLOAD(spawn##cname, this,##__VA_ARGS__)
#endif

#define TRANSMUTE(cname, this, ...) MACRO_BEGIN \
    entity _e = (this); \
    if (_e.vtblbase != cname##_vtbl) { \
        _e.transmute = true; \
        _e.classname = #cname; \
        _TRANSMUTE(cname, _e, __VA_ARGS__); \
    } \
    MACRO_END

#define CLASS(...) EVAL_CLASS(OVERLOAD__(CLASS, __VA_ARGS__))
#define EVAL_CLASS(...) __VA_ARGS__

#define ATTRIB(...) EVAL_ATTRIB(OVERLOAD_(ATTRIB, __VA_ARGS__))
#define EVAL_ATTRIB(...) __VA_ARGS__

#ifdef QCC_SUPPORT_CLASS

#warning "QCC_SUPPORT_CLASS not implemented"

#define CLASS_1(name)                               CLASS_2(name, entity)
#define CLASS_2(name, base)                         class name : base {

#define INIT(class)                                     void class::class()
#define CONSTRUCTOR(class, ...)                         void class::class(__VA_ARGS__)
#define DESTRUCTOR(class)                               class::~class()

#define SUPER(class)                                    super

#define ATTRIB_3(class, name, T)                        T name
#define ATTRIB_4(class, name, T, val)                   ATTRIB_3(class, name, T) = val
#define STATIC_ATTRIB(class, name, T, val)              static T name = val

#define ATTRIB_STRZONE(class, name, T, val)             T name = val
#define STATIC_ATTRIB_STRZONE(class, name, T, val)      static T name = val

#define ATTRIBARRAY(class, name, T, val)                T name[val]

#define METHOD(class, name, prototype)                  virtual void class::name()
#define STATIC_METHOD(class, name, prototype)           static void class::name()

#define ENDCLASS(class)                             };

#else

#define CLASS_1(cname) CLASS_2(cname, )
#define CLASS_2(cname, base)                                                                       \
	entityclass(cname, base);                                                                      \
	classfield(cname).bool instanceOf##cname;                                                      \
	DEBUG_STUFF(cname)                                                                             \
	VTBL(cname, base)                                                                              \
	_INIT_STATIC(cname)                                                                            \
	{                                                                                              \
		if (cname##_vtbl && !this.transmute)                                                       \
		{                                                                                          \
			copyentity_qc(cname##_vtbl, this);                                                     \
			return;                                                                                \
		}                                                                                          \
		spawn##base##_static(this);                                                                \
		this.instanceOf##cname = true;                                                             \
	}                                                                                              \
	INIT(cname)                                                                                    \
	{                                                                                              \
		/* Only statically initialize the current class, it contains everything it inherits */     \
		if (cname##_vtbl.vtblname == this.classname)                                               \
		{                                                                                          \
			spawn##cname##_static(this);                                                           \
			this.transmute = false;                                                                \
			this.classname = #cname;                                                               \
			this.vtblname = string_null;                                                           \
			this.vtblbase = cname##_vtbl;                                                          \
		}                                                                                          \
		spawn##base##_1(this);                                                                     \
	}

#define INIT(cname)                                                                                \
	ACCUMULATE cname spawn##cname##_1(cname this)

#define CONSTRUCTOR(cname, ...)                                                                    \
	cname OVERLOAD(spawn##cname, cname this, __VA_ARGS__)                                          \
	{                                                                                              \
		return = this;                                                                             \
	}                                                                                              \
	ACCUMULATE cname OVERLOAD(spawn##cname, cname this, __VA_ARGS__)

#define DESTRUCTOR(cname)                                                                          \
	STATIC_METHOD(cname, dtorimpl, void(cname this));                                              \
    METHOD(cname, dtor, void(cname this))                                                          \
    {                                                                                              \
        METHOD_REFERENCE(cname, dtorimpl)(this);                                                   \
        this.instanceOf##cname = false;                                                            \
        entity super = SUPER(cname);                                                               \
        if (super != cname##_vtbl) super.dtor(this);                                               \
    }                                                                                              \
	STATIC_METHOD(cname, dtorimpl, void(cname this))

#define SUPER(cname) (cname##_vtbl.vtblbase)

#define ATTRIB_3(cname, name, type) classfield(cname) .type name
#define ATTRIB_4(cname, name, type, val)                                                           \
	ATTRIB_3(cname, name, type);                                                                   \
	INIT(cname)                                                                                    \
	{                                                                                              \
		noref bool strzone; /* Error on strzone() calls. */                                        \
		this.name = val;                                                                           \
	}                                                                                              \
	ATTRIB_3(cname, name, type)

#define STATIC_ATTRIB(cname, name, type, val)                                                      \
	type cname##_##name;                                                                           \
	_INIT_STATIC(cname)                                                                            \
	{                                                                                              \
		noref bool strzone; /* Error on strzone() calls. */                                        \
		cname##_##name = val;                                                                      \
	}

// cleanup potentially zoned strings from base classes
#define ATTRIB_STRZONE(cname, name, type, val)                                                     \
	classfield(cname).type name;                                                                   \
	INIT(cname)                                                                                    \
	{                                                                                              \
		strcpy(this.name, val);                                                                    \
	}

#define STATIC_ATTRIB_STRZONE(cname, name, type, val)                                              \
	type cname##_##name;                                                                           \
	_INIT_STATIC(cname)                                                                            \
	{                                                                                              \
		strcpy(cname##_##name, val);                                                               \
	}

#define ATTRIBARRAY(cname, name, type, cnt)                                                        \
	classfield(cname) .type name[cnt]

#define METHOD(cname, name, prototype)                                                             \
	STATIC_METHOD(cname, name, prototype);                                                         \
	classfield(cname) .prototype name;                                                             \
	_INIT_STATIC(cname)                                                                            \
	{                                                                                              \
		this.name = METHOD_REFERENCE(cname, name);                                                 \
	}                                                                                              \
	STATIC_METHOD(cname, name, prototype)

#define STATIC_METHOD(cname, name, prototype)                                                      \
	prototype METHOD_REFERENCE(cname, name)

#define ENDCLASS(cname)                                                                            \
	INIT(cname)                                                                                    \
	{                                                                                              \
		return this;                                                                               \
	}

// impl

.string vtblname;
.entity vtblbase;

void RegisterClasses() {}
STATIC_INIT(RegisterClasses)
{
	RegisterClasses();
}

#define VTBL(cname, base) \
	_INIT_STATIC(cname); \
	entity cname##_vtbl; \
	void cname##_vtbl_init() \
	{ \
		cname e = new_pure(vtbl); \
		spawn##cname##_static(e); \
		e.vtblname = #cname; \
		/* Top level objects refer to themselves */ \
		e.vtblbase = base##_vtbl ? base##_vtbl : e; \
		cname##_vtbl = e; \
	} \
	ACCUMULATE_FUNCTION(RegisterClasses, cname##_vtbl_init)

#define _INIT_STATIC(cname) ACCUMULATE void spawn##cname##_static(cname this)

#if NDEBUG
	#define DEBUG_STUFF(cname)
#else
	#define DEBUG_STUFF(cname) \
		ERASEABLE bool is_##cname(entity e) { return e.instanceOf##cname; } \
		ERASEABLE void isnt_##cname(entity e) { eprint(e); }
#endif

#define METHOD_REFERENCE(cname, name) \
	cname##_##name

#endif

#define spawn_static(this)
#define spawn_1(this)
#define _vtbl NULL
CLASS(Object)
    DESTRUCTOR(Object) { builtin_remove(this); }
    #define remove(this) delete(this)
	METHOD(Object, describe, string(Object this))
	{
		TC(Object, this);
		string s = _("No description");
		if (autocvar_developer > 0)
		{
			for (int i = 0, n = numentityfields(); i < n; ++i)
			{
				string value = getentityfieldstring(i, this);
				if (value != "") s = sprintf("%s\n%s = %s", s, entityfieldname(i), value);
			}
		}
		return s;
	}
	METHOD(Object, display, void(Object this, void(string name, string icon) returns))
	{
		TC(Object, this);
		returns(sprintf("entity %i", this), "nopreview_map");
	}
ENDCLASS(Object)
#undef spawn_static
#undef spawn_1
#undef _vtbl
