#ifndef LIB_H
# define LIB_H

# ifndef _STDINT_H
#  define LIB_H_STDINT

#  define intmax_t long int
# endif /* _STDINT_H */

# ifndef _UNISTD_H
#  define LIB_H_UNISTD

#  define R_OK 4
#  define W_OK 2
#  define X_OK 1
#  define F_OK 0
# endif /* _UNISTD_H */

# ifndef NULL
#  define LIB_H_NULL
#  define NULL ((void *) 0)
# endif /* NULL */


# ifndef _STDBOOL_H
#  define LIB_H_STDBOOL

#  define bool unsigned char
#  define true 1
#  define false 0
# endif /* _STDBOOL_H */

/* --------------------------- START GLOBAL DEF ---------------------------- */

# define ALLOC_FUNC void *(*alloc)(u64)
# define FREE_FUNC void (*free)(void *)

# define MACRO_STR_CONCAT_X(lhs, rhs) lhs ## rhs
# define MACRO_STR_CONCAT(lhs, rhs) MACRO_STR_CONCAT_X(lhs, rhs)

# define MACRO_STR_VALUE(arg) #arg
# define MACRO_STR_VALUE_X(arg) MACRO_STR_VALUE(arg)

# define BOOL2CSTR(bool) ((bool) ? "True" : "False")

# define UNUSED(arg) (void) arg

enum err {
    ERR_OK = 0,
    ERR_NOT_INT,
    ERR_TOO_BIG,
    ERR_NULL_ARG,
    ERR_INVALID_ARG,
    ERR_NOT_FOUND,
    ERR_INDEX_TOO_LARGE,
    ERR_FAILED_OPEN,
    ERR_FAILED_CLOSE,
    ERR_FAILED_READ,
    ERR_FAILED_WRITE,
    ERR_WROTE_WRONG_AMOUNT,
    ERR_FAILED_ALLOC,
    ERR_MKDIR_FAILED,
    ERR_STR_EMPTY,
    ERR_PATH_EMPTY,
    ERR_PATH_INVALID,
    ERR_PATH_FILE_EMPTY,
    ERR_PATH_FILE_FAILED_SEEK,
    ERR_SGFX_POS_OUTSIDE_CANVAS
};

const char *err_to_name[] = {
    "ERR_OK",
    "ERR_NOT_INT",
    "ERR_TOO_BIG",
    "ERR_NULL_ARG",
    "ERR_INVALID_ARG",
    "ERR_NOT_FOUND",
    "ERR_INDEX_TOO_LARGE",
    "ERR_FAILED_OPEN",
    "ERR_FAILED_CLOSE",
    "ERR_FAILED_READ",
    "ERR_FAILED_WRITE",
    "ERR_WROTE_WRONG_AMOUNT",
    "ERR_FAILED_ALLOC",
    "ERR_MKDIR_FAILED",
    "ERR_PATH_EMPTY",
    "ERR_PATH_INVALID",
    "ERR_PATH_FILE_EMPTY",
    "ERR_PATH_FILE_FAILED_SEEK",
    "ERR_SGFX_POS_OUTSIDE_CANVAS",
};

/* ---------------------------- END GLOBAL DEF ----------------------------- */

/* ----------------------------- START INT DEF ----------------------------- */

typedef          long int i64;
typedef unsigned long int u64;

typedef          int i32;
typedef unsigned int u32;

typedef          short int i16;
typedef unsigned short int u16;

typedef          char i8;
typedef unsigned char u8;

/* ------------------------------ END INT DEF ------------------------------ */

void *malloc(u64 size);
void *realloc(void *ptr, u64 new_size);
void free(void *ptr);
void *memcpy(void *dest, const void *src, u64 n);
void *memset(void *s, int c, u64 n);
char *getenv(const char *name);

/* ----------------------------- START LIB DEF ----------------------------- */

# define LIB_COALESCE2(arg1, arg2) ((arg1 != Null) ? arg1 : arg2)

# define LIB_SET_IF_NULL(var, val) \
    if ( (var) == NULL ) { \
        (var) = (val); \
    }

# define LIB_SET_IF_NOT_NULL(var, err) \
    if ( var != NULL ) { \
        *var = err; \
    }

# define LIB_ARG_IF_NOT_NULL_MUST_BE(arg, val, ret_val) \
    if ( arg != NULL ) { \
        if ( *arg != val ) { \
            return ret_val; \
        } \
    }

# define LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(arg, val) \
    if ( arg != NULL ) { \
        if ( *arg != val ) { \
            return *arg; \
        } \
    }

# define LIB_ARG_MUST_NOT_BE_NULL_RET_ERR(arg) \
    if ( (arg) == NULL ) { \
        return ERR_NULL_ARG; \
    }

# define LIB_ARG_MUST_NOT_BE_NULL(arg, err_var, ret_val) \
    if ( (arg) == NULL ) { \
        LIB_SET_IF_NOT_NULL(err_var, ERR_NULL_ARG); \
        return ret_val; \
    }

# define LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(arg, err_var) \
    if ( (arg) == NULL ) { \
        LIB_SET_IF_NOT_NULL(err_var, ERR_NULL_ARG); \
        return ERR_NULL_ARG; \
    }

# define LIB_STR_MUST_NOT_BE_EMPTY(arg, err_var, ret_val) \
    if ( (arg).data == NULL || (arg).size == 0 ) { \
        LIB_SET_IF_NOT_NULL(err_var, ERR_STR_EMPTY); \
        return ret_val; \
    }


/* ------------------------------ END LIB DEF ------------------------------ */

/* ----------------------------- START VEC DEF ----------------------------- */

# if defined(WANT_VEC2) || defined(WANT_ALL)

#  define _VEC2_STRUCT_DEF(type) \
    struct MACRO_STR_CONCAT(type, vec2) { \
        type x, y; \
    }; \
    struct MACRO_STR_CONCAT(type, vec2s) { \
        type w, h; \
    }


_VEC2_STRUCT_DEF(i64);
_VEC2_STRUCT_DEF(u64);

_VEC2_STRUCT_DEF(i32);
_VEC2_STRUCT_DEF(u32);

_VEC2_STRUCT_DEF(i16);
_VEC2_STRUCT_DEF(u16);

_VEC2_STRUCT_DEF(i8);
_VEC2_STRUCT_DEF(u8);

#  undef _VEC2_STRUCT_DEF
# endif /* defined(WANT_VEC2) || defined(WANT_ALL) */

/* ------------------------------ END VEC DEF ------------------------------ */

/* ---------------------------- START CSTR DEF ----------------------------- */

# if defined(WANT_CSTR) || defined(WANT_ALL)

u64 cstr_len_max(const char *cstr, u64 max,
            bool *out_hit_max, enum err *out_err);

# endif /* defined(WANT_CSTR) || defined(WANT_ALL) */

/* ----------------------------- END CSTR DEF ------------------------------ */

/* ---------------------------- START STR DEF ------------------------------ */

# if defined(WANT_STR) || defined(WANT_ALL)

# ifndef STR_SIZE_LIMIT
#  define STR_SIZE_LIMIT 1028
# endif /* STR_SIZE_LIMIT */

struct str {
    const char *data;
    u64 size;
    bool should_be_freed;
};

const struct str STR_EMPTY = {"", 0, false};

struct str_tokenizer {
    struct str str;
    u64 cur;
    char c;
};

struct str str_from_cstr(const char *cstr, u64 cstr_size,
                enum err *out_err);
struct str str_from_cstr_ns(const char *cstr, enum err *out_err);

struct str str_from_i64(i64 num, enum err *out_err);
struct str str_from_i64_temp(i64 num);

struct str str_dup(struct str str);
struct str str_replace_escape_chars(const struct str *str, enum err *out_err);

intmax_t str_to_int(struct str str, enum err *err);

struct str str_rstrip(struct str str);
struct str str_lstrip(struct str str);
struct str str_strip(struct str str);

u64 str_lindex(struct str str, char c);
u64 str_rindex(struct str str, char c);

struct str_tokenizer str_tokenize(struct str str, char c);
struct str str_tokenizer_next(struct str_tokenizer *st);

struct str str_slice(struct str str, u64 from, u64 to);

bool str_eq_cstr(struct str str, const char *cstr, u64 cstr_size);
bool str_eq_str(struct str str1, struct str str2);
bool str_startswith_cstr(struct str str, const char *cstr, u64 cstr_size);

bool str_is_int(struct str str);

struct str_builder {
    char *data;
    u64 size;
    u64 cap;
};

struct str str_builder_to_str(const struct str_builder *str_bldr);

#  if defined(IMP) || defined(IMP_STR)

struct str
str_replace_escape_chars(const struct str *str, enum err *out_err)
{
    struct str empty = {0};
    struct str_builder ret = {0};
    u64 i = 0;

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, empty);
    LIB_ARG_MUST_NOT_BE_NULL(str, out_err, empty);
    LIB_STR_MUST_NOT_BE_EMPTY(*str, out_err, empty);

    ret.data = malloc(str->size);
    ret.size = 0;

    if ( ret.data == NULL ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_ALLOC);
        return empty;
    }
    memset((void*)ret.data, 0, str->size);

    for ( i = 0; i < str->size; ++i ) {
        if ( str->data[i] == '\\' ) {
            if ( (i+1) < str->size ) {
                ++i;
                switch ( str->data[i] ) {
                case 'n': ret.data[ret.size++] = '\n'; break;
                case 'r': ret.data[ret.size++] = '\r'; break;
                case 't': ret.data[ret.size++] = '\t'; break;
                case 'e': ret.data[ret.size++] = '\033'; break;
                default: ret.data[ret.size++] = '\\';
                         ret.data[ret.size++] = str->data[i];
                         break;
                }
                continue;
            }
        }
        ret.data[ret.size++] = str->data[i];
    }

    return str_builder_to_str(&ret);
}

#  endif /* defined(IMP) || defined(IMP_STR) */

# endif /* defined(WANT_STR) || defined(WANT_ALL) */

/* ----------------------------- END STR DEF ------------------------------- */

/* -------------------------- START DYN ARR DEF ---------------------------- */

# if defined(WANT_DYN_ARR) || defined(WANT_ALL)

struct dyn_arr {
    void *data;
    u64   size;
    u64   cap;
    u16   elem_size;
};

struct dyn_arr dyn_arr_create(u16 elem_size, enum err *out_err);

enum err dyn_arr_append(struct dyn_arr *da, void *elem, enum err *out_err);
enum err dyn_arr_prepend(struct dyn_arr *da, void *elem, enum err *out_err);
enum err dyn_arr_insert(struct dyn_arr *da, void *elem, u64 index,
                        enum err *out_err);
void *dyn_arr_pop(struct dyn_arr *da, u64 index, enum err *out_err);
void *dyn_arr_pop_last(struct dyn_arr *da, enum err *out_err);

enum err dyn_arr_destroy(struct dyn_arr *da, enum err *out_err);


#  if defined(IMP) || defined(IMP_DYN_ARR)

struct dyn_arr
dyn_arr_create(u16 elem_size, enum err *out_err)
{
    struct dyn_arr empty = {0};
    struct dyn_arr da = {0};

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, empty);

    da.size = 0;
    da.cap  = 64;
    da.elem_size = elem_size;
    da.data = malloc(da.elem_size * da.cap);
    if ( da.data == NULL ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_ALLOC);
        return empty;
    }
    memset(da.data, 0, elem_size * da.cap);


    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return da;
}

enum err
dyn_arr_insert(struct dyn_arr *da, void *elem, u64 index, enum err *out_err)
{
    u64 new_size = 0;
    u64 i = 0;
    u8 *data = NULL;

    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);

    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(da,       out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(da->data, out_err);

    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(elem,     out_err);

    new_size = da->size + 1;
    if ( index >= new_size ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_INDEX_TOO_LARGE);
        return ERR_INDEX_TOO_LARGE;
    }

    if ( new_size >= da->cap ) {
        da->cap *= 2;
        da->data = realloc(da->data, da->cap);
        if ( da->data == NULL ) {
            LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_ALLOC);
            return ERR_FAILED_ALLOC;
        }
    }

    data = (u8*) da->data;

    if ( da->size > 0 ) {
        for ( i = da->size-1; i > index; i-- ) {
            memcpy((data + i), (data + i - 1), da->elem_size);
        }
    }

    memcpy((data + index), elem, da->elem_size);

    ++da->size;

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}

enum err
dyn_arr_append(struct dyn_arr *da, void *elem, enum err *out_err)
{
    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);

    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(da,       out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(da->data, out_err);

    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(elem,     out_err);

    return dyn_arr_insert(da, elem, da->size, out_err);
}

enum err
dyn_arr_prepend(struct dyn_arr *da, void *elem, enum err *out_err)
{
    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);

    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(da,       out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(da->data, out_err);

    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(elem,     out_err);

    return dyn_arr_insert(da, elem, 0, out_err);
}

void *
dyn_arr_pop(struct dyn_arr *da, u64 index, enum err *out_err)
{
    static u8 temp_mem[256] = {0};
    u8 *data = NULL;
    u64 i = 0;

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, NULL);

    LIB_ARG_MUST_NOT_BE_NULL(da,       out_err, NULL);
    LIB_ARG_MUST_NOT_BE_NULL(da->data, out_err, NULL);

    if ( index >= da->size ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_INDEX_TOO_LARGE);
        return NULL;
    }

    data = (u8*) da->data;

    memcpy(temp_mem, (data + index), da->elem_size);

    for ( i = index; i < da->size-1; ++i ) {
        memcpy((data + i), (data + i + 1), da->elem_size);
    }

    --da->size;

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return temp_mem;
}

void *
dyn_arr_pop_last(struct dyn_arr *da, enum err *out_err)
{
    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, NULL);

    LIB_ARG_MUST_NOT_BE_NULL(da,       out_err, NULL);
    LIB_ARG_MUST_NOT_BE_NULL(da->data, out_err, NULL);

    return dyn_arr_pop(da, da->size-1, out_err);
}

enum err
dyn_arr_destroy(struct dyn_arr *da, enum err *out_err)
{
    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);

    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(da,       out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(da->data, out_err);

    free(da->data);

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}

#  endif /* defined(IMP) || defined(IMP_DYN_ARR) */

# endif /* defined(WANT_DYN_ARR) || defined(WANT_ALL) */

/* --------------------------- END DYN ARR DEF ----------------------------- */

/* ---------------------------- START ENV DEF ------------------------------ */

# if defined(WANT_ENV) || defined(WANT_ALL)

struct str getenv_as_str(const char *env, enum err *out_err);
struct path getenv_as_path(const char *env, enum err *out_err);

# endif /* defined(WANT_ENV) || defined(WANT_ALL) */

/* ----------------------------- END ENV DEF ------------------------------- */

/* ---------------------------- START PATH DEF ----------------------------- */

# if defined(WANT_PATH) || defined(WANT_ALL)

# ifndef PATH_SIZE_LIMIT
#  define PATH_SIZE_LIMIT 1024
# endif /* PATH_SIZE_LIMIT */

struct path {
    char data[PATH_SIZE_LIMIT];
    u64 size;
};

struct path path_from_str(struct str str, enum err *out_err);
struct path path_from_cstr(const char *cstr, u64 cstr_size, enum err *out_err);
struct path path_from_cstr_ns(const char *cstr, enum err *out_err);

struct path path_get_xdg_state_home(enum err *out_err);
struct path path_get_xdg_data_home(enum err *out_err);

struct path path_dirname(struct path path, enum err *out_err);

struct path path_join(struct path lhs, struct path rhs, enum err *out_err);
struct path path_join_with_str(struct path lhs, struct str rhs,
                enum err *out_err);
struct path path_join_with_cstr(struct path lhs,
                const char *rhs, u64 rhs_size,
                enum err *out_err);
struct path path_join_with_cstr_ns(struct path lhs, const char *rhs,
                    enum err *out_err);

bool path_exists(struct path path, int (*access)(const char *, int));
bool path_can_read(struct path path, int (*access)(const char *, int));
bool path_can_write(struct path path, int (*access)(const char *, int));
bool path_can_execute(struct path path, int (*access)(const char *, int));

bool path_mkdir(struct path path, u32 mode, bool do_create_parents,
        int (*mkdir)(const char *, u32), enum err *out_err);

bool path_touch(struct path path, int (*open)(const char *, int),
        enum err *out_err);


struct file {
    u8 *data;
    u64 size;
};

struct file path_file_read_all(const struct path *path, enum err *out_err);
enum err path_file_save(const struct path *path, const struct file *file,
                enum err *out_err);

# endif /* defined(WANT_PATH) || defined(WANT_ALL) */

/* ----------------------------- END PATH DEF ------------------------------ */

/* ------------------------- START TOKENIZER DEF --------------------------- */

# if defined(WANT_TOKENIZER) || defined(WANT_ALL)

struct tokenizer;

struct tokenizer_options {
    bool (*skip_token)(struct tokenizer *tkn, char c);

    bool (*is_id)(struct tokenizer *tkn, char c);
    bool (*is_id_start)(struct tokenizer *tkn, char c);

    bool (*is_digit)(struct tokenizer *tkn, char c);
    bool (*is_num_lit)(struct tokenizer *tkn, struct str str);

    bool (*is_str_lit_start)(struct tokenizer *tkn, char c);
    bool (*is_str_lit_end)(struct tokenizer *tkn, char c);
    bool (*is_str_lit)(struct tokenizer *tkn, struct str str);
};

struct tokenizer {
    u64 i;

    struct tokenizer_options opts;

    struct path src;
    struct str code;

    void *edata; /* This is not used by the lib */
};

enum tokenizer_loc_format {
    TLF_VIM = 0
};

enum token_type {
    TK_INVALID = -2,
    TK_EOF = -1,
    TK_NUL = '\0',
    TK_NL = '\n',
    TK_TAB = '\t',
    TK_SPACE = ' ',
    TK_ASTERISK = '*',
    TK_AMPERSAND = '&',
    TK_PLUS = '+',
    TK_MINUS = '-',
    TK_EQUAL = '=',
    TK_SLASH = '/',
    TK_BACKSLASH = '\\',
    TK_POUND = '#',
    TK_SEMICOLON = ';',
    TK_COLON = ':',
    TK_COMMA = ',',
    TK_DOT = '.',
    TK_UNDERSCORE = '_',
    TK_L_BRACES = '(',
    TK_R_BRACES = ')',
    TK_L_BRACKET = '[',
    TK_R_BRACKET = ']',
    TK_L_CUR_BRACES = '{',
    TK_R_CUR_BRACES = '}',
    TK_L_ANG_BRACKET = '<',
    TK_R_ANG_BRACKET = '>',
    TK_SINGLE_QUOTE = '\'',
    TK_DOUBLE_QUOTE = '"',
    TK_BACKTICK = '`',
    TK_TILDE = '~',

    TK_a = 'a',
    TK_b = 'b',
    TK_c = 'c',
    TK_d = 'd',
    TK_e = 'e',
    TK_f = 'f',
    TK_g = 'g',
    TK_h = 'h',
    TK_i = 'i',
    TK_j = 'j',
    TK_k = 'k',
    TK_l = 'l',
    TK_m = 'm',
    TK_n = 'n',
    TK_o = 'o',
    TK_p = 'p',
    TK_q = 'q',
    TK_r = 'r',
    TK_s = 's',
    TK_t = 't',
    TK_u = 'u',
    TK_v = 'v',
    TK_w = 'w',
    TK_x = 'x',
    TK_y = 'y',
    TK_z = 'z',

    TK_A = 'A',
    TK_B = 'B',
    TK_C = 'C',
    TK_D = 'D',
    TK_E = 'E',
    TK_F = 'F',
    TK_G = 'G',
    TK_H = 'H',
    TK_I = 'I',
    TK_J = 'J',
    TK_K = 'K',
    TK_L = 'L',
    TK_M = 'M',
    TK_N = 'N',
    TK_O = 'O',
    TK_P = 'P',
    TK_Q = 'Q',
    TK_R = 'R',
    TK_S = 'S',
    TK_T = 'T',
    TK_U = 'U',
    TK_V = 'V',
    TK_W = 'W',
    TK_X = 'X',
    TK_Y = 'Y',
    TK_Z = 'Z',

    TK_0 = '0',
    TK_1 = '1',
    TK_2 = '2',
    TK_3 = '3',
    TK_4 = '4',
    TK_5 = '5',
    TK_6 = '6',
    TK_7 = '7',
    TK_8 = '8',
    TK_9 = '9',

    TK_ID = 257,
    TK_NUM_LIT,
    TK_STR_LIT,

    TK_LAST = 512
};

struct token {
    enum token_type type;
    u64 loc_start;
    u64 loc_end;

    u8 _[64];
};

struct token_wstr {
    enum token_type type;
    u64 loc_start;
    u64 loc_end;

    struct str string;
};


struct tokenizer tokenizer_create(struct str code, struct path src,
                                  struct tokenizer_options *opts,
                                  enum err *out_err);

struct token tokenizer_next_token(struct tokenizer *tkn, enum err *out_err);
bool tokenizer_is_next(struct tokenizer *tkn, enum token_type type,
                       struct token *out_tk, enum err *out_err);
bool tokenizer_is_next_id(struct tokenizer *tkn, struct str string,
                          struct token *out_tk, enum err *out_err);

enum err tokenizer_destroy(struct tokenizer *tkn, enum err *out_err);

const char *tokenizer_token_loc_temp(const struct tokenizer *tkn,
                                     const struct token *tk,
                                     enum tokenizer_loc_format format,
                                     enum err *out_err);

const char *token_to_cstr(enum token_type type);

struct tokenizer_options tokenizer_options_defaultlyzer(
                                           struct tokenizer_options *tkn_opts);

bool tokenizer_skip_token(struct tokenizer *tkn, char c);

bool tokenizer_is_id(struct tokenizer *tkn, char c);
bool tokenizer_is_id_start(struct tokenizer *tkn, char c);

bool tokenizer_is_digit(struct tokenizer *tkn, char c);
bool tokenizer_is_num_lit(struct tokenizer *tkn, struct str str);

bool tokenizer_is_str_lit_start(struct tokenizer *tkn, char c);
bool tokenizer_is_str_lit_end(struct tokenizer *tkn, char c);
bool tokenizer_is_str_lit(struct tokenizer *tkn, struct str str);

#  if defined(IMP) || defined(IMP_TOKENIZER)

struct tokenizer
tokenizer_create(struct str code, struct path src,
                 struct tokenizer_options *opts, enum err *out_err)
{
    struct tokenizer empty = {0};
    struct tokenizer tkn = {0};
    struct tokenizer_options defs = {0};

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, empty);

    if ( opts == NULL ) {
        tkn.opts = defs;
    } else {
        tkn.opts = *opts;
    }

    tkn.opts = tokenizer_options_defaultlyzer(&tkn.opts);

    tkn.i = 0;
    tkn.code = code;
    tkn.src = src;

    return tkn;
}

struct token
tokenizer_next_token(struct tokenizer *tkn, enum err *out_err)
{
    static struct {
        char data[256];
        u16 size;
    } buf = {0};

    struct token empty = {0};
    struct token tk = {0};
    char c = 0;

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, empty);

    LIB_ARG_MUST_NOT_BE_NULL(tkn, out_err, empty);

    buf.size = 0;

    if ( tkn->i >= tkn->code.size ) {
        tk.type = TK_EOF;
        tk.loc_start = tkn->i;
        tk.loc_end   = tkn->i;
        return tk;
    }

    c = tkn->code.data[tkn->i++];

    while ( tkn->opts.skip_token(tkn, c) == true ) {
        c = tkn->code.data[tkn->i++];
    }

    if ( tkn->opts.is_id_start(tkn, c) == true ) {
        struct token_wstr tk_ws = {0};

        tk_ws.type = TK_ID;
        tk_ws.loc_start = tkn->i;

        do {
            buf.data[buf.size++] = c;
            c = tkn->code.data[tkn->i++];
        } while ( tkn->opts.is_id(tkn, c) == true );
        --tkn->i;

        tk_ws.string.data = buf.data;
        tk_ws.string.size = buf.size;

        if ( tkn->opts.is_num_lit(tkn, tk_ws.string) == true ) {
            tk_ws.type = TK_NUM_LIT;
        }
        tk_ws.loc_end = tkn->i;

        return *(struct token *)&tk_ws;
    }

    if ( tkn->opts.is_digit(tkn, c) == true ) {
        struct token_wstr tk_ws = {0};

        tk_ws.type = TK_NUM_LIT;
        tk_ws.loc_start = tkn->i;

        do {
            buf.data[buf.size++] = c;
            c = tkn->code.data[tkn->i++];
        } while ( tkn->opts.is_digit(tkn, c) == true );
        --tkn->i;

        tk_ws.string.data = buf.data;
        tk_ws.string.size = buf.size;
        tk_ws.loc_end = tkn->i;

        return *(struct token *)&tk_ws;
    }

    if ( tkn->opts.is_str_lit_start(tkn, c) == true ) {
        struct token_wstr tk_ws = {0};

        tk_ws.type = TK_STR_LIT;
        tk_ws.loc_start = tkn->i;

        do {
            buf.data[buf.size++] = c;
            c = tkn->code.data[tkn->i++];
        } while ( tkn->opts.is_str_lit_end(tkn, c) == false );
        buf.data[buf.size++] = c;

        tk_ws.string.data = buf.data;
        tk_ws.string.size = buf.size - 1;
        tk_ws.loc_end = tkn->i;

        return *(struct token *)&tk_ws;
    }

    if ( tkn->i >= tkn->code.size ) {
        tk.type = TK_EOF;
        tk.loc_start = tkn->i;
        tk.loc_end   = tkn->i;
        return tk;
    }

    tk.type = c;
    tk.loc_start = tkn->i;
    tk.loc_end   = tkn->i;

    return tk;
}

bool
tokenizer_is_next(struct tokenizer *tkn, enum token_type type,
                  struct token *out_tk, enum err *out_err)
{
    enum err err = ERR_OK;
    struct token tk = {0};
    u64 pi = 0;

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, false);

    LIB_ARG_MUST_NOT_BE_NULL(tkn, out_err, false);

    pi = tkn->i;

    tk = tokenizer_next_token(tkn, &err);
    if ( err != ERR_OK ) {
        tkn->i = pi;
        LIB_SET_IF_NOT_NULL(out_err, err);
        return false;
    }

    if ( tk.type != type ) {
        tkn->i = pi;
        return false;
    }

    LIB_SET_IF_NOT_NULL(out_tk, tk);
    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return true;
}

bool
tokenizer_is_next_id(struct tokenizer *tkn, struct str string,
                     struct token *out_tk, enum err *out_err)
{
    enum err err = ERR_OK;
    struct token tk = {0};
    struct token_wstr *tk_ws = NULL;
    u64 pi = 0;

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, false);

    LIB_ARG_MUST_NOT_BE_NULL(tkn, out_err, false);

    LIB_STR_MUST_NOT_BE_EMPTY(string, out_err, false);

    pi = tkn->i;

    tk = tokenizer_next_token(tkn, &err);
    if ( err != ERR_OK ) {
        LIB_SET_IF_NOT_NULL(out_err, err);
        return false;
    }

    if ( tk.type != TK_ID ) {
        tkn->i = pi;
        return false;
    }

    tk_ws = (struct token_wstr *)&tk;

    if ( str_eq_str(tk_ws->string, string) == false ) {
        tkn->i = pi;
        return false;
    }

    LIB_SET_IF_NOT_NULL(out_tk, tk);
    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return true;
}

enum err tokenizer_destroy(struct tokenizer *tkn, enum err *out_err);

const char *
tokenizer_token_loc_temp(const struct tokenizer *tkn, const struct token *tk,
                         enum tokenizer_loc_format format, enum err *out_err)
{
    static struct {
        char data[1024];
        u64 size;
    } buf = {0};

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, NULL);

    LIB_ARG_MUST_NOT_BE_NULL(tkn, out_err, NULL);
    LIB_ARG_MUST_NOT_BE_NULL(tk, out_err, NULL);

    buf.size = 0;

    switch ( format ) {
    case TLF_VIM: {
        u64 col = 0;
        /* Format src.c:line:col: msg */

        memcpy(buf.data, tkn->src.data, tkn->src.size);
        buf.size += tkn->src.size;

        buf.data[buf.size++] = ':';

        {
            u64 i = 0;
            u64 line = 0;
            struct str line_str = {0};
            for ( i = 0; i < tk->loc_start; ++i ) {
                line += ( tkn->code.data[i] == '\n' );
            }
            line_str = str_from_i64_temp((i64)line);
            memcpy(buf.data + buf.size, line_str.data, line_str.size);
            buf.size += line_str.size;

            /* Col values wrong */
            while ( tkn->code.data[i] != '\n' ) {
                --i;
            }

            col = tk->loc_start - i;
        }

        buf.data[buf.size++] = ':';

        {
            struct str col_str = {0};
            col_str = str_from_i64_temp((i64)col);
            memcpy(buf.data + buf.size, col_str.data, col_str.size);
            buf.size += col_str.size;
        }

        buf.data[buf.size++] = ':';
        buf.data[buf.size++] = 0;

        } break;
    default:
        LIB_SET_IF_NOT_NULL(out_err, ERR_INVALID_ARG);
        return NULL;
    }

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return buf.data;
}

const char *
token_to_cstr(enum token_type type)
{
    switch ( type ) {
    case TK_INVALID: return "TK_INVALID";
    case TK_EOF: return "TK_EOF";
    case TK_NUL: return "TK_NUL";
    case TK_NL: return "TK_NL";
    case TK_TAB: return "TK_TAB";
    case TK_SPACE: return "TK_SPACE";
    case TK_ASTERISK: return "TK_ASTERISK";
    case TK_AMPERSAND: return "TK_AMPERSAND";
    case TK_PLUS: return "TK_PLUS";
    case TK_MINUS: return "TK_MINUS";
    case TK_EQUAL: return "TK_EQUAL";
    case TK_SLASH: return "TK_SLASH";
    case TK_BACKSLASH: return "TK_BACKSLASH";
    case TK_POUND: return "TK_POUND";
    case TK_SEMICOLON: return "TK_SEMICOLON";
    case TK_COLON: return "TK_COLON";
    case TK_COMMA: return "TK_COMMA";
    case TK_DOT: return "TK_DOT";
    case TK_UNDERSCORE: return "TK_UNDERSCORE";
    case TK_L_BRACES: return "TK_L_BRACES";
    case TK_R_BRACES: return "TK_R_BRACES";
    case TK_L_BRACKET: return "TK_L_BRACKET";
    case TK_R_BRACKET: return "TK_R_BRACKET";
    case TK_L_CUR_BRACES: return "TK_L_CUR_BRACES";
    case TK_R_CUR_BRACES: return "TK_R_CUR_BRACES";
    case TK_L_ANG_BRACKET: return "TK_L_ANG_BRACKET";
    case TK_R_ANG_BRACKET: return "TK_R_ANG_BRACKET";
    case TK_SINGLE_QUOTE: return "TK_SINGLE_QUOTE";
    case TK_DOUBLE_QUOTE: return "TK_DOUBLE_QUOTE";
    case TK_BACKTICK: return "TK_BACKTICK";
    case TK_TILDE: return "TK_TILDE";

    case TK_a: return "TK_a";
    case TK_b: return "TK_b";
    case TK_c: return "TK_c";
    case TK_d: return "TK_d";
    case TK_e: return "TK_e";
    case TK_f: return "TK_f";
    case TK_g: return "TK_g";
    case TK_h: return "TK_h";
    case TK_i: return "TK_i";
    case TK_j: return "TK_j";
    case TK_k: return "TK_k";
    case TK_l: return "TK_l";
    case TK_m: return "TK_m";
    case TK_n: return "TK_n";
    case TK_o: return "TK_o";
    case TK_p: return "TK_p";
    case TK_q: return "TK_q";
    case TK_r: return "TK_r";
    case TK_s: return "TK_s";
    case TK_t: return "TK_t";
    case TK_u: return "TK_u";
    case TK_v: return "TK_v";
    case TK_w: return "TK_w";
    case TK_x: return "TK_x";
    case TK_y: return "TK_y";
    case TK_z: return "TK_z";

    case TK_A: return "TK_A";
    case TK_B: return "TK_B";
    case TK_C: return "TK_C";
    case TK_D: return "TK_D";
    case TK_E: return "TK_E";
    case TK_F: return "TK_F";
    case TK_G: return "TK_G";
    case TK_H: return "TK_H";
    case TK_I: return "TK_I";
    case TK_J: return "TK_J";
    case TK_K: return "TK_K";
    case TK_L: return "TK_L";
    case TK_M: return "TK_M";
    case TK_N: return "TK_N";
    case TK_O: return "TK_O";
    case TK_P: return "TK_P";
    case TK_Q: return "TK_Q";
    case TK_R: return "TK_R";
    case TK_S: return "TK_S";
    case TK_T: return "TK_T";
    case TK_U: return "TK_U";
    case TK_V: return "TK_V";
    case TK_W: return "TK_W";
    case TK_X: return "TK_X";
    case TK_Y: return "TK_Y";
    case TK_Z: return "TK_Z";

    case TK_0: return "TK_0";
    case TK_1: return "TK_1";
    case TK_2: return "TK_2";
    case TK_3: return "TK_3";
    case TK_4: return "TK_4";
    case TK_5: return "TK_5";
    case TK_6: return "TK_6";
    case TK_7: return "TK_7";
    case TK_8: return "TK_8";
    case TK_9: return "TK_9";

    case TK_ID: return "TK_ID";
    case TK_NUM_LIT: return "TK_NUM_LIT";
    case TK_STR_LIT: return "TK_STR_LIT";
    default: return "TK_UNKNOWN";
    }
    return "TK_UNKNOWN";
}

struct tokenizer_options
tokenizer_options_defaultlyzer(struct tokenizer_options *tkn_opts)
{
    struct tokenizer_options defs = {0};

    if ( tkn_opts != NULL ) {
        defs = *tkn_opts;
    }

    LIB_SET_IF_NULL(defs.skip_token, tokenizer_skip_token);

    LIB_SET_IF_NULL(defs.is_id, tokenizer_is_id);
    LIB_SET_IF_NULL(defs.is_id_start, tokenizer_is_id_start);

    LIB_SET_IF_NULL(defs.is_digit, tokenizer_is_digit);
    LIB_SET_IF_NULL(defs.is_num_lit, tokenizer_is_num_lit);

    LIB_SET_IF_NULL(defs.is_str_lit_start, tokenizer_is_str_lit_start);
    LIB_SET_IF_NULL(defs.is_str_lit_end, tokenizer_is_str_lit_end);
    LIB_SET_IF_NULL(defs.is_str_lit, tokenizer_is_str_lit);

    LIB_SET_IF_NULL(defs.skip_token, tokenizer_skip_token);

    return defs;
}

bool
tokenizer_skip_token(struct tokenizer *tkn, char c)
{
    UNUSED(tkn);
    return (c == ' ') || (c == '\n') || (c == '\r') || (c == '\t');
}

bool
tokenizer_is_id(struct tokenizer *tkn, char c)
{
    UNUSED(tkn);
    return (c == '_') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}

bool
tokenizer_is_id_start(struct tokenizer *tkn, char c)
{
    UNUSED(tkn);
    return tokenizer_is_id(tkn, c);
}

bool
tokenizer_is_digit(struct tokenizer *tkn, char c)
{
    UNUSED(tkn);
    return (c >= '0' && c <= '9');
}

bool
tokenizer_is_num_lit(struct tokenizer *tkn, struct str str)
{
    u64 i = 0;

    UNUSED(tkn);

    for ( i = 0; i < str.size; ++i ) {
        if ( ! tokenizer_is_digit(tkn, str.data[i]) ) {
            return false;
        }
    }
    return true;
}

bool
tokenizer_is_str_lit_start(struct tokenizer *tkn, char c)
{
    UNUSED(tkn);
    return (c == '\'' || c == '"');
}

bool
tokenizer_is_str_lit_end(struct tokenizer *tkn, char c)
{
    return tokenizer_is_str_lit_start(tkn, c);
}

bool
tokenizer_is_str_lit(struct tokenizer *tkn, struct str str)
{
    UNUSED(tkn);
    UNUSED(str);
    return true;
}

#  endif /* defined(IMP) || defined(IMP_TOKENIZER) */

# endif /* defined(WANT_TOKENIZER) || defined(WANT_ALL) */

/* -------------------------- END TOKENIZER DEF ---------------------------- */

/* ---------------------------- START SGFX DEF ----------------------------- */

# if defined(WANT_SGFX) || defined(WANT_ALL)

# ifndef SGFX_WIDTH
#  define SGFX_WIDTH 800
# endif /* SGFX_WIDTH */

# ifndef SGFX_HEIGHT
#  define SGFX_HEIGHT 600
# endif /* SGFX_HEIGHT */

# define SGFX_WIDTH_CSTR MACRO_STR_VALUE_X(SGFX_WIDTH)
# define SGFX_HEIGHT_CSTR MACRO_STR_VALUE_X(SGFX_HEIGHT)

struct sgfx_rgb {
    u8 r, g, b;
};

struct sgfx_rgba {
    u8 r, g, b, a;
};

struct sgfx_canvas {
    u32 pixels[SGFX_WIDTH * SGFX_HEIGHT];
    u64 width;
    u64 height;
    u64 cap;
};

enum err sgfx_canvas_populate(struct sgfx_canvas *canvas, enum err *out_err);

enum err sgfx_canvas_fill(struct sgfx_canvas *canvas, u32 color,
                enum err *out_err);

enum err sgfx_canvas_fill_rect(struct sgfx_canvas *canvas,
                struct u64vec2 pos, struct u64vec2s size,
                u32 color,
                enum err *out_err);

enum err sgfx_canvas_save_to_ppm(const struct sgfx_canvas *canvas,
                    struct path path, enum err *out_err);

# endif /* defined(WANT_SGFX) || defined(WANT_ALL) */

/* ----------------------------- END SGFX DEF ------------------------------ */

/* --------------------------- START RAYLIB DEF ---------------------------- */

# if defined(WANT_RAYLIB) || defined(WANT_ALL)

#  if defined(RAYLIB_H)
enum err raylib_draw_str(const struct str_builder *str, Font font,
                Vector2 position, float fontSize,
                float spacing, Color tint, enum err *out_err);
#  endif /* defined(RAYLIB_H) */

# endif /* defined(WANT_RAYLIB) || defined(WANT_ALL) */

/* ---------------------------- END RAYLIB DEF ----------------------------- */

/* ---------------------------- START GUI DEF ------------------------------ */

#  if defined(WANT_GUI) || defined(WANT_ALL)

enum gui_unit_type {
    GUT_ABSOLUTE = 0,
    GUT_PERCENTAGE
};

enum gui_unit_reference {
    GUR_TOP_LEFT = 0,
    GUR_CENTER
};

struct gui_unit {
    enum gui_unit_type type;
    enum gui_unit_reference ref;
    i64 value;
};

enum gui_item_type {
    GIT_BUTTOM = 0
};

struct gui_item {
    enum gui_item_type type;
    struct gui_unit x;
    struct gui_unit y;
    struct gui_unit width;
    struct gui_unit height;
};

struct gui {
    struct {
        struct gui_item *data;
        u64 size;
        u64 cap;
    } items;
};

struct gui gui_open_window(const char *title, enum err *out_err);
bool gui_should_close(struct gui *gui, enum err *out_err);

enum err gui_start_loop(struct gui *gui, enum err *out_err);

enum err gui_add_buttom(struct gui *gui,
            struct gui_unit x, struct gui_unit y,
            struct gui_unit width, struct gui_unit height,
            enum err *out_err);

enum err gui_end_loop(struct gui *gui, enum err *out_err);

enum err gui_close_window(struct gui *gui, enum err *out_err);

# if defined(IMP) || defined(IMP_STR)

struct gui
gui_open_window(const char *title, enum err *out_err)
{
    struct gui empty = {0};

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, empty);
    LIB_ARG_MUST_NOT_BE_NULL(title, out_err, empty);

#   if defined(RAYLIB_H)
    InitWindow(800, 600, title);
    SetWindowState(FLAG_WINDOW_RESIZABLE);
#   endif /* defined(RAYLIB_H) */

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return empty;
}

bool
gui_should_close(struct gui *gui, enum err *out_err)
{
    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, true);
    LIB_ARG_MUST_NOT_BE_NULL(gui, out_err, true);

    (void) gui;
#   if defined(RAYLIB_H)
    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return WindowShouldClose();
#   endif /* defined(RAYLIB_H) */

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return true;
}

enum err
gui_start_loop(struct gui *gui, enum err *out_err)
{
    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(gui, out_err);
#   if defined(RAYLIB_H)
    BeginDrawing();
#   endif /* defined(RAYLIB_H) */
    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}

enum err
gui_add_buttom(struct gui *gui,
        struct gui_unit x, struct gui_unit y,
        struct gui_unit width, struct gui_unit height,
        enum err *out_err)
{
#   if defined(RAYLIB_H)
    Color red = { 230, 41, 55, 255 };
#   endif /* defined(RAYLIB_H) */

    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(gui, out_err);

#   if defined(RAYLIB_H)
    DrawRectangle((i32)x.value, (i32)y.value,
            (i32)width.value, (i32)height.value, red);
#   endif /* defined(RAYLIB_H) */

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}

enum err
gui_end_loop(struct gui *gui, enum err *out_err)
{
    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(gui, out_err);

#   if defined(RAYLIB_H)
    EndDrawing();
#   endif /* defined(RAYLIB_H) */

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}

enum err
gui_close_window(struct gui *gui, enum err *out_err)
{
    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(gui, out_err);

#   if defined(RAYLIB_H)
    CloseWindow();
#   endif /* defined(RAYLIB_H) */

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}
# endif /* defined(IMP) || defined(IMP_STR) */

#  endif /* defined(WANT_GUI) || defined(WANT_ALL) */

/* ----------------------------- END GUI DEF ------------------------------- */

# if defined(IMP) || defined(IMP_STR)

#  define _SET_IF_NOT_NULL(var, err) \
    if ( var != NULL ) { \
        *var = err; \
    }

#  define _ARG_IF_NOT_NULL_MUST_BE(arg, val, ret_val) \
    if ( arg != NULL ) { \
        if ( *arg != val ) { \
            return ret_val; \
        } \
    }

#  define _ARG_IF_NOT_NULL_MUST_BE_RET_IT(arg, val) \
    if ( arg != NULL ) { \
        if ( *arg != val ) { \
            return *arg; \
        } \
    }

#  define _ARG_MUST_NOT_BE_NULL_RET_ERR(arg) \
    if ( (arg) == NULL ) { \
        return ERR_NULL_ARG; \
    }

#  define _ARG_MUST_NOT_BE_NULL(arg, err_var, ret_val) \
    if ( (arg) == NULL ) { \
        _SET_IF_NOT_NULL(err_var, ERR_NULL_ARG); \
        return ret_val; \
    }

#  define _ARG_MUST_NOT_BE_NULL_SET_RET_ERR(arg, err_var) \
    if ( (arg) == NULL ) { \
        _SET_IF_NOT_NULL(err_var, ERR_NULL_ARG); \
        return ERR_NULL_ARG; \
    }

#  ifndef _CTYPE_H
#   define LIB_H_CTYPE

#   define isspace(c) ((c) == ' ' || (c) == '\f' \
            || (c) == '\n' || (c) == '\r' \
            || (c) == '\t' || (c) == '\v')

#   define isdigit(c) ((c) >= '0' && (c) <= '9')

#  endif /* _CTYPE_H */


#  ifndef _STRING_H
#   define LIB_H_STRING

void *_str_memcpy(void *dest, const void *src, u64 n);
void *
_str_memcpy(void *dest, const void *src, u64 n)
{
    u64 i = 0;
    char *_dest = (char *) dest;
    char *_src = (char *) src;

    for ( i = 0; i < n; ++i ) {
        _dest[i] = _src[i];
    }

    return dest;
}

#   define memcpy _str_memcpy
#  endif /* _STRING_H */

/* ----------------------------- START LIB IMP ----------------------------- */

/* ------------------------------ END LIB IMP ------------------------------ */

/* ---------------------------- START CSTR IMP ----------------------------- */

#  if defined(WANT_CSTR) || defined(WANT_ALL)

u64
cstr_len_max(const char *cstr, u64 max,
        bool *out_hit_max, enum err *out_err)
{
    u64 ret = 0;

    _ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, 0);

    if ( cstr == NULL ) {
        _SET_IF_NOT_NULL(out_err, ERR_NULL_ARG);
        return 0;
    }

_loop:
    if ( ret >= max ) {
        _SET_IF_NOT_NULL(out_hit_max, true);
        goto _loop_end;
    }
    if ( cstr[ret] == '\0' ) {
        goto _loop_end;
    }

    ++ret;
    goto _loop;
_loop_end:

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return ret;
}

#  endif /* defined(WANT_CSTR) || defined(WANT_ALL) */

/* ----------------------------- END CSTR IMP ------------------------------ */

/* ---------------------------- START STR DEF ------------------------------ */

#  if defined(WANT_STR) || defined(WANT_ALL)

struct str
str_from_cstr(const char *cstr, u64 cstr_size, enum err *out_err)
{
    struct str empty = {0};
    struct str str = {0};

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, empty);

    if ( cstr == NULL ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_NULL_ARG);
        return empty;
    }

    if ( cstr_size >= STR_SIZE_LIMIT ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_TOO_BIG);
        return empty;
    }

    str.data = cstr;
    str.size = cstr_size;

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return str;
}

struct str
str_from_cstr_ns(const char *cstr, enum err *out_err)
{
    struct str empty = {0};
    struct str str = {0};
    bool too_big = false;
    enum err err = ERR_OK;

    if ( cstr == NULL ) {
        _SET_IF_NOT_NULL(out_err, ERR_NULL_ARG);
        return empty;
    }

    str.size = cstr_len_max(cstr, STR_SIZE_LIMIT, &too_big, &err);
    if ( err ) {
        _SET_IF_NOT_NULL(out_err, err);
        return empty;
    }

    if ( too_big ) {
        _SET_IF_NOT_NULL(out_err, ERR_TOO_BIG);
        return empty;
    }

    str.data = cstr;

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return str;
}

struct str
str_from_i64(i64 num, enum err *out_err)
{
    struct str empty = {0};
    struct str str_temp = {0};
    struct str ret = {0};

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, empty);

    str_temp = str_from_i64_temp(num);

    ret.size = str_temp.size;
    ret.should_be_freed = true;

    ret.data = malloc(str_temp.size);
    if ( ret.data == NULL ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_ALLOC);
        return empty;
    }

    memcpy((void *)ret.data, str_temp.data, str_temp.size);

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ret;
}

struct str
str_from_i64_temp(i64 num)
{
    static char buffer[32];

    struct {
        char data[24];
        u8 size;
    } buf = {0};
    u64 _num = 0;
    struct str ret = {0};

    ret.data = buffer;

    if ( num < 0 ) {
        buf.data[buf.size++] = '-';
        num *= -1;
    }

    _num = (u64)num;

    while ( 1 ) {
        u8 mod = (u8) (_num % 10);

        buf.data[buf.size++] = (char) (mod + 48);

        _num /= 10;

        if ( buf.size >= 24 ) {
            _loop:
            buffer[ret.size++] = buf.data[--buf.size];
            if ( buf.size > 0 ) {
                goto _loop;
            }
        }

        if ( _num == 0 ) {
            break;
        }
    }
    if ( buf.size == 0 ) {
        goto exit;
    }

    _out_loop:
    buffer[ret.size++] = buf.data[--buf.size];
    if ( buf.size > 0 ) {
        goto _out_loop;
    }

    exit:
    buffer[ret.size] = 0;
    return ret;
}


struct str
str_dup(struct str str)
{
    struct str ret = str;

    ret.data = malloc(ret.size * sizeof(*ret.data));

    memcpy((char *)ret.data, str.data, str.size);

    ret.should_be_freed = true;
    return ret;
}

intmax_t
str_to_int(struct str str, enum err *out_err)
{
    intmax_t res = 0;
    u64 i = 0;
    bool negative = false;
    int digits[] = {
        1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000
    };

    if ( ! str_is_int(str) ) {
        _SET_IF_NOT_NULL(out_err, ERR_NOT_INT);
        return -1;
    }

    if ( str.size > STR_SIZE_LIMIT ) {
        _SET_IF_NOT_NULL(out_err, ERR_TOO_BIG);
        return -1;
    }

    negative = (str.data[0] == '-');
    if ( str.data[0] == '-' || str.data[0] == '+' ) {
        ++str.data;
        --str.size;
    }

    for ( i = 0; i < str.size; ++i ) {
        intmax_t digit = digits[(str.size - (i + 1))];
        res += (str.data[i] - 48) * digit;
    }

    if ( negative == true ) {
        res *= -1;
    }

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return res;
}

struct str
str_rstrip(struct str str)
{
    while ( isspace(str.data[str.size-1]) ) {
        --str.size;
    }
    return str;
}

struct str
str_lstrip(struct str str)
{
    while ( isspace(*str.data) ) {
        ++str.data;
        --str.size;
    }
    return str;
}

struct str
str_strip(struct str str)
{
    return str_lstrip(str_rstrip(str));
}

u64
str_lindex(struct str str, char c)
{
    u64 i = 0;
    for ( ; i < str.size; ++i ) {
        if ( str.data[i] == c ) {
            return i;
        }
    }
    return (u64) -1;
}

u64
str_rindex(struct str str, char c)
{
    u64 i = str.size - 1;
    for ( ; i > 0; --i ) {
        if ( str.data[i] == c ) {
            return i;
        }
    }
    if ( str.data[i] == c ) {
        return i;
    }
    return (u64) -1;
}

struct str_tokenizer
str_tokenize(struct str str, char c)
{
    struct str_tokenizer st = {0};
    st.str = str;
    st.c = c;
    return st;
}

struct str
str_tokenizer_next(struct str_tokenizer *st)
{
    struct str str;

    if ( st == NULL ) {
        goto ret_err;
    }

    if ( st->cur >= st->str.size ) {
        goto ret_done;
    }

    if ( st->str.data[st->cur] == '\0' ) {
        goto ret_done;
    }

    str = st->str;
    str.data += st->cur;
    str.size = 0;

    while ( str.data[str.size] != st->c
        && st->cur < st->str.size ) {
        ++str.size;
        ++st->cur;
    }
    ++st->cur;

    if ( str.size == 0 ) {
        goto ret_empty;
    }

    return str;

ret_empty:
    str.data = "";
    str.size = 0;
    return str;

ret_done:
    str.data = "";
    str.size = (u64) -1;
    return str;

ret_err:
    str.data = "";
    str.size = (u64) -2;
    return str;
}

struct str
str_slice(struct str str, u64 from, u64 to)
{
    if ( from > str.size ) {
        goto ret_err;
    }
    if ( from > to ) {
        goto ret_err;
    }

    to = ( to > str.size ) * str.size \
         + ( to <= str.size ) * to;
    
    str.data += from;
    str.size = to - from;

    return str;

ret_err:
    str.data = "";
    str.size = (u64) -2;
    return str;
}

bool
str_eq_cstr(struct str str, const char *cstr, u64 cstr_size)
{
    u64 i = 0;
    if ( str.size != cstr_size ) {
        return false;
    }

    for ( i = 0; i < str.size; ++i ) {
        if ( str.data[i] != cstr[i] ) {
            return false;
        }
    }

    return true;
}

bool
str_eq_str(struct str str1, struct str str2)
{
    u64 i = 0;

    if ( str1.size != str2.size ) {
        return false;
    }

    for ( i = 0; i < str1.size; ++i ) {
        if ( str1.data[i] != str2.data[i] ) {
            return false;
        }
    }

    return true;
}

bool
str_startswith_cstr(struct str str, const char *cstr, u64 cstr_size)
{
    u64 i = 0;

    if ( cstr_size > str.size ) {
        return false;
    }

    for ( i = 0; i < cstr_size; ++i ) {
        if ( str.data[i] != cstr[i] ) {
            return false;
        }
    }

    return true;
}

bool
str_is_int(struct str str)
{
    u64 i = 0;

    if ( str.size == 0 ) {
        return false;
    }

    if ( str.size == 1 ) {
        return isdigit(str.data[0]);
    }

    i += (str.data[0] == '-' || str.data[0] == '+');

    for ( ; i < str.size; ++i ) {
        if ( ! isdigit(str.data[i]) ) {
            return false;
        }
    }

    return true;
}

struct str
str_builder_to_str(const struct str_builder *str_bldr)
{
    struct str str = {0};

    str.data = str_bldr->data;
    str.size = str_bldr->size;
    str.should_be_freed = true;

    return str;
}

#  endif /* defined(WANT_STR) || defined(WANT_ALL) */

/* ----------------------------- END STR IMP ------------------------------- */

/* ---------------------------- START ENV IMP ------------------------------ */

#  if defined(WANT_ENV) || defined(WANT_ALL)

struct str
getenv_as_str(const char *name, enum err *out_err)
{
    struct str empty = {0};
    struct str str = {0};
    char *res = NULL;

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, empty);

    LIB_ARG_MUST_NOT_BE_NULL(name, out_err, empty);

    res = getenv(name);
    if ( res == NULL ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_NOT_FOUND);
        return str;
    }

    return str_from_cstr_ns(res, out_err);
}

struct path
getenv_as_path(const char *name, enum err *out_err)
{
    struct path empty = {0};
    struct str str = {0};
    enum err err;

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, empty);

    LIB_ARG_MUST_NOT_BE_NULL(name, out_err, empty);

    str = getenv_as_str(name, &err);
    if ( err ) {
        LIB_SET_IF_NOT_NULL(out_err, err);
        return empty;
    }

    return path_from_str(str, out_err);
}

#  endif /* defined(WANT_ENV) || defined(WANT_ALL) */

/* ----------------------------- END ENV IMP ------------------------------- */

/* ---------------------------- START PATH IMP ----------------------------- */

#  if defined(WANT_PATH) || defined(WANT_ALL)

struct path
path_from_str(struct str str, enum err *out_err)
{
    struct path path = {0};

    if ( str.size > PATH_SIZE_LIMIT ) {
        _SET_IF_NOT_NULL(out_err, ERR_TOO_BIG);
        return path;
    }

    memcpy(path.data, str.data, str.size);

    path.size = str.size;

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return path;
}

struct path
path_from_cstr(const char *cstr, u64 cstr_size, enum err *out_err)
{
    enum err err;
    struct str str;
    struct path path = {0};

    str = str_from_cstr(cstr, cstr_size, &err);
    if ( err ) {
        _SET_IF_NOT_NULL(out_err, err);
        return path;
    }

    return path_from_str(str, out_err);
}

struct path
path_from_cstr_ns(const char *cstr, enum err *out_err)
{
    enum err err;
    struct str str;
    struct path path = {0};

    _ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, path);

    str = str_from_cstr_ns(cstr, &err);
    if ( err ) {
        _SET_IF_NOT_NULL(out_err, err);
        return path;
    }

    return path_from_str(str, out_err);
}

struct path
path_get_xdg_state_home(enum err *out_err)
{
    struct path empty = {0};
    struct path path = {0};
    enum err err;

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, empty);

    path = getenv_as_path("XDG_STATE_HOME", &err);
    if ( err == ERR_NOT_FOUND ) {
        path = getenv_as_path("HOME", &err);
        if ( err ) {
            _SET_IF_NOT_NULL(out_err, err);
            return empty;
        }
        path = path_join_with_cstr(path, ".local/state", 12, NULL);
    } else if ( err ) {
        _SET_IF_NOT_NULL(out_err, err);
        return empty;
    }

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return path;
}

struct path
path_get_xdg_data_home(enum err *out_err)
{
    struct path empty = {0};
    struct path path = {0};
    enum err err;

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, empty);

    path = getenv_as_path("XDG_DATA_HOME", &err);
    if ( err == ERR_NOT_FOUND ) {
        path = getenv_as_path("HOME", &err);
        if ( err ) {
            _SET_IF_NOT_NULL(out_err, err);
            return empty;
        }
        path = path_join_with_cstr(path, ".local/share", 12, NULL);
    } else if ( err ) {
        _SET_IF_NOT_NULL(out_err, err);
        return empty;
    }

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return path;
}

struct path
path_dirname(struct path path, enum err *out_err)
{
    struct path empty = {0};
    struct path ret = {0};
    u64 i = 0;

    if ( path.size == 0 || path.data[0] == 0 ) {
        _SET_IF_NOT_NULL(out_err, ERR_PATH_EMPTY);
        return empty;
    }

    i = path.size;
loop:
    --i;

    if ( path.data[i] == '/' ) {
        goto exit_loop;
    }

    if ( i == 0 ) {
        ret.data[0] = '.';
        ret.data[1] = 0;
        ret.size = 1;

        goto ret_ok;
    }
    goto loop;
exit_loop:

    memcpy(ret.data, path.data, i);
    ret.size = i;

ret_ok:
    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return ret;
}

struct path
path_join(struct path lhs, struct path rhs, enum err *out_err)
{
    struct path path = {0};
    if ( (lhs.size + rhs.size + 1) > PATH_SIZE_LIMIT ) {
        _SET_IF_NOT_NULL(out_err, ERR_TOO_BIG);
        return path;
    }

    /* TODO: Check if lhs or rhs have / at the end and start respectively */

    path.size = (lhs.size + rhs.size + 1);

    memcpy(path.data, lhs.data, lhs.size);
    path.data[lhs.size] = '/';
    memcpy(path.data + lhs.size + 1, rhs.data, rhs.size);

    return path;
}

struct path
path_join_with_str(struct path lhs, struct str rhs, enum err *out_err)
{
    struct path path = {0};
    enum err err;

    path = path_from_str(rhs, &err);
    if ( err ) {
        _SET_IF_NOT_NULL(out_err, err);
        return path;
    }

    return path_join(lhs, path, out_err);
}

struct path
path_join_with_cstr(struct path lhs, const char *rhs, u64 rhs_size,
            enum err *out_err)
{
    struct path path = {0};
    enum err err;

    path = path_from_cstr(rhs, rhs_size, &err);
    if ( err ) {
        _SET_IF_NOT_NULL(out_err, err);
        return path;
    }

    return path_join(lhs, path, out_err);
}

struct path
path_join_with_cstr_ns(struct path lhs, const char *rhs, enum err *out_err)
{
    struct path empty = {0};
    struct path path = {0};
    enum err err;

    _ARG_MUST_NOT_BE_NULL(rhs, out_err, empty);

    path = path_from_cstr_ns(rhs, &err);
    if ( err ) {
        _SET_IF_NOT_NULL(out_err, err);
        return empty;
    }

    return path_join(lhs, path, out_err);
}

bool
path_exists(struct path path, int (*access)(const char *, int))
{
    return access(path.data, F_OK) == 0;
}

bool
path_can_read(struct path path, int (*access)(const char *, int))
{
    return access(path.data, R_OK) == 0;
}

bool
path_can_write(struct path path, int (*access)(const char *, int))
{
    return access(path.data, W_OK) == 0;
}

bool
path_can_execute(struct path path, int (*access)(const char *, int))
{
    return access(path.data, X_OK) == 0;
}

bool
path_mkdir(struct path path, u32 mode, bool do_create_parents,
        int (*mkdir)(const char *, u32), enum err *out_err)
{
    u64 i = 0;

    _ARG_MUST_NOT_BE_NULL(mkdir, out_err, false);

    if ( path.size == 1 ) {
        _SET_IF_NOT_NULL(out_err, ERR_PATH_INVALID);
        return 1;
    }

    if ( do_create_parents ) {
        for ( i = 1; i < path.size; ++i ) {
            if ( path.data[i] == '/' ) {
                path.data[i] = 0;
                mkdir(path.data, mode);
                path.data[i] = '/';
            }
        }
    }

    if ( ! mkdir(path.data, mode) ) {
        _SET_IF_NOT_NULL(out_err, ERR_MKDIR_FAILED);
        return false;
    }

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return true;
}

/*
bool
path_touch(struct path path, int (*open)(const char *, int), enum err *out_err)
{
    int fd = -1;

    _ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, false);

    _ARG_MUST_NOT_BE_NULL(open, out_err, false);

    fd = open(path.data, 0100 | 00644);
    if ( fd == -1 ) {
        _SET_IF_NOT_NULL(out_err, ERR_PATH_FAILED_OPEN);
        return false;
    }

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return true;
}
*/

/* TODO: Replace this */
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>

struct file
path_file_read_all(const struct path *path, enum err *out_err)
{
    struct file empty = {0};
    struct file file = {0};
    i64 file_size = -1;
    i32 fd = -1;

    _ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, empty);
    _ARG_MUST_NOT_BE_NULL(path, out_err, empty);

    fd = open(path->data, O_RDONLY);
    if ( fd < 0 ) {
        _SET_IF_NOT_NULL(out_err, ERR_FAILED_OPEN);
        goto exit_err;
    }

    file_size = lseek(fd, 0, SEEK_END);
    if ( file_size < 0 ) {
        _SET_IF_NOT_NULL(out_err, ERR_PATH_FILE_FAILED_SEEK);
        goto exit_err;
    }
    lseek(fd, 0, SEEK_SET);
    file.size = (u64) file_size;

    if ( file.size == 0 ) {
        _SET_IF_NOT_NULL(out_err, ERR_PATH_FILE_EMPTY);
        goto exit_err;
    }

    file.data = calloc(file.size + 1, sizeof(u8));
    if ( file.data == NULL ) {
        _SET_IF_NOT_NULL(out_err, ERR_FAILED_ALLOC);
        goto exit_err;
    }

    {
        i64 rd = read(fd, file.data, file.size);
        if ( rd < 0 ) {
            _SET_IF_NOT_NULL(out_err, ERR_FAILED_READ);
            goto exit_err;
        }
        if ( rd == 0 ) {
            _SET_IF_NOT_NULL(out_err, ERR_PATH_FILE_EMPTY);
            goto exit_err;
        }
    }

    if ( close(fd) != 0 ) {
        /* It should be possible to handle EIO */
        _SET_IF_NOT_NULL(out_err, ERR_FAILED_CLOSE);
        return empty;
    }

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return file;
exit_err:
    if ( file.data != NULL ) {
        free(file.data);
    }
    if ( fd > 0 ) {
        if ( close(fd) != 0 ) {
            /* It should be possible to handle EIO */
            _SET_IF_NOT_NULL(out_err, ERR_FAILED_CLOSE);
            return empty;
        }
    }
    return empty;
}

enum err
path_file_save(const struct path *path, const struct file *file,
        enum err *out_err)
{
    enum err err = ERR_OK;
    i64 wrote = -1;
    i32 fd = -1;

    _ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    _ARG_MUST_NOT_BE_NULL_SET_RET_ERR(path, out_err);
    _ARG_MUST_NOT_BE_NULL_SET_RET_ERR(file, out_err);

    fd = open(path->data, O_WRONLY | O_CREAT | O_TRUNC,
            S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    if ( fd < 0 ) {
        err = ERR_FAILED_OPEN;
        goto exit;
    }

    wrote = write(fd, file->data, file->size);
    if ( wrote == -1 ) {
        err = ERR_FAILED_WRITE;
        goto exit;
    }

    if ( ((u64) wrote) != file->size ) {
        err = ERR_WROTE_WRONG_AMOUNT;
        goto exit;
    }

    err = ERR_OK;

exit:
    if ( close(fd) != 0 ) {
        /* It should be possible to handle EIO */
        err = ERR_FAILED_CLOSE;
        goto exit;
    }

    _SET_IF_NOT_NULL(out_err, err);
    return err;
}

#  endif /* defined(WANT_PATH) || defined(WANT_ALL) */

/* ----------------------------- END PATH IMP ------------------------------ */

/* ---------------------------- START SGFX IMP ----------------------------- */

#  if defined(WANT_SGFX) || defined(WANT_ALL)

enum err
sgfx_canvas_populate(struct sgfx_canvas *canvas, enum err *out_err)
{
    _ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    _ARG_MUST_NOT_BE_NULL_SET_RET_ERR(canvas, out_err);

    canvas->width  = SGFX_WIDTH;
    canvas->height = SGFX_HEIGHT;
    canvas->cap = SGFX_WIDTH * SGFX_HEIGHT;

    _SET_IF_NOT_NULL(out_err, ERR_OK)
    return ERR_OK;
}

enum err
sgfx_canvas_fill(struct sgfx_canvas *canvas, u32 color, enum err *out_err)
{
    u64 i = 0;

    _ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    _ARG_MUST_NOT_BE_NULL_SET_RET_ERR(canvas, out_err);

    for ( i = 0; i < canvas->cap; ++i ) {
        canvas->pixels[i] = color;
    }

    _SET_IF_NOT_NULL(out_err, ERR_OK)
    return ERR_OK;
}

enum err
sgfx_canvas_fill_rect(struct sgfx_canvas *canvas,
            struct u64vec2 pos, struct u64vec2s size, u32 color,
            enum err *out_err)
{
    u64 i = 0;
    u64 j = 0;
    u64 startp = 0;
    u64 endp = 0;

    _ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    _ARG_MUST_NOT_BE_NULL_SET_RET_ERR(canvas, out_err);

    /* TODO: Create a diferent error for when size lands outside canvas */
    if ( pos.x > canvas->width || pos.y > canvas->height
         || (pos.x + size.w) > canvas->width
         || (pos.y + size.h) > canvas->height ) {
        _SET_IF_NOT_NULL(out_err, ERR_SGFX_POS_OUTSIDE_CANVAS);
        return ERR_SGFX_POS_OUTSIDE_CANVAS;
    }

    /*
    printf("pos.x -> %ld || pos.y -> %ld\n", pos.x, pos.y);
    printf("size.w -> %ld || size.h -> %ld\n", size.w, size.h);
    printf("(pos.x + size.w) -> %ld || (pos.y + size.h) -> %ld\n", (pos.x + size.w), (pos.y + size.h));
    */

    startp = (canvas->width * pos.y) + pos.x;
    endp = (canvas->width * (pos.y + size.h)) + (pos.x + size.w);
    if ( endp >= canvas->cap ) {
        endp = canvas->cap;
    }
    for ( i = startp; i < endp; i += canvas->width ) {
        for ( j = i; j < i+size.w; ++j ) {
            canvas->pixels[j] = color;
        }
    }

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}

enum err
sgfx_canvas_save_to_ppm(const struct sgfx_canvas *canvas, struct path path,
            enum err *out_err)
{
#define __T(s) s, cstr_len_max(s, 255, NULL, out_err)
    /* enum err err = ERR_OK; */
    /* u64 i = 0; */
    /* i32 fd = -1; */
    (void) path;

    _ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);

    _ARG_MUST_NOT_BE_NULL_SET_RET_ERR(canvas, out_err);

    /* TODO: Check the errors */
    /*
    fd = open(path.data, O_RDWR | O_CREAT, 00644);

    if ( err ) {
        _SET_IF_NOT_NULL(out_err, err);
        return err;
    }

    write(fd, __T("P6\n"));

    write(fd, __T(SGFX_WIDTH_CSTR"\n"));
    write(fd, __T(SGFX_HEIGHT_CSTR"\n"));

    write(fd, __T("255\n"));

    for ( i = 0; i < canvas->cap; ++i ) {
        struct sgfx_rgb rgb = {0};
        rgb.r = (u8)(canvas->pixels[i] >> 24);
        rgb.g = (u8)(canvas->pixels[i] >> 16);
        rgb.b = (u8)(canvas->pixels[i] >> 8);
        write(fd, &rgb, sizeof(struct sgfx_rgb));
    }

    close(fd);
    */

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
#undef __T
}

#  endif /* defined(WANT_SGFX) || defined(WANT_ALL) */

/* ----------------------------- END SGFX IMP ------------------------------ */

/* --------------------------- START RAYLIB IMP ---------------------------- */

#  if defined(WANT_RAYLIB) || defined(WANT_ALL)

#   if defined(RAYLIB_H)
enum err
raylib_draw_str(const struct str_builder *str, Font font, Vector2 position,
        float fontSize, float spacing, Color tint, enum err *out_err)
{
    enum err err = ERR_OK;
    char *cstr = NULL;

    _ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);

    cstr = calloc(str->size + 1, sizeof(char));
    if ( cstr == NULL ) {
        err = ERR_FAILED_ALLOC;
        goto exit;
    }

    memcpy(cstr, str->data, str->size);

    DrawTextEx(font, cstr, position, fontSize, spacing, tint);

    err = ERR_OK;

exit:
    if ( cstr != NULL ) {
        free(cstr);
    }
    _SET_IF_NOT_NULL(out_err, err);
    return err;
}
#   endif /* defined(RAYLIB_H) */

#  endif /* defined(WANT_RAYLIB) || defined(WANT_ALL) */

/* ---------------------------- END RAYLIB IMP ----------------------------- */


#  ifdef LIB_H_CTYPE
#   undef LIB_H_CTYPE

#   undef isspace
#   undef isdigit
#  endif /* LIB_H_CTYPE */

#  ifdef LIB_H_STRING
#   undef LIB_H_STRING

#   undef memcpy
#  endif /* LIB_H_STRING */

#  undef _SET_IF_NOT_NULL
#  undef _ARG_IF_NOT_NULL_MUST_BE
#  undef _ARG_MUST_NOT_BE_NULL

# endif /* defined(IMP) || defined(IMP_STR) */

# ifdef LIB_H_ALLOC_FUNC
#  undef LIB_H_ALLOC_FUNC

#  undef ALLOC_FUNC
# endif /* LIB_H_ALLOC_FUNC */

# ifdef LIB_H_STDBOOL
#  undef LIB_H_STDBOOL

#  undef bool
#  undef true
#  undef false
# endif /* LIB_H_STDBOOL */

# ifdef LIB_H_NULL
#  undef LIB_H_NULL

#  undef NULL
# endif /* LIB_H_NULL */

# ifdef LIB_H_UNISTD
#  undef LIB_H_UNISTD

#  undef R_OK
#  undef W_OK
#  undef X_OK
#  undef F_OK
# endif /* LIB_H_UNISTD */

# ifdef LIB_H_STDINT
#  undef LIB_H_STDINT

#  undef intmax_t
# endif /* LIB_H_STDINT */

#endif /* LIB_H */
