/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <marcin.kolny@gmail.com> wrote this file. As long as you retain this notice
 * you can do whatever you want with this stuff. If we meet some day, and you
 * think this stuff is worth it, you can buy me a beer in return. Marcin Kolny
 * http://github.com/loganek/mkcreflect
 * ----------------------------------------------------------------------------
 */
#ifndef MKCREFLECT_H_
#define MKCREFLECT_H_

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef enum
{
    MKCREFLECT_TYPES_STRUCT = 1,
    MKCREFLECT_TYPES_STRING = 2,
    MKCREFLECT_TYPES_INTEGER = 3,
    MKCREFLECT_TYPES_FLOAT = 4,
    MKCREFLECT_TYPES_DOUBLE = 5,
    MKCREFLECT_TYPES_POINTER = 6
} MKCREFLECT_Types;

struct _MKCREFLECT_FieldInfo
{
    const char* field_type;
    const char* field_name;
    size_t size;
    size_t offset;
    int is_signed;
    int array_size;
    MKCREFLECT_Types data_type;
};

typedef struct _MKCREFLECT_FieldInfo MKCREFLECT_FieldInfo;

struct _MKCREFLECT_TypeInfo
{
    const char* name;
    size_t fields_count;
    size_t size;
    size_t packed_size;
    MKCREFLECT_FieldInfo* fields;
};

typedef struct _MKCREFLECT_TypeInfo MKCREFLECT_TypeInfo;

#define MKCREFLECT_EXPAND_(X) X
#define MKCREFLECT_EXPAND_VA_(...) __VA_ARGS__
#define MKCREFLECT_FOREACH_1_(FNC, USER_DATA, ARG) FNC(ARG, USER_DATA)
#define MKCREFLECT_FOREACH_2_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_1_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_3_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_2_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_4_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_3_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_5_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_4_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_6_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_5_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_7_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_6_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_8_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_7_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_9_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_8_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_10_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_9_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_11_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_10_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_12_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_11_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_13_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_12_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_14_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_13_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_15_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_14_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_16_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_15_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_17_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_16_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_18_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_17_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_19_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_18_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_20_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_19_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_21_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_20_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_22_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_21_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_23_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_22_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_24_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_23_(FNC, USER_DATA, __VA_ARGS__))
#define MKCREFLECT_FOREACH_25_(FNC, USER_DATA, ARG, ...) \
    FNC(ARG, USER_DATA) MKCREFLECT_EXPAND_(MKCREFLECT_FOREACH_24_(FNC, USER_DATA, __VA_ARGS__))

#define MKCREFLECT_OVERRIDE_4(_1, _2, _3, _4, FNC, ...) FNC
#define MKCREFLECT_OVERRIDE_4_PLACEHOLDER 1, 2, 3, 4
#define MKCREFLECT_OVERRIDE_5(_1, _2, _3, _4, _5, FNC, ...) FNC
#define MKCREFLECT_OVERRIDE_5_PLACEHOLDER MKCREFLECT_OVERRIDE_4_PLACEHOLDER, 5
#define MKCREFLECT_OVERRIDE_14(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, FNC, ...) FNC
#define MKCREFLECT_OVERRIDE_14_PLACEHOLDER MKCREFLECT_OVERRIDE_5_PLACEHOLDER, 6, 7, 8, 9, 10, 11, 12, 13, 14
#define MKCREFLECT_OVERRIDE_20(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, FNC, ...) FNC
#define MKCREFLECT_OVERRIDE_20_PLACEHOLDER MKCREFLECT_OVERRIDE_14_PLACEHOLDER, 15, 16, 17, 18, 19, 20
#define MKCREFLECT_OVERRIDE_25(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, FNC, ...) FNC
#define MKCREFLECT_OVERRIDE_25_PLACEHOLDER MKCREFLECT_OVERRIDE_20_PLACEHOLDER, 21, 22, 23, 24, 25
    
#define MKCREFLECT_FOREACH(FNC, USER_DATA, ...) \
    MKCREFLECT_EXPAND_(MKCREFLECT_OVERRIDE_25( __VA_ARGS__,	\
    MKCREFLECT_FOREACH_25_, \
    MKCREFLECT_FOREACH_24_, \
    MKCREFLECT_FOREACH_23_, \
    MKCREFLECT_FOREACH_22_, \
    MKCREFLECT_FOREACH_21_, \
    MKCREFLECT_FOREACH_20_, \
    MKCREFLECT_FOREACH_19_, \
    MKCREFLECT_FOREACH_18_, \
    MKCREFLECT_FOREACH_17_, \
    MKCREFLECT_FOREACH_16_, \
    MKCREFLECT_FOREACH_15_, \
    MKCREFLECT_FOREACH_14_, \
    MKCREFLECT_FOREACH_13_, \
    MKCREFLECT_FOREACH_12_, \
    MKCREFLECT_FOREACH_11_, \
    MKCREFLECT_FOREACH_10_, \
    MKCREFLECT_FOREACH_9_, \
    MKCREFLECT_FOREACH_8_, \
    MKCREFLECT_FOREACH_7_, \
    MKCREFLECT_FOREACH_6_, \
    MKCREFLECT_FOREACH_5_, \
    MKCREFLECT_FOREACH_4_, \
    MKCREFLECT_FOREACH_3_, \
    MKCREFLECT_FOREACH_2_, \
    MKCREFLECT_FOREACH_1_)(FNC, USER_DATA, __VA_ARGS__))

#define MKCREFLECT_DECLARE_SIMPLE_FIELD_(IGNORE, TYPE, FIELD_NAME) \
    TYPE FIELD_NAME;
#define MKCREFLECT_DECLARE_ARRAY_FIELD_(IGNORE, TYPE, FIELD_NAME, ARRAY_SIZE) \
    TYPE FIELD_NAME[ARRAY_SIZE];

#define MKCREFLECT_DECLARE_FIELD_(...) MKCREFLECT_EXPAND_(MKCREFLECT_OVERRIDE_4( \
    __VA_ARGS__, \
    MKCREFLECT_DECLARE_ARRAY_FIELD_, \
    MKCREFLECT_DECLARE_SIMPLE_FIELD_, \
    MKCREFLECT_OVERRIDE_4_PLACEHOLDER)(__VA_ARGS__))

#define MKCREFLECT_DECLARE_FIELD(X, USER_DATA) MKCREFLECT_DECLARE_FIELD_ X

#define MKCREFLECT_SIZEOF_(IGNORE, C_TYPE, ...) +sizeof(C_TYPE)
#define MKCREFLECT_SIZEOF(X, USER_DATA) MKCREFLECT_SIZEOF_ X

#define MKCREFLECT_SUM(...) +1

#define MKCREFLECT_IS_TYPE_SIGNED_(C_TYPE) (C_TYPE)-1 < (C_TYPE)1
#define MKCREFLECT_IS_SIGNED_STRUCT(C_TYPE) 0
#define MKCREFLECT_IS_SIGNED_STRING(C_TYPE) MKCREFLECT_IS_TYPE_SIGNED_(C_TYPE)
#define MKCREFLECT_IS_SIGNED_INTEGER(C_TYPE) MKCREFLECT_IS_TYPE_SIGNED_(C_TYPE)
#define MKCREFLECT_IS_SIGNED_FLOAT(C_TYPE) MKCREFLECT_IS_TYPE_SIGNED_(C_TYPE)
#define MKCREFLECT_IS_SIGNED_DOUBLE(C_TYPE) MKCREFLECT_IS_TYPE_SIGNED_(C_TYPE)
#define MKCREFLECT_IS_SIGNED_POINTER(C_TYPE) 0

#define MKCREFLECT_IS_SIGNED_(DATA_TYPE, CTYPE) MKCREFLECT_IS_SIGNED_##DATA_TYPE(CTYPE)

#define MKCREFLECT_ARRAY_FIELD_INFO_(TYPE_NAME, DATA_TYPE, C_TYPE, FIELD_NAME, ARRAY_SIZE) \
    #C_TYPE, #FIELD_NAME, sizeof(C_TYPE) * ARRAY_SIZE, offsetof(TYPE_NAME, FIELD_NAME), \
    MKCREFLECT_IS_SIGNED_(DATA_TYPE, C_TYPE), ARRAY_SIZE, MKCREFLECT_TYPES_##DATA_TYPE

#define MKCREFLECT_SIMPLE_FIELD_INFO_(TYPE_NAME, DATA_TYPE, C_TYPE, FIELD_NAME) \
    #C_TYPE, #FIELD_NAME, sizeof(C_TYPE), offsetof(TYPE_NAME, FIELD_NAME), \
    MKCREFLECT_IS_SIGNED_(DATA_TYPE, C_TYPE), -1, MKCREFLECT_TYPES_##DATA_TYPE

#define MKCREFLECT_FIELD_INFO_(...) \
{ \
    MKCREFLECT_EXPAND_(MKCREFLECT_OVERRIDE_5( \
    __VA_ARGS__, \
    MKCREFLECT_ARRAY_FIELD_INFO_, \
    MKCREFLECT_SIMPLE_FIELD_INFO_, \
    MKCREFLECT_OVERRIDE_5_PLACEHOLDER)(__VA_ARGS__)) \
},

#define MKCREFLECT_FIELD_INFO(X, USER_DATA) \
    MKCREFLECT_FIELD_INFO_(USER_DATA, MKCREFLECT_EXPAND_VA_ X)

#ifdef MKCREFLECT_IMPL

#define MKCREFLECT_DEFINE_GET_METHOD(TYPE_NAME, ...) \
    MKCREFLECT_TypeInfo* mkcreflect_get_##TYPE_NAME##_type_info(void) \
    { \
        static MKCREFLECT_FieldInfo fields_info[MKCREFLECT_FOREACH(MKCREFLECT_SUM, 0, __VA_ARGS__)] = \
        { \
            MKCREFLECT_FOREACH(MKCREFLECT_FIELD_INFO, TYPE_NAME, __VA_ARGS__) \
        }; \
        static MKCREFLECT_TypeInfo type_info = \
        { \
            #TYPE_NAME, \
            MKCREFLECT_FOREACH(MKCREFLECT_SUM, 0, __VA_ARGS__), \
            sizeof(TYPE_NAME), \
            MKCREFLECT_FOREACH(MKCREFLECT_SIZEOF, 0, __VA_ARGS__), \
            fields_info \
        }; \
        return &type_info; \
    }

#else

#define MKCREFLECT_DEFINE_GET_METHOD(TYPE_NAME, ...)

#endif /* MKCREFLECT_IMPL */

#define MKCREFLECT_DEFINE_STRUCT(TYPE_NAME, ...) \
    typedef struct \
    { \
        MKCREFLECT_FOREACH(MKCREFLECT_DECLARE_FIELD, 0, __VA_ARGS__) \
    } TYPE_NAME; \
    MKCREFLECT_TypeInfo* mkcreflect_get_##TYPE_NAME##_type_info(void); \
    MKCREFLECT_DEFINE_GET_METHOD(TYPE_NAME, __VA_ARGS__) \

#ifdef __cplusplus
}
#endif

#endif /* MKCREFLECT_H_ */
