#ifndef UNIT_IMPLEMENTATION_HEADER
#define UNIT_IMPLEMENTATION_HEADER

#include <cgreen/filename.h>

#ifdef __cplusplus
#include <cstddef>
#endif

#include "stringify_token.h"


typedef struct {
    const char* name;
    const char* filename;
    void (*setup)(void);
    void (*teardown)(void);
} CgreenContext;

typedef struct {
    int skip;                 /* Should test be skipped? */
    CgreenContext* context;
    const char* name;
    void(*run)(void);
    const char* filename;
    int line;
} CgreenTest;

#define CGREEN_SPEC_PREFIX "CgreenSpec"
#define CGREEN_SEPARATOR "__"
#define spec_name(contextName, testName) CgreenSpec__##contextName##__##testName##__

//This gives better error messages at the cost of duplication
#define ENSURE_VA_NUM_ARGS(...) ENSURE_VA_NUM_ARGS_IMPL_((__VA_ARGS__, _CALLED_WITH_TOO_MANY_ARGUMENTS,  WithContextAndSpecificationName,  WithSpecificationName, DummyToFillVaArgs))
#define ENSURE_VA_NUM_ARGS_IMPL_(tuple) ENSURE_VA_NUM_ARGS_IMPL tuple

#define ENSURE_VA_NUM_ARGS_IMPL(_1, _2, _3, _4, N, ...) N

#define ENSURE_macro_dispatcher(func, ...)   ENSURE_macro_dispatcher_(func, ENSURE_VA_NUM_ARGS(__VA_ARGS__))

// these levels of indirecton are a work-around for variadic macro deficiencies in Visual C++ 2012 and prior
#define ENSURE_macro_dispatcher_(func, nargs)           ENSURE_macro_dispatcher__(func, nargs)
#define ENSURE_macro_dispatcher__(func, nargs)           ENSURE_macro_dispatcher___(func, nargs)
#define ENSURE_macro_dispatcher___(func, nargs)          func ## nargs

#define Ensure_NARG(...) ENSURE_macro_dispatcher(Ensure, __VA_ARGS__)

#define EnsureWithContextAndSpecificationName(skip, contextName, specName)      \
    static void contextName##__##specName (void);                               \
    __attribute__((__visibility__("default")))                                  \
    extern CgreenTest spec_name(contextName, specName);                         \
    CgreenTest spec_name(contextName, specName) = {                             \
        skip, &contextFor##contextName, STRINGIFY_TOKEN(specName),              \
        &contextName##__##specName, FILENAME, __LINE__                          \
    };                                                                          \
    static void contextName##__##specName (void)

extern CgreenContext defaultContext;

#define EnsureWithSpecificationName(skip, specName)                             \
    static void specName (void);                                                \
    __attribute__((visibility("default")))                                      \
    extern CgreenTest spec_name(default, specName);                             \
    CgreenTest spec_name(default, specName) = {                                 \
        skip, &defaultContext, STRINGIFY_TOKEN(specName),                       \
        &specName, FILENAME, __LINE__                                           \
    };                                                                          \
    static void specName (void)

#define DescribeImplementation(subject)                                         \
    static void setup##subject (void);                                          \
    static void teardown##subject (void);                                       \
    static CgreenContext contextFor##subject __attribute__((__used__)) = {      \
        STRINGIFY_TOKEN(subject), FILENAME, &setup##subject, &teardown##subject \
    };                                                                          \
    extern void(*BeforeEach_For_##subject)(void);                               \
    extern void(*AfterEach_For_##subject)(void);                                \
    static void setup##subject (void) {                                         \
        if (BeforeEach_For_##subject != NULL) BeforeEach_For_##subject();       \
    }                                                                           \
    static void teardown##subject (void) {                                      \
        if (AfterEach_For_##subject != NULL) AfterEach_For_##subject();         \
    }                                                                           \
    typedef struct Dummy_ ## subject { int x; } Dummy_ ## subject ## _t

#define BeforeEachImplementation(subject) \
        void BeforeEach_For_##subject##_Function(void);                 \
        void(*BeforeEach_For_##subject)(void) = &BeforeEach_For_##subject##_Function; \
        void BeforeEach_For_##subject##_Function(void)

#define AfterEachImplementation(subject) \
        static void AfterEach_For_##subject##_Function(void);           \
        void(*AfterEach_For_##subject)(void) = &AfterEach_For_##subject##_Function; \
        static void AfterEach_For_##subject##_Function(void)

#endif
