#ifndef LISP_H
#define LISP_H
#include <stdio.h>
#include <stdbool.h>
#include <stdarg.h>

typedef enum {
  CHAR,         // 0
  SYMBOL,       // 1
  NUMBER,       // 2
  NATIVE_FUNC,  // 3
  SPECIAL_FORM, // 4
  STREAM,       // 5
  FUNC,         // 6
  MACRO,        // 7
  STRING,       // 8
  TABLE,        // 9
  ARRAY,        // 10
  CONS,         // 11
  ERROR,        // 12
  TYPE_ALL      // 13
} Type;

typedef unsigned Pointer;
typedef unsigned Char;

typedef struct {
  Pointer car, cdr;
} Cons;

typedef double Number;

typedef struct {
  unsigned length;
  char data[];
} Symbol;

typedef struct {
  unsigned length;
  char data[];
} String;

typedef struct {
  unsigned length;
  char data[];
} Error;

typedef struct {
  unsigned length, size;
  Pointer data[];
} Array;

typedef struct {
  Pointer code, env;
} Func;

typedef struct {
  unsigned offset, length;
} TableHash;

typedef struct {
  Pointer key, value;
} TablePair;

typedef union {
  TableHash hash;
  TablePair pair;
} TableData;

typedef struct {
  unsigned length, size;
  TableData data[];
} Table;

/* CHANGER POUR TABLEAU DE FONCTIONS */
typedef Pointer (*NativeFunc)(Pointer params, Pointer env);
typedef Pointer (*SpecialForm)(Pointer params, Pointer env);

typedef FILE* Stream;

typedef union {
  Cons cons;
  Symbol symbol;
  Number number;
  Table table;
  NativeFunc nativeFunc;
  SpecialForm specialForm;
  Func func;
  String string;
  Stream stream;
  Array array;
  Char c;
  Error error;
  Pointer next;
} Data;

typedef struct {
  /* Header header; */
  Type type: 4;
  bool garbage: 1;
  size_t size: sizeof(size_t) * 8 - 5; 
  Data data[];
} Block; // 16 Bytes

typedef struct Memory {
  Block* buffer;
  Pointer freelist;
  size_t used, size; // Actual bytes
} Memory;

extern Pointer NIL;
extern Pointer UNDEFINED;
extern Pointer T;
extern Pointer STANDARD_INPUT;
extern Pointer STANDARD_OUTPUT;
extern Pointer BODY;
extern Pointer REST;

/** UTILS **/
void init(void);
void memory_init(size_t);
void symbol_init(void);
void reader_init(void);
void environment_init(void);

void    memory_free(void);
Block*  memory_get(Pointer);
Pointer memory_new(Type, size_t);
void    memory_destroy(Pointer);
Pointer memory_resize(Pointer, size_t);

void repl(void);

Pointer array_push(Pointer, Pointer);
Pointer array_pop(Pointer);
Pointer array_set(Pointer, size_t, Pointer);
Pointer array_get(Pointer, size_t);
size_t  array_length(Pointer);

Pointer table_get(Pointer, Pointer);
Pointer table_set(Pointer, Pointer, Pointer);

Pointer string_push(Pointer str, char c);
Pointer string_clear(Pointer str);

Pointer environment_get(Pointer env, Pointer key);
Pointer environment_set(Pointer env, Pointer key, Pointer value);

Char get_utf8(FILE* s);
Char unget_utf8(Char c, FILE* s);
Char peek_char(Pointer type, Stream stream);
Pointer read_char_macro(Pointer args, Pointer env);
Pointer read_list_macro(Pointer args, Pointer env);
Pointer read_right_paren_macro(Pointer args, Pointer env);

Pointer prin1(Pointer data, Stream stream);
Pointer print(Pointer data, Stream stream);

Pointer eval(Pointer data, Pointer env);

/* CONSTRUCTORS */
Pointer array(size_t size);
Pointer table(size_t size);
Pointer symbol(char* string, size_t size);
#define symbol1(s) symbol(s, sizeof(s) - 1)
Pointer string(char* string, size_t size);
Pointer cons(Pointer car, Pointer cdr);
Pointer number(Number num);
Pointer func(Pointer code, Pointer env);
Pointer macro(Pointer code, Pointer env);
Pointer native_func(NativeFunc func);
Pointer special_form(SpecialForm func);
Pointer character(Char c);
Pointer stream(FILE* s);

/* FUNCTIONS */
Pointer eval_fn(Pointer args, Pointer env);
Pointer cons_fn(Pointer args, Pointer env);
Pointer car_fn(Pointer args, Pointer env);
Pointer cdr_fn(Pointer args, Pointer env);
Pointer reduce_fn(Pointer args, Pointer env);
Pointer list_fn(Pointer args, Pointer env);
Pointer add_fn(Pointer args, Pointer env);
Pointer sub_fn(Pointer args, Pointer env);
Pointer mul_fn(Pointer args, Pointer env);
Pointer div_fn(Pointer args, Pointer env);
Pointer pow_fn(Pointer args, Pointer env);
Pointer sqrt_fn(Pointer args, Pointer env);
Pointer logand_fn(Pointer args, Pointer env);
Pointer logor_fn(Pointer args, Pointer env);
Pointer logxor_fn(Pointer args, Pointer env);
Pointer lognot_fn(Pointer args, Pointer env);
Pointer peek_char_fn(Pointer args, Pointer env);
Pointer read_char_fn(Pointer args, Pointer env);
Pointer read_fn(Pointer args, Pointer env);
Pointer set_reader_macro_fn(Pointer args, Pointer env);
Pointer print_fn(Pointer args, Pointer env);
Pointer not_fn(Pointer args, Pointer env);
Pointer exit_fn(Pointer args, Pointer env);
Pointer eq_fn(Pointer args, Pointer env);
Pointer lt_fn(Pointer args, Pointer env);
Pointer gt_fn(Pointer args, Pointer env);
Pointer le_fn(Pointer args, Pointer env);
Pointer ge_fn(Pointer args, Pointer env);

/* SPECIAL FORMS */
Pointer if_fn(Pointer args, Pointer env);
Pointer let_fn(Pointer args, Pointer env);
Pointer quote_fn(Pointer args, Pointer env);
Pointer and_fn(Pointer args, Pointer env);
Pointer or_fn(Pointer args, Pointer env);
Pointer def_fn(Pointer args, Pointer env);
Pointer set_fn(Pointer args, Pointer env);
Pointer fn_fn(Pointer args, Pointer env);
Pointer defmacro_fn(Pointer args, Pointer env);

#define TYPE(p)         memory_get(p)->type
#define SIZE(p)         memory_get(p)->size
#define GET(p)          memory_get(p)->data
#define NEXT(p)         GET(p)->next
#define CONS(p)         GET(p)->cons
#define NUMBER(p)       GET(p)->number
#define SYMBOL(p)       GET(p)->symbol
#define STRING(p)       GET(p)->string
#define CHAR(p)         GET(p)->c
#define ARRAY(p)        GET(p)->array
#define TABLE(p)        GET(p)->table
#define SPECIAL_FORM(p) GET(p)->specialForm
#define NATIVE_FUNC(p)  GET(p)->nativeFunc
#define FUNC(p)         GET(p)->func
#define STREAM(p)       GET(p)->stream

#define CAR(p) (p == NIL ? NIL : CONS(p).car)
#define CDR(p) (p == NIL ? NIL : CONS(p).cdr)

#define ELEVENTH_ARGUMENT(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, ...) a11
#define COUNT_ARGUMENTS(...) ELEVENTH_ARGUMENT(dummy, ## __VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
/* #define LIST(...) list_new(COUNT_ARGUMENTS(__VA_ARGS__), __VA_ARGS__) */

#define LIST_0 NIL
#define LIST_1(a) cons(a, LIST_0)
#define LIST_2(a, ...) cons(a, LIST_1(__VA_ARGS__))
#define LIST_3(a, ...) cons(a, LIST_2(__VA_ARGS__))
#define LIST_4(a, ...) cons(a, LIST_3(__VA_ARGS__))
#define LIST_5(a, ...) cons(a, LIST_4(__VA_ARGS__))
#define LIST_6(a, ...) cons(a, LIST_5(__VA_ARGS__))
#define LIST_7(a, ...) cons(a, LIST_6(__VA_ARGS__))
#define LIST_8(a, ...) cons(a, LIST_7(__VA_ARGS__))
#define LIST_9(a, ...) cons(a, LIST_8(__VA_ARGS__))

#define LIST__(n, ...) LIST_##n(__VA_ARGS__)
#define LIST_(n, ...) LIST__(n, __VA_ARGS__)
#define LIST(...) LIST_(COUNT_ARGUMENTS(__VA_ARGS__), __VA_ARGS__)

#define REDUCE(list, reducer, previous)				\
  while(list != NIL) {						\
    previous = reducer;						\
    list = CDR(list);						\
  }							       

#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
#define MAX(X, Y) (((X) > (Y)) ? (X) : (Y))

#endif