#include <stdint.h>
#include <stdbool.h>

#define CWEB_ENABLE_DATABASE
#define CWEB_ENABLE_TEMPLATE

#define CWEB_STR(X) ((CWEB_String) { (X), (int) sizeof(X)-1 })

#define CWEB_TRACE(X, ...) fprintf(stderr, ("TRACE: " X "\n"), ##__VA_ARGS__);

typedef struct {
    char *ptr;
    int   len;
} CWEB_String;

typedef struct {
    char data[61];
} CWEB_PasswordHash;

CWEB_String cweb_trim(CWEB_String s);
bool        cweb_streq(CWEB_String a, CWEB_String b);

typedef enum {
    CWEB_VARG_TYPE_C,
    CWEB_VARG_TYPE_S,
    CWEB_VARG_TYPE_I,
    CWEB_VARG_TYPE_L,
    CWEB_VARG_TYPE_LL,
    CWEB_VARG_TYPE_SC,
    CWEB_VARG_TYPE_SS,
    CWEB_VARG_TYPE_SI,
    CWEB_VARG_TYPE_SL,
    CWEB_VARG_TYPE_SLL,
    CWEB_VARG_TYPE_UC,
    CWEB_VARG_TYPE_US,
    CWEB_VARG_TYPE_UI,
    CWEB_VARG_TYPE_UL,
    CWEB_VARG_TYPE_ULL,
    CWEB_VARG_TYPE_F,
    CWEB_VARG_TYPE_D,
    CWEB_VARG_TYPE_B,
    CWEB_VARG_TYPE_STR,
    CWEB_VARG_TYPE_HASH,
    CWEB_VARG_TYPE_PC,
    CWEB_VARG_TYPE_PS,
    CWEB_VARG_TYPE_PI,
    CWEB_VARG_TYPE_PL,
    CWEB_VARG_TYPE_PLL,
    CWEB_VARG_TYPE_PSC,
    CWEB_VARG_TYPE_PSS,
    CWEB_VARG_TYPE_PSI,
    CWEB_VARG_TYPE_PSL,
    CWEB_VARG_TYPE_PSLL,
    CWEB_VARG_TYPE_PUC,
    CWEB_VARG_TYPE_PUS,
    CWEB_VARG_TYPE_PUI,
    CWEB_VARG_TYPE_PUL,
    CWEB_VARG_TYPE_PULL,
    CWEB_VARG_TYPE_PF,
    CWEB_VARG_TYPE_PD,
    CWEB_VARG_TYPE_PB,
    CWEB_VARG_TYPE_PSTR,
    CWEB_VARG_TYPE_PHASH,
} CWEB_VArgType;

typedef struct {
    CWEB_VArgType type;
    union {
        char c;
        short s;
        int i;
        long l;
        long long ll;
        signed char sc;
        signed short ss;
        signed int si;
        signed long sl;
        signed long long sll;
        unsigned char uc;
        unsigned short us;
        unsigned int ui;
        unsigned long ul;
        unsigned long long ull;
        float f;
        double d;
        bool b;
        CWEB_String str;
        CWEB_PasswordHash hash;
        char *pc;
        short *ps;
        int *pi;
        long *pl;
        long long *pll;
        signed char *psc;
        signed short *pss;
        signed int *psi;
        signed long *psl;
        signed long long *psll;
        unsigned char *puc;
        unsigned short *pus;
        unsigned int *pui;
        unsigned long *pul;
        unsigned long long *pull;
        float *pf;
        double *pd;
        bool *pb;
        CWEB_String *pstr;
        CWEB_PasswordHash *phash;
    };
} CWEB_VArg;

typedef struct {
    int len;
    CWEB_VArg *ptr;
} CWEB_VArgs;

CWEB_VArg cweb_varg_from_c    (char c);
CWEB_VArg cweb_varg_from_s    (short s);
CWEB_VArg cweb_varg_from_i    (int i);
CWEB_VArg cweb_varg_from_l    (long l);
CWEB_VArg cweb_varg_from_ll   (long long ll);
CWEB_VArg cweb_varg_from_sc   (char sc);
CWEB_VArg cweb_varg_from_ss   (short ss);
CWEB_VArg cweb_varg_from_si   (int si);
CWEB_VArg cweb_varg_from_sl   (long sl);
CWEB_VArg cweb_varg_from_sll  (long long sll);
CWEB_VArg cweb_varg_from_uc   (char uc);
CWEB_VArg cweb_varg_from_us   (short us);
CWEB_VArg cweb_varg_from_ui   (int ui);
CWEB_VArg cweb_varg_from_ul   (long ul);
CWEB_VArg cweb_varg_from_ull  (long long ull);
CWEB_VArg cweb_varg_from_f    (float f);
CWEB_VArg cweb_varg_from_d    (double d);
CWEB_VArg cweb_varg_from_b    (bool b);
CWEB_VArg cweb_varg_from_str  (CWEB_String str);
CWEB_VArg cweb_varg_from_hash (CWEB_PasswordHash hash);
CWEB_VArg cweb_varg_from_pc   (char *pc);
CWEB_VArg cweb_varg_from_ps   (short *ps);
CWEB_VArg cweb_varg_from_pi   (int *pi);
CWEB_VArg cweb_varg_from_pl   (long *pl);
CWEB_VArg cweb_varg_from_pll  (long long *pll);
CWEB_VArg cweb_varg_from_psc  (signed char *psc);
CWEB_VArg cweb_varg_from_pss  (signed short *pss);
CWEB_VArg cweb_varg_from_psi  (signed int *psi);
CWEB_VArg cweb_varg_from_psl  (signed long *psl);
CWEB_VArg cweb_varg_from_psll (signed long long *psll);
CWEB_VArg cweb_varg_from_puc  (unsigned char *puc);
CWEB_VArg cweb_varg_from_pus  (unsigned short *pus);
CWEB_VArg cweb_varg_from_pui  (unsigned int *pui);
CWEB_VArg cweb_varg_from_pul  (unsigned long *pul);
CWEB_VArg cweb_varg_from_pull (unsigned long long *pull);
CWEB_VArg cweb_varg_from_pf   (float *pf);
CWEB_VArg cweb_varg_from_pd   (double *pd);
CWEB_VArg cweb_varg_from_pb   (bool *pb);
CWEB_VArg cweb_varg_from_pstr (CWEB_String *pstr);
CWEB_VArg cweb_varg_from_phash(CWEB_PasswordHash *phash);

#define __CWEB_HELPER_ARG(X) (_Generic((X),   \
    char              : cweb_varg_from_c,     \
    short             : cweb_varg_from_s,     \
    int               : cweb_varg_from_i,     \
    long              : cweb_varg_from_l,     \
    long long         : cweb_varg_from_ll,    \
    signed char       : cweb_varg_from_sc,    \
    /*signed short      : cweb_varg_from_ss,*/  \
    /*signed int        : cweb_varg_from_si,*/  \
    /*signed long       : cweb_varg_from_sl,*/  \
    /*signed long long  : cweb_varg_from_sll,*/ \
    unsigned char     : cweb_varg_from_uc,    \
    unsigned short    : cweb_varg_from_us,    \
    unsigned int      : cweb_varg_from_ui,    \
    unsigned long     : cweb_varg_from_ul,    \
    unsigned long long: cweb_varg_from_ull,   \
    float             : cweb_varg_from_f,     \
    double            : cweb_varg_from_d,     \
    bool              : cweb_varg_from_b,     \
    CWEB_String       : cweb_varg_from_str,   \
    CWEB_PasswordHash : cweb_varg_from_hash,  \
    char*              : cweb_varg_from_pc,   \
    short*             : cweb_varg_from_ps,   \
    int*               : cweb_varg_from_pi,   \
    long*              : cweb_varg_from_pl,   \
    long long*         : cweb_varg_from_pll,  \
    signed char*       : cweb_varg_from_psc,  \
    /*signed short*     : cweb_varg_from_pss,*/  \
    /*signed int*       : cweb_varg_from_psi,*/  \
    /*signed long*      : cweb_varg_from_psl,*/  \
    /*signed long long* : cweb_varg_from_psll,*/ \
    unsigned char*     : cweb_varg_from_puc,  \
    unsigned short*    : cweb_varg_from_pus,  \
    unsigned int*      : cweb_varg_from_pui,  \
    unsigned long*     : cweb_varg_from_pul,  \
    unsigned long long*: cweb_varg_from_pull, \
    float*             : cweb_varg_from_pf,   \
    double*            : cweb_varg_from_pd,   \
    bool*              : cweb_varg_from_pb,   \
    CWEB_String*       : cweb_varg_from_pstr, \
    CWEB_PasswordHash* : cweb_varg_from_phash \
))(X)

// Helper macros
#define __CWEB_HELPER_DISPATCH_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N
#define __CWEB_HELPER_CONCAT_0(A, B) A ## B
#define __CWEB_HELPER_CONCAT_1(A, B) __CWEB_HELPER_CONCAT_0(A, B)
#define __CWEB_HELPER_ARGS_0()                       (CWEB_VArgs) { 0, (CWEB_VArg[]) {}}
#define __CWEB_HELPER_ARGS_1(a)                      (CWEB_VArgs) { 1, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a) }}
#define __CWEB_HELPER_ARGS_2(a, b)                   (CWEB_VArgs) { 2, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b) }}
#define __CWEB_HELPER_ARGS_3(a, b, c)                (CWEB_VArgs) { 3, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c) }}
#define __CWEB_HELPER_ARGS_4(a, b, c, d)             (CWEB_VArgs) { 4, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d) }}
#define __CWEB_HELPER_ARGS_5(a, b, c, d, e)          (CWEB_VArgs) { 5, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e) }}
#define __CWEB_HELPER_ARGS_6(a, b, c, d, e, f)       (CWEB_VArgs) { 6, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e), __CWEB_HELPER_ARG(f) }}
#define __CWEB_HELPER_ARGS_7(a, b, c, d, e, f, g)    (CWEB_VArgs) { 7, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e), __CWEB_HELPER_ARG(f), __CWEB_HELPER_ARG(g) }}
#define __CWEB_HELPER_ARGS_8(a, b, c, d, e, f, g, h) (CWEB_VArgs) { 8, (CWEB_VArg[]) { __CWEB_HELPER_ARG(a), __CWEB_HELPER_ARG(b), __CWEB_HELPER_ARG(c), __CWEB_HELPER_ARG(d), __CWEB_HELPER_ARG(e), __CWEB_HELPER_ARG(f), __CWEB_HELPER_ARG(g), __CWEB_HELPER_ARG(h) }}
#define __CWEB_COUNT_ARGS(...) __CWEB_HELPER_DISPATCH_N(DUMMY, ##__VA_ARGS__, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)

#define CWEB_VARGS(...) __CWEB_HELPER_CONCAT_1(__CWEB_HELPER_ARGS_, __CWEB_COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__)

typedef struct CWEB CWEB;
typedef struct CWEB_Request CWEB_Request;

int  cweb_global_init(void);
void cweb_global_free(void);

// TODO: comment
CWEB *cweb_init(CWEB_String addr, uint16_t port, uint16_t secure_port,
    CWEB_String cert_key, CWEB_String private_key);

// TODO: comment
void cweb_free(CWEB *cweb);

// TODO: comment
int cweb_add_website(CWEB *cweb, CWEB_String domain, CWEB_String cert_file, CWEB_String key_file);

// TODO: comment
int cweb_create_test_certificate(CWEB_String C, CWEB_String O,
    CWEB_String CN, CWEB_String cert_file, CWEB_String key_file);

// Open an SQLite instance in file "database_file" and run the DDL script at "schema_file".
// Note that "database_file" may be ":memory:". 
int cweb_enable_database(CWEB *cweb, CWEB_String database_file, CWEB_String schema_file);

// Log all evaluated SQL statements to stdout
void cweb_trace_sql(CWEB *cweb, bool enable);

// TODO: comment
void cweb_enable_template_cache(CWEB *cweb, bool enable);

// Pause execution until a request is available.
// TODO: When does this function return NULL?
CWEB_Request *cweb_wait(CWEB *cweb);

bool cweb_match_domain(CWEB_Request *req, CWEB_String str);

// Returns true iff the request matches the specified endpoint
bool cweb_match_endpoint(CWEB_Request *req, CWEB_String str);

// Returns the CSRF token associated to the current session 
CWEB_String cweb_get_csrf(CWEB_Request *req);

// Returns the user ID for the current session, or -1 if there is no session
int cweb_get_user_id(CWEB_Request *req);

// Sets the user ID for the current session (it must be a positive integer).
// If the ID is -1, the session is deleted.
int cweb_set_user_id(CWEB_Request *req, int user_id);

// TODO: comment
CWEB_String cweb_get_path(CWEB_Request *req);

// Returns the request parameter with the specified name
// If the request uses POST, the parameter is taken from the body,
// else it's taken from the URL. If the parameter is not present,
// an empty string is returned.
CWEB_String cweb_get_param_s(CWEB_Request *req, CWEB_String name);

// Like cweb_get_param_s, but also parser the argument as an integer.
// If parsing fails or the parameter is missing, -1 is returned.
int cweb_get_param_i(CWEB_Request *req, CWEB_String name);

// Create a string by evaluating a format. Memory is allocated from the arena of the request.
// If the arena is full, an empty string is returned.
CWEB_String cweb_format_impl(CWEB_Request *req, char *fmt, CWEB_VArgs args);

// Helper
#define cweb_format(req, fmt, ...) cweb_format_impl((req), (fmt), CWEB_VARGS(__VA_ARGS__))

// Responds to the specified request with the given status code and content
void cweb_respond_basic(CWEB_Request *req, int status, CWEB_String content);

// Responds to the request by redirecting the client to the given target
void cweb_respond_redirect(CWEB_Request *req, CWEB_String target);

// Responds to the request by evaluating a WL template file
void cweb_respond_template_impl(CWEB_Request *req, int status, CWEB_String template_file, CWEB_VArgs args);

// Helper
#define cweb_respond_template(req, status, template_file, ...) \
    cweb_respond_template_impl((req), (status), (template_file), CWEB_VARGS(__VA_ARGS__))

// Evaluates an SQL INSERT statement and returns the ID of the last inserted row. On error -1 is returned
int64_t cweb_database_insert_impl(CWEB *cweb, char *fmt, CWEB_VArgs args);

// Helper
#define cweb_database_insert(cweb, fmt, ...) \
    cweb_database_insert_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__))

// Iterator over database rows
typedef struct { void *handle; } CWEB_QueryResult;

// Evaluates an SQL SELECT statement, returning a scanner over the returned rows.
// You don't have to check for errors with this function
CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, char *fmt, CWEB_VArgs args);

// Helper
#define cweb_database_select(cweb, fmt, ...) cweb_database_select_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__))

// Returns the next row from the query result iterator.
int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args);

// Helper
#define cweb_next_query_row(res, ...) cweb_next_query_row_impl((res), CWEB_VARGS(__VA_ARGS__))

// Frees the result of a database query
void cweb_free_query_result(CWEB_QueryResult *res);

// Calculates the bcrypt hash of the specified password
int cweb_hash_password(CWEB_String pass, int cost, CWEB_PasswordHash *hash);

// Checks whether the password matches the given hash
int cweb_check_password(CWEB_String pass, CWEB_PasswordHash hash);
