#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct partially_applied {
  void * data;
  void (*function)(void *);
  void (*store)(void *, char const *, void *);
};

typedef struct partially_applied * FUN;

#define SPLICE_2(l,r) l##r
#define SPLICE_1(l,r) SPLICE_2(l,r)
#define SPLICE(l,r) SPLICE_1(l,r)

#define DATA_DEF_0(...)
#define DATA_DEF_1(type, name)      type name; bool name ## _valid;
#define DATA_DEF_2(type, name, ...) type name; bool name ## _valid; DATA_DEF_1(__VA_ARGS__)
#define DATA_DEF_3(type, name, ...) type name; bool name ## _valid; DATA_DEF_2(__VA_ARGS__)
#define DATA_DEF_4(type, name, ...) type name; bool name ## _valid; DATA_DEF_3(__VA_ARGS__)
// add more to support more parameters
#define DATA_DEF(N, ...) SPLICE(DATA_DEF_,N)(__VA_ARGS__)

#define DATA_ACCESS_0(...)
#define DATA_ACCESS_1(t, name)      data->name
#define DATA_ACCESS_2(t, name, ...) data->name, DATA_ACCESS_1(__VA_ARGS__)
#define DATA_ACCESS_3(t, name, ...) data->name, DATA_ACCESS_2(__VA_ARGS__)
#define DATA_ACCESS_4(t, name, ...) data->name, DATA_ACCESS_3(__VA_ARGS__)
// add more to support more parameters
#define DATA_ACCESS(N, ...) SPLICE(DATA_ACCESS_,N)(__VA_ARGS__)

#define DATA_CHECK_0(...) true
#define DATA_CHECK_1(t, name)      data->name ## _valid
#define DATA_CHECK_2(t, name, ...) data->name ## _valid && DATA_CHECK_1(__VA_ARGS__)
#define DATA_CHECK_3(t, name, ...) data->name ## _valid && DATA_CHECK_2(__VA_ARGS__)
#define DATA_CHECK_4(t, name, ...) data->name ## _valid && DATA_CHECK_3(__VA_ARGS__)
// add more to support more parameters
#define DATA_CHECK(N, ...) SPLICE(DATA_CHECK_,N)(__VA_ARGS__)

#define DATA_INIT_0(...)
#define DATA_INIT_1(t, name)      data->name ## _valid = false;
#define DATA_INIT_2(t, name, ...) data->name ## _valid = false; DATA_INIT_1(__VA_ARGS__)
#define DATA_INIT_3(t, name, ...) data->name ## _valid = false; DATA_INIT_2(__VA_ARGS__)
#define DATA_INIT_4(t, name, ...) data->name ## _valid = false; DATA_INIT_3(__VA_ARGS__)
// add more to support more parameters
#define DATA_INIT(N, ...) SPLICE(DATA_INIT_,N)(__VA_ARGS__)


#define DATA_STORE_CODE_OP(type, name) \
if (strcmp(id, #name) == 0) { data->name = *((type *) src); data->name ## _valid = true; return; }

#define DATA_STORE_CODE_0(...)
#define DATA_STORE_CODE_1(type, name)      DATA_STORE_CODE_OP(type, name)
#define DATA_STORE_CODE_2(type, name, ...) DATA_STORE_CODE_OP(type, name) DATA_STORE_CODE_1(__VA_ARGS__)
#define DATA_STORE_CODE_3(type, name, ...) DATA_STORE_CODE_OP(type, name) DATA_STORE_CODE_2(__VA_ARGS__)
#define DATA_STORE_CODE_4(type, name, ...) DATA_STORE_CODE_OP(type, name) DATA_STORE_CODE_3(__VA_ARGS__)
// more
#define DATA_STORE_CODE(N, ...) SPLICE(DATA_STORE_CODE_,N)(__VA_ARGS__)

#define MAKE_PARTIAL(fn, N, ...) \
struct partially_applied_ ## fn ## _data { \
	DATA_DEF(N, __VA_ARGS__) \
}; \
static void init_partially_applied_ ## fn ## _data (void * p) { \
	struct partially_applied_ ## fn ## _data * data = p; \
	DATA_INIT(N, __VA_ARGS__); \
} \
static void * allocate_partially_applied_ ## fn ## _data (void) { \
	void * data = malloc(sizeof(struct partially_applied_ ## fn ## _data)); \
	if (data == NULL) { \
		fprintf(stderr, "Allocation failure for " #fn " data\n"); \
		exit(1); \
	} \
	init_partially_applied_ ## fn ## _data(data); \
	return data; \
} \
static void partially_applied_ ## fn (void * p) { \
	struct partially_applied_ ## fn ## _data * data = p; \
	if (DATA_CHECK(N, __VA_ARGS__)) { \
		fn(DATA_ACCESS(N, __VA_ARGS__)); \
	} else { \
		fprintf(stderr, "Not all parameters for " #fn " are vaild\n"); \
	} \
} \
static void partially_applied_ ## fn ## _store ( \
	void * p, char const * id, void * src) { \
	struct partially_applied_ ## fn ## _data * data = p; \
	DATA_STORE_CODE(N, __VA_ARGS__) \
	fprintf(stderr, "Cannot store %s in " #fn "!\n", id); \
}







#define DATA_STORE_OP(name, value) pfn->store(pfn->data, #name, &(value));
#define DATA_STORE_0(...)
#define DATA_STORE_1(name, value) DATA_STORE_OP(name, value)
#define DATA_STORE_2(name, value, ...) DATA_STORE_OP(name, value) DATA_STORE_1(__VA_ARGS__)
#define DATA_STORE_3(name, value, ...) DATA_STORE_OP(name, value) DATA_STORE_2(__VA_ARGS__)
#define DATA_STORE_4(name, value, ...) DATA_STORE_OP(name, value) DATA_STORE_3(__VA_ARGS__)
#define DATA_STORE(N, ...) SPLICE(DATA_STORE_,N)(__VA_ARGS__)


#define APPLY(PFN, N, ...) \
do { \
	struct partially_applied * pfn = (PFN); \
	DATA_STORE(N, __VA_ARGS__) \
} while(0)


FUN make_fun(void (*function)(void *), void (*store)(void *, char const *, void *), void * data) {
	FUN f = malloc(sizeof(*f));
	if (f == NULL) {
		fprintf(stderr, "Allocation of FUN failed\n");
		exit(1);
	}
	f->function = function;
	f->store = store;
	f->data = data;
	return f;
}
void free_fun(FUN f) {
	free(f->data);
	free(f);
}
#define PARTIAL(fn) make_fun(&(partially_applied_ ## fn), \
                             &(partially_applied_ ## fn ## _store), \
                             allocate_partially_applied_ ## fn ## _data())





#define DISCARD(fn) free_fn(fn)

#define CALL(fn) (fn)->function((fn)->data)

void foo(char * str, int i) {
	printf("FOO| str = %s, i = %d\n", str, i);
}
void bar(float f, int i, size_t s) {
	printf("BAR| f = %f, i = %d, s = %zu\n", f, i, s);
}
MAKE_PARTIAL(foo, 2, char *, string, int, integer)
MAKE_PARTIAL(bar, 3, float, floating, int, INT, size_t, SOME_SIZE)


int main() {
	FUN f = PARTIAL(foo);
	char * c = "Crazy";
	APPLY(f, 1, string, c);
	printf("doing other stuff\n");
	FUN g = PARTIAL(bar);
	size_t size = 99;
	APPLY(g, 1, SOME_SIZE, size);
	int answer = 42;
	APPLY(f, 1, integer, answer);
	answer = 21;
	float pi = 3.14;
	APPLY(g, 2, INT, answer, floating, pi);
	CALL(f);
	printf("done\n");
	CALL(g);
	printf("now completely done\n");
	return 0;
}