 
#ifndef GU_TYPE_H_
#define GU_TYPE_H_

#include <gu/defs.h>

//
// kind
//

typedef const struct GuKind GuKind;

struct GuKind {
	GuKind* super;
};

// Use GU_PASTE here so k_ can be preprocessor-expanded
#define GU_TYPE_IDENT(k_) GU_PASTE(gu_type__,k_)

#define gu_kind(k_) ((GuKind*)GU_TYPE_IDENT(k_))

#define GU_DECLARE_KIND(k_) \
	GuKind GU_TYPE_IDENT(k_)[1]

extern GU_DECLARE_KIND(kind);

#define GU_DEFINE_KIND(k_, super_k_) \
	GuKind GU_TYPE_IDENT(k_)[1] = {{ .super = gu_kind(super_k_) }}

//
// type
//

typedef const struct GuType GuType;

struct GuType {
	GuKind kind_base;
};

typedef GuType GuType_type;

extern GU_DECLARE_KIND(type);

#define GU_TYPE_INIT_type(k_, t_, _) { .kind_base = { .super = gu_kind(k_) } }

#define gu_type(t_) ((GuType*)gu_kind(t_))


#define GU_KIND_TYPE(k_) GU_PASTE(GuType_,k_)

// This cannot be used indirectly, since we don't want to pp-expand k_.
// We must inline the body into other macros.
#define GU_TYPE_INIT(k_, ...)		\
	GU_TYPE_INIT_##k_(k_, __VA_ARGS__)

//#define GU_TYPE_LIT(k_, ...)						
//	((GuType*)(GuType_##k_[]){GU_TYPE_INIT(k_, __VA_ARGS__)})
#define GU_TYPE_LIT(k_, ...)						\
	((GuType*)&(GU_KIND_TYPE(k_)) GU_TYPE_INIT_##k_(k_, __VA_ARGS__))

#define GU_DECLARE_TYPE(t_, k_)	\
	GU_KIND_TYPE(k_) GU_TYPE_IDENT(t_)[1]

//#define GU_DEFINE_TYPE(t_, k_, ...)					
//	GuType_##k_ GU_TYPE_IDENT(t_) = GU_TYPE_INIT(k_, t_, __VA_ARGS__)
#define GU_DEFINE_TYPE(t_, k_, ...)					\
	GU_KIND_TYPE(k_) GU_TYPE_IDENT(t_)[1] =				\
	{ GU_TYPE_INIT_##k_(k_, t_, __VA_ARGS__) }

#define GU_DEFINE_TYPE_ALIAS(t1_, t2_)		\
	static GuType* const GU_TYPE_IDENT(t1_) = gu_type(t2_)


//
// abstract
//

typedef GuType GuType_abstract;

#define GU_TYPE_INIT_abstract(k_, t_, _)		\
	GU_TYPE_INIT_type(k_, t_, _)

extern GU_DECLARE_KIND(abstract);


//
// repr 
//

typedef struct GuTypeRepr GuTypeRepr, GuType_repr;

struct GuTypeRepr {
	GuType type_base;
	uint16_t size;
	uint16_t align;
};

#define GU_TYPE_INIT_repr(k_, t_, _) {				\
		.type_base = GU_TYPE_INIT_type(k_, t_, _),	\
		.size = sizeof(t_), \
		.align = gu_alignof(t_) \
		 }

extern GU_DECLARE_KIND(repr);



//
// GuOpaque
//

typedef GuType_repr GuType_GuOpaque;

#define GU_TYPE_INIT_GuOpaque GU_TYPE_INIT_repr

extern GU_DECLARE_KIND(GuOpaque);

//
// pointer
//

typedef const struct GuPointerType GuPointerType, GuType_pointer;

struct GuPointerType {
	GuType_repr repr_base;
	GuType* pointed_type;
};

#define GU_TYPE_INIT_pointer(k_, t_, pointed_)	\
	{					   \
		.repr_base = GU_TYPE_INIT_repr(k_, t_, _),	\
	.pointed_type = pointed_ \
}	


extern GU_DECLARE_KIND(pointer);

#define gu_ptr_type(t_) \
	GU_TYPE_LIT(pointer, t_*, gu_type(t_))







//
// alias
//


typedef const struct GuTypeAlias GuTypeAlias, GuType_alias;

struct GuTypeAlias {
	GuType type_base;
	GuType* type;
};

#define GU_TYPE_INIT_alias(k_, t_, type_) {			\
	.type_base = GU_TYPE_INIT_type(k_, t_, _),	\
	.type = type_				\
}

extern GU_DECLARE_KIND(alias);

//
// typedef
//

typedef const struct GuTypeDef GuTypeDef, GuType_typedef;

struct GuTypeDef {
        GuType_alias alias_base;
	const char* name;
};

#define GU_TYPE_INIT_typedef(k_, t_, type_) {			\
	.alias_base = GU_TYPE_INIT_alias(k_, t_, type_),	\
	.name = #t_,		\
}

extern GU_DECLARE_KIND(typedef);

#define GU_DEFINE_TYPEDEF_X(t_, dk_, k_, ...)			\
	GU_DEFINE_TYPE(t_, dk_, GU_TYPE_LIT(k_, t_, __VA_ARGS__))

#define GU_DEFINE_TYPEDEF(t_, ...)	\
	GU_DEFINE_TYPEDEF_X(t_, typedef, __VA_ARGS__)



//
// referenced
//

extern GU_DECLARE_KIND(referenced);

typedef GuType_alias GuType_referenced;

#define GU_TYPE_INIT_referenced GU_TYPE_INIT_alias



#include <gu/list.h>

//
// struct
//

typedef const struct GuStructRepr GuStructRepr, GuType_struct;

typedef const struct GuMember GuMember;

struct GuMember {
	ptrdiff_t offset;
	const char* name;
	GuType* type;
	bool is_flex;
};

struct GuStructRepr {
	GuType_repr repr_base;
	const char* name;
	GuSList(GuMember) members;
};

extern GU_DECLARE_KIND(struct);

#define GU_MEMBER_AUX_(struct_, member_, type_, is_flex_)      \
	{					      \
		.offset = offsetof(struct_, member_), \
		.name = #member_,	      \
		.type = type_,		      \
		.is_flex = is_flex_,  \
	}

#define GU_MEMBER_V(struct_, member_, type_) \
	GU_MEMBER_AUX_(struct_, member_, type_, false)

#define GU_MEMBER(s_, m_, t_) \
	GU_MEMBER_V(s_, m_, gu_type(t_))

#define GU_MEMBER_P(s_, m_, t_) \
	GU_MEMBER_V(s_, m_, gu_ptr_type(t_))

#define GU_MEMBER_S(s_, m_, t_) \
	GU_MEMBER_V(s_, m_, gu_shared_ptr_type(t_))

#define GU_FLEX_MEMBER_V(struct_, member_, type_) \
	GU_MEMBER_AUX_(struct_, member_, type_, true)

#define GU_FLEX_MEMBER(s_, m_, t_) \
	GU_FLEX_MEMBER_V(s_, m_, gu_type(t_))

#define GU_FLEX_MEMBER_P(s_, m_, t_) \
	GU_FLEX_MEMBER_V(s_, m_, gu_ptr_type(t_))


#define GU_TYPE_INIT_struct(k_, t_, ...)	{		\
	.repr_base = GU_TYPE_INIT_repr(k_, t_, _),		\
	.name = #t_, \
	.members = GU_SLIST(GuMember, __VA_ARGS__)	\
}

bool
gu_struct_has_flex(GuStructRepr* srepr);


//
// reference
//

typedef GuType_pointer GuType_reference;

#define GU_TYPE_INIT_reference GU_TYPE_INIT_pointer

extern GU_DECLARE_KIND(reference);


//
// shared
//

typedef GuType_pointer GuType_shared;

#define GU_TYPE_INIT_shared GU_TYPE_INIT_pointer

extern GU_DECLARE_KIND(shared);

#define gu_shared_ptr_type(t_) \
	GU_TYPE_LIT(shared, t_*, gu_type(t_))

//
// primitives
//

typedef const struct GuPrimType GuPrimType, GuType_primitive;

struct GuPrimType {
	GuType_repr repr_base;
	const char* name;
};

#define GU_TYPE_INIT_primitive(k_, t_, _) {	\
	.repr_base = GU_TYPE_INIT_repr(k_, t_, _),	\
	.name = #t_			\
}	

extern GU_DECLARE_KIND(primitive);
extern GU_DECLARE_TYPE(void, primitive);

#define GU_TYPE_INIT_integer GU_TYPE_INIT_primitive
typedef GuType_primitive GuType_integer;
extern GU_DECLARE_KIND(integer);
extern GU_DECLARE_TYPE(char, integer);

#define GU_TYPE_INIT_signed GU_TYPE_INIT_integer
typedef GuType_integer GuType_signed;
extern GU_DECLARE_KIND(signed);
extern GU_DECLARE_TYPE(int, signed);
extern GU_DECLARE_TYPE(int8_t, signed);
extern GU_DECLARE_TYPE(int16_t, signed);
extern GU_DECLARE_TYPE(int32_t, signed);
extern GU_DECLARE_TYPE(int64_t, signed);
extern GU_DECLARE_TYPE(intptr_t, signed);
extern GU_DECLARE_TYPE(intmax_t, signed);

#define GU_TYPE_INIT_unsigned GU_TYPE_INIT_integer
typedef GuType_integer GuType_unsigned;
extern GU_DECLARE_KIND(unsigned);
extern GU_DECLARE_TYPE(uint8_t, unsigned);
extern GU_DECLARE_TYPE(uint16_t, unsigned);
extern GU_DECLARE_TYPE(uint32_t, unsigned);
extern GU_DECLARE_TYPE(uint64_t, unsigned);
extern GU_DECLARE_TYPE(uintmax_t, unsigned);
extern GU_DECLARE_TYPE(size_t, unsigned);

typedef size_t GuLength;
extern GU_DECLARE_TYPE(GuLength, unsigned); // TODO: get rid


#define GU_TYPE_INIT_GuFloating GU_TYPE_INIT_primitive
typedef GuType_primitive GuType_GuFloating;
extern GU_DECLARE_KIND(GuFloating);
extern GU_DECLARE_TYPE(float, GuFloating);
extern GU_DECLARE_TYPE(double, GuFloating);
typedef long double GuLongDouble;
extern GU_DECLARE_TYPE(GuLongDouble, GuFloating);



//
// enum
//

extern GU_DECLARE_KIND(enum);

typedef const struct GuEnumConstant GuEnumConstant;

struct GuEnumConstant {
	const char* name;
	int64_t value;
	const void* enum_value;
};

typedef const struct GuEnumType GuEnumType, GuType_enum;

struct GuEnumType {
	GuType_repr repr_base;
	GuSList(GuEnumConstant) constants;
};

#define GU_ENUM_C(t_, x) {		\
		.name = #x, \
		.value = x,		\
		.enum_value = (const t_[1]){ x } \
 }

#define GU_TYPE_INIT_enum(k_, t_, ...) { \
	.repr_base = GU_TYPE_INIT_repr(k_, t_, _),	\
	.constants = GU_SLIST(GuEnumConstant, __VA_ARGS__) \
}

GuEnumConstant*
gu_enum_value(GuEnumType* etype, const void* enump);




bool gu_type_has_kind(const GuType* type, const GuKind* kind);




typedef const struct GuTypeTableEntry GuTypeTableEntry;

struct GuTypeTableEntry {
	GuKind* kind;
	void* val;
};

typedef const struct GuTypeTable GuTypeTable;

struct GuTypeTable {
	GuSList(const GuTypeTable*) parents;
	GuSList(GuTypeTableEntry) entries;
};

#define GU_TYPETABLE(parents_, ...) { \
	.parents = parents_, \
	.entries = GU_SLIST(GuTypeTableEntry, \
			    __VA_ARGS__) \
 }

typedef struct GuTypeMap GuTypeMap;

GuTypeMap*
gu_new_type_map(GuTypeTable* table, GuPool* pool);

void*
gu_type_map_get(GuTypeMap* tmap, GuType* type);

size_t 
gu_type_size(GuType* type);

GuTypeRepr*
gu_type_repr(GuType* type);

const void*
gu_type_check_cast(GuType* t, GuKind* k);

const void*
gu_type_dyn_cast(GuType* t, GuKind* k);

#define gu_type_try_cast(type_, k_) \
	((GU_KIND_TYPE(k_)*)gu_type_dyn_cast(type_, gu_kind(k_)))

#ifndef NDEBUG
#define gu_type_cast(type_, k_) \
	((GU_KIND_TYPE(k_)*)gu_type_check_cast(type_, gu_kind(k_)))
#else
#define gu_type_cast(type_, k_) \
	((GU_KIND_TYPE(k_)*)(type_))
#endif

void* gu_type_malloc(GuType* type, GuPool* pool);

#if 0
void* gu_type_ptr_get(GuType* type, const void* pp);
void gu_type_ptr_set(GuType* type, void* pp, void* p);
#endif


#endif // GU_TYPE_H_
