#ifndef BREEZE_LAMBDA_H
#define BREEZE_LAMBDA_H

#include "../memory/memory.h"

typedef struct BzLambda BzLambda;

typedef void (*BzLambdaFn)(BzLambda *lambda, ...);

#define BZ_LAMBDA_STACK_SIZE 52
struct BzLambda {
    BzLambdaFn fn;

    i32 captureSize;
    /* stack contents */
    u8 stack[BZ_LAMBDA_STACK_SIZE];
};
static_assert(sizeof(BzLambda) == 64, "");

#define BZ_LAMBDA(lambdaName)                                            \
    (BzLambda) {                                                        \
        .fn = (BzLambdaFn) lambdaName                                   \
    }

#define BZ_LAMBDA_BEGIN_CAPTURE(lambda)                                \
    do {                                                               \
        BzLambda *_lam     = (lambda);                                 \
        _lam->captureSize = 0;                                         \
    } while (0)

#define BZ_LAMBDA_CAPTURE(lambda, var)                                 \
    do {                                                               \
        BzLambda *_lam       = (lambda);                               \
        i32  _stackSize = _lam->captureSize;                           \
        i32  _varSize   = sizeof(var);                                 \
        assert(_stackSize + _varSize < (i32) sizeof(_lam->stack));     \
        void *_location = &(var);                                      \
        bzMemCpy(&_lam->stack[_stackSize], _location, _varSize);       \
        _lam->captureSize += _varSize;                                 \
    } while (0)

#define BZ_LAMBDA_UNPACK_START() int32_t _stackOffset = 0
#define BZ_LAMBDA_UNPACK(type, name)                                     \
    type name = (_stackOffset += sizeof(type),                         \
                 assert(_stackOffset <= _lam->captureSize),            \
                 *(type *) &_lam->stack[_stackOffset - sizeof(type)])

#define BZ_LAMBDA_CAST_RET(retType) (retType(*)(BzLambda *, ...))

/*
 * Note: C99 requires at least one argument for "..." in variadic macro,
 * so we need to have 2 variants of CLAMBDA_CALL and CLAMBDA_DECL.
 */

#define BZ_LAMBDA_CALL(lambda, retType, ...) ((BZ_LAMBDA_CAST_RET(retType)((lambda)->fn))(lambda, ##__VA_ARGS__))
#define BZ_LAMBDA_NOP_CALL(lambda, retType) ((BZ_LAMBDA_CAST_RET(retType)((lambda)->fn))(lambda))

#define BZ_LAMBDA_DECL(lambdaName, ...) lambdaName(BzLambda *_lam, ##__VA_ARGS__)
#define BZ_LAMBDA_NOP_DECL(lambdaName) lambdaName(BzLambda *_lam)


#endif // BREEZE_LAMBDA_H

