#ifndef _fencer_mirror_h
#define _fencer_mirror_h

#include "typeclass_helpers.h"
#include "stdint.h"
#include "string.h"
#include "list.h"
#include "strutil.h"

typedef uintptr_t typeid;

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

typedef struct {
    void* data;
    IMirror const* tc;
} Mirror;

typedef struct {
    const char* typeclass_name;
    uintptr_t name_hash;
    const void* typeclass;
} 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);

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

#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(List*, 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 = (List* (*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 List* 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;\
}\
List* T##_get_typeclasses(T* self) {\
    static char init_flag = 0;\
    static List typeclasses;\
    if(!init_flag) {\
        init_flag = 1;\
        typeclasses = list_init(sizeof(MirroredTypeclass));\
        MirroredTypeclass tc;

#define REFLECT_TYPECLASS(T, TypeClass_)\
    tc = (MirroredTypeclass){\
        .name_hash = strhash(#TypeClass_),\
        .typeclass_name = #TypeClass_,\
        .typeclass = (void*)T##_as_##TypeClass_(NULL).tc\
    };\
    list_add(&typeclasses, &tc);

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

#endif // !_fencer_mirror_h
