#ifndef CUTES_MIRROR_H
#define CUTES_MIRROR_H

#include "typeclass_helpers.h"
#include "stdint.h"
#include "string.h"
#include "dictionary.h"
#include "strutil.h" // included because the impl macros require strhash

typedef uintptr_t typeid;

typedef struct {
    const char* (*const get_typestring)(void* self);
    typeid (*const get_typeid)(void* self);
    Dictionary* (*const get_typeclasses)(void* self);
} IMirror;

typedef struct {
    void* data;
    union {
        IMirror const* tc;
        // this is cursed, but it allows the TC_CAST macro to work
        IMirror const* mirror;
    };
} Mirror;

typedef struct {
    const void* typeclass;
    void* function;
} MirroredTypeclass;

static inline int mirror_is_typeid(const Mirror* mirror, typeid id) {
    return mirror->tc->get_typeid(mirror->data) == id;
}
static inline int mirror_is_typestring(const Mirror* mirror, const char* id) {
    return strcmp(id, mirror->tc->get_typestring(mirror->data)) == 0;
}
static inline int mirror_eq(const Mirror* lhs, const Mirror* rhs) {
    return lhs->tc->get_typeid(lhs->data) == rhs->tc->get_typeid(rhs->data);
}

extern const void* mirror_get_typeclass(void* data, IMirror const* tc, const char* typeclass);
// get the wrapper function for a given typeclass name
// example:
// mirror_get_function(physics_entity.data, physics_entity.mirror, "BehaviourEntity")
extern void* mirror_get_function(void* data, IMirror const* tc, const char* typeclass_name);

// macro reexport of mirror_get_function which will cast the function so it can be called immediately
// example:
// MIRROR_GET_WRAP_FUNC(physics_entity.data, physics_entity.mirror, BehaviourEntity)(physics_entity.data)
#define MIRROR_GET_WRAP_FUNC(Data_, MirrorTc_, Typeclass_)\
((Typeclass_(*)(void*))mirror_get_function(Data_, MirrorTc_, #Typeclass_))

// casting only works if the typeclass in question exposes IMirror as .mirror
// will segfault if the Mirror does not expose To_
// example:
// TC_CAST(physics_entity, BehaviourEntity)
#define TC_CAST(From_, To_)\
(MIRROR_GET_WRAP_FUNC(From_.data, From_.mirror, To_)(From_.data))

#define TC_MIRRORS(From_, To_)\
(MIRROR_GET_WRAP_FUNC(From_.data, From_.mirror, To_) != NULL)

#define MIRROR_TRY_WRAP(Into_, Mirror_, Typeclass_)do{\
    MirroredTypeclassWrapFunc fn_ = mirror_get_typeclass(Mirror_, #Typeclass_);\
    if(fn_ != NULL) {\
        Into_ = (TypeClass_)fn_(Mirror_->data);\
    }\
} while(0)

#define impl_Mirror_for(T, get_typestring_f, get_typeid_f, get_typeclasses_f)\
Mirror T##_as_Mirror(T* x) {\
    TC_FN_TYPECHECK(const char*, get_typestring_f, T*);\
    TC_FN_TYPECHECK(typeid, get_typeid_f, T*);\
    TC_FN_TYPECHECK(Dictionary*, get_typeclasses_f, T*);\
    static IMirror const tc = {\
        .get_typestring = (const char*(*const)(void*)) get_typestring_f,\
        .get_typeid = (typeid (*const)(void*)) get_typeid_f,\
        .get_typeclasses = (Dictionary* (*const)(void*)) get_typeclasses_f,\
    };\
    return (Mirror){.tc = &tc, .data = x};\
}

#define DECL_REFLECT(T)\
extern const char* T##_get_typestring(T* self);\
extern typeid T##_get_typeid(T* self);\
extern Dictionary* T##_get_typeclasses(T* self);\
decl_typeclass_impl(Mirror, T)

#define START_REFLECT(T)\
const char* T##_get_typestring(T* self) {\
    static const char* const typestring = #T;\
    return typestring;\
}\
typeid T##_get_typeid(T* self) {\
    static char init_flag = 0;\
    static typeid id = 0;\
    if(!init_flag) {\
        init_flag = 1;\
        id = strhash(#T);\
    }\
    return id;\
}\
Dictionary* T##_get_typeclasses(T* self) {\
    static char init_flag = 0;\
    static Dictionary typeclasses;\
    if(!init_flag) {\
        init_flag = 1;\
        typeclasses = dictionary_new(sizeof(MirroredTypeclass));\
        MirroredTypeclass tc;\
    REFLECT_TYPECLASS(T, Mirror)

#define REFLECT_TYPECLASS(T, TypeClass_)\
    tc = (MirroredTypeclass){\
        .typeclass = (void*)T##_as_##TypeClass_(NULL).tc,\
        .function = (void*)T##_as_##TypeClass_\
    };\
    dictionary_set_raw(&typeclasses, #TypeClass_, &tc)

#define END_REFLECT(T)\
    }\
    return &typeclasses;\
}\
impl_Mirror_for(T, T##_get_typestring, T##_get_typeid, T##_get_typeclasses)

#endif // !CUTES_MIRROR_H