#ifndef TGSCHEME_H
#define TGSCHEME_H
#include "scheme.h"
#include <string.h>
#include <stdarg.h>

#define method(sc, name, ...)				\
	sc->vptr->name(__VA_ARGS__)

// We can get away with it, because mk() is mainly created to create
// pointer-s from literal data and not something complex. So if one
// passes a void * pointer to it, it likely is a nullptr (or its
// equivalent)
#if (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 202311L) && !defined(__cplusplus)
typedef void *nullptr_t;
#else
#endif

// this part is questionable:
// void *: (sc)->NIL
// But it is a case that works well with pre-C23 nullptr et al.
// Because it's unlikely anyone would pass in a void * that is not NULL
//
// _Bool case never fires, because true and false are aliases for 1
// and 0?
#define mk(sc, ...)							\
	_Generic((__VA_ARGS__),						\
		 pointer: (__VA_ARGS__),				\
		 _Bool: (__VA_ARGS__ ? (sc)->T : (sc)->F),		\
		 char *: method(sc, mk_string, sc, (char *)__VA_ARGS__), \
		 nullptr_t: (sc)->NIL,					\
		 int: ((0 == strcmp("true", #__VA_ARGS__))		\
		       ? (sc)->T					\
		       : (0 == strcmp("false", #__VA_ARGS__))		\
		       ? (sc)->F					\
		       : ((#__VA_ARGS__)[0] == '\'')			\
		       ? method(sc, mk_character, sc, (int)__VA_ARGS__) \
		       : method(sc, mk_integer, sc, (long long)__VA_ARGS__)), \
		 default: method(sc, mk_integer, sc, (long long)__VA_ARGS__))

// See the unsigned long and unsigned long long cases: these might not
// fit into long long due to compiler / platform restrictions, so we
// pre-emptively cast them to reals instead.
#define mknum(sc, ...)							\
	_Generic((__VA_ARGS__),						\
		 short: method(sc, mk_integer, sc, (long long)__VA_ARGS__), \
		 unsigned short: method(sc, mk_integer, sc, (long long)__VA_ARGS__), \
		 int: (((#__VA_ARGS__)[0] == '\'')			\
		       ? method(sc, mk_integer, sc, (int)__VA_ARGS__)	\
		       : method(sc, mk_integer, sc, (long long)__VA_ARGS__)), \
		 unsigned int: method(sc, mk_integer, sc, (long long)__VA_ARGS__), \
		 long: method(sc, mk_integer, sc, (long long)__VA_ARGS__), \
		 unsigned long: method(sc, mk_real, sc, __VA_ARGS__),	\
		 long long: method(sc, mk_integer, sc, (long long)__VA_ARGS__), \
		 unsigned long long: method(sc, mk_real, sc, __VA_ARGS__), \
		 default: method(sc, mk_real, sc, (long double)__VA_ARGS__))

#define mksym(sc, ...)							\
	_Generic((__VA_ARGS__),						\
		 char *: method(sc, mk_symbol, sc, __VA_ARGS__),	\
		 pointer: __VA_ARGS__)

#define define(sc, env, name, ...)					\
	scheme_define(sc, env, mksym(sc, name), mk(sc, __VA_ARGS__))

#define eval(sc, ...)							\
	_Generic((__VA_ARGS__),						\
		 char *: scheme_eval(sc, mksym(sc, __VA_ARGS__)),	\
		 pointer: scheme_eval(sc, (pointer)__VA_ARGS__))

extern inline size_t
length(scheme *sc, pointer p)
{
	scheme_interface *si = sc->vptr;
	if (si->is_string(p))
		return si->string_length(p);
	else if (si->is_list(sc, p))
		return si->list_length(sc, p);
	else if (si->is_bvector(p))
		return si->bvector_length(p);
	else if (si->is_vector(p))
		return si->vector_length(p);
	else if (si->is_struct(p))
		return si->struct_length(p);
	else
		return 0;
}

pointer
list(scheme *sc, size_t length, ...)
{
	va_list args;
	va_start(args, length);
	scheme_interface *si = sc->vptr;
	pointer arglist = sc->NIL;
	for (size_t i = 0; i < length; ++i)
		arglist = si->cons(sc, va_arg(args, pointer), arglist);
	va_end(args);
	// Reverse the list, the last arg (car of arglist) is an actual pair
	pointer final_list = si->pair_car(arglist);
	for (pointer iter = si->pair_cdr(arglist); si->is_pair(iter);
	     iter = si->pair_cdr(iter))
		final_list = si->cons(sc, si->pair_car(iter), final_list);
	return final_list;
}

#define apply(sc, fn, ...)						\
	scheme_call(sc, fn,						\
		    list(sc,						\
			 sizeof((pointer[]){__VA_ARGS__}) / sizeof(pointer), \
			 __VA_ARGS__))

#endif
