/* vim: set filetype=c : */
#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_GENERAL_ERROR = -1,
    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_FAILED_FORK,
    ERR_FAILED_WAITPID,
    ERR_FAILED_STAT,
    ERR_FAILED_OPENDIR,
    ERR_FAILED_READDIR,
    ERR_WROTE_WRONG_AMOUNT,
    ERR_FAILED_ALLOC,
    ERR_FAILED_REALLOC,
    ERR_MKDIR_FAILED,
    ERR_FAILED_HASHING,
    ERR_STR_EMPTY,
    ERR_PATH_EMPTY,
    ERR_PATH_INVALID,
    ERR_PATH_FILE_EMPTY,
    ERR_PATH_FILE_FAILED_SEEK,
    ERR_CMD_NON_ZERO_EXIT_CODE,
    ERR_SGFX_POS_OUTSIDE_CANVAS
};

const char *err2cstr(enum err err);

# if defined(IMP) || defined(IMP_LIB)

const char *
err2cstr(enum err err)
{
    switch ( err ) {
    case ERR_GENERAL_ERROR: return "ERR_GENERAL_ERROR";
    case ERR_OK: return "ERR_OK";
    case ERR_NOT_INT: return "ERR_NOT_INT";
    case ERR_TOO_BIG: return "ERR_TOO_BIG";
    case ERR_NULL_ARG: return "ERR_NULL_ARG";
    case ERR_INVALID_ARG: return "ERR_INVALID_ARG";
    case ERR_NOT_FOUND: return "ERR_NOT_FOUND";
    case ERR_INDEX_TOO_LARGE: return "ERR_INDEX_TOO_LARGE";
    case ERR_FAILED_OPEN: return "ERR_FAILED_OPEN";
    case ERR_FAILED_CLOSE: return "ERR_FAILED_CLOSE";
    case ERR_FAILED_READ: return "ERR_FAILED_READ";
    case ERR_FAILED_WRITE: return "ERR_FAILED_WRITE";
    case ERR_FAILED_FORK: return "ERR_FAILED_FORK";
    case ERR_FAILED_WAITPID: return "ERR_FAILED_WAITPID";
    case ERR_FAILED_STAT: return "ERR_FAILED_STAT";
    case ERR_FAILED_OPENDIR: return "ERR_FAILED_OPENDIR";
    case ERR_FAILED_READDIR: return "ERR_FAILED_READDIR";
    case ERR_WROTE_WRONG_AMOUNT: return "ERR_WROTE_WRONG_AMOUNT";
    case ERR_FAILED_ALLOC: return "ERR_FAILED_ALLOC";
    case ERR_FAILED_REALLOC: return "ERR_FAILED_REALLOC";
    case ERR_MKDIR_FAILED: return "ERR_MKDIR_FAILED";
    case ERR_FAILED_HASHING: return "ERR_FAILED_HASHING";
    case ERR_STR_EMPTY: return "ERR_STR_EMPTY";
    case ERR_PATH_EMPTY: return "ERR_PATH_EMPTY";
    case ERR_PATH_INVALID: return "ERR_PATH_INVALID";
    case ERR_PATH_FILE_EMPTY: return "ERR_PATH_FILE_EMPTY";
    case ERR_PATH_FILE_FAILED_SEEK: return "ERR_PATH_FILE_FAILED_SEEK";
    case ERR_CMD_NON_ZERO_EXIT_CODE: return "ERR_CMD_NON_ZERO_EXIT_CODE";
    case ERR_SGFX_POS_OUTSIDE_CANVAS: return "ERR_SGFX_POS_OUTSIDE_CANVAS";
    default: return "UNKNOWN_ERR";
    }
}

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

/* ---------------------------- 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 ------------------------------ */

extern char **environ;

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);
void abort(void);
void exit(int exit_code);

int execv(const char *path, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execvpe(const char *path, char *const argv[], char *const envp[]);

int fork(void);
# ifndef WIFEXITED
#  error "Must import <sys/wait.h> before lib.h!"
# endif

# if (! defined(_STDARG_H)) || (! defined(va_start))
#  error "Must import <stdarg.h> before lib.h!"
# endif

# ifndef _DIRENT_H
#  error "Must import <dirent.h> before lib.h!"
# endif

# ifndef _ERRNO_H
#  error "Must import <errno.h> before lib.h!"
# endif

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

# define TODO(msg) \
    do { \
        fprintf(stderr, "%s:%d "msg"\n", __FILE__, __LINE__); \
        abort(); \
    } while(0);

# 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; \
    }

# define _r_ __restrict__

# define SIZEOF_MEMBER(Struct, Member) sizeof(((Struct *)0)->Member)

# define CAST(Type, Var) ((Type) (Var))

/*# define LIB_FREE lib_free*/
# define LIB_FREE(Ptr) if ( (Ptr) != NULL ) { free((void *) (Ptr)); }

void lib_free(const void *ptr);
const char *temp_sprintf(const char *fmt, ...);

# if defined(IMP) || defined(IMP_LIB)

void
lib_free(const void *ptr)
{
    if ( ptr == NULL ) return;

    free((void *)ptr);
}

const char *
temp_sprintf(const char *fmt, ...)
{
    static char buf[1024] = {0};

    {
        va_list ap;
        va_start(ap, fmt);
        vsnprintf(buf, 1024, fmt, ap);
        va_end(ap);
    }
    buf[1023] = 0;

    return (const char *) buf;
}

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

/* ------------------------------ 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)

#  ifndef CSTR_SIZE_LIMIT
#   define CSTR_SIZE_LIMIT 1028
#  endif /* CSTR_SIZE_LIMIT */

#  define CSTR_ARRAY(...) (const char *[]){__VA_ARGS__, NULL}

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

struct __cstr_len_args {
    bool has_max;
    u64 max;
    bool *out_hit_max;
};
#  define cstr_len(Cstr, OutErr, ...) \
    _cstr_len( \
        (Cstr), \
        (struct __cstr_len_args) { \
            .has_max=false, \
            __VA_ARGS__ \
        }, \
        (OutErr) \
    )
u64 _cstr_len(const char *cstr, struct __cstr_len_args args,
              enum err *out_err);

const char *cstr_dup(const char *cstr, enum err *out_err);

#  define CSTR_WITH_SIZE(Cstr, OutErr, ...) \
    (Cstr), cstr_len((Cstr), (OutErr), __VA_ARGS__)

bool cstr_eq(const char *_r_ cstr1, u64 size1,
             const char *_r_ cstr2, u64 size2, enum err *_r_ out_err);

bool cstr_endswith(const char *_r_ cstr, u64 size,
                   const char *_r_ end, u64 end_size, enum err *_r_ out_err);

#  if defined(IMP_CSTR) || defined(IMP)

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

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, 0);
    LIB_ARG_MUST_NOT_BE_NULL(cstr, out_err, 0);

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

    ++ret;
    goto _loop;
_loop_end:

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ret;
}

u64
_cstr_len(const char *cstr, struct __cstr_len_args args, enum err *out_err)
{
    u64 ret = 0;

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, 0);
    LIB_ARG_MUST_NOT_BE_NULL(cstr, out_err, 0);

_loop:
    if ( args.has_max == true && ret >= args.max ) {
        LIB_SET_IF_NOT_NULL(args.out_hit_max, true);
        goto _loop_end;
    }
    if ( cstr[ret] == '\0' ) {
        LIB_SET_IF_NOT_NULL(args.out_hit_max, false);
        goto _loop_end;
    }

    ++ret;
    goto _loop;
_loop_end:

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ret;
}

const char *
cstr_dup(const char *cstr, enum err *out_err)
{
    char *ret = NULL;
    u64 size = 0;
    bool hit_limit = false;

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, NULL);
    LIB_ARG_MUST_NOT_BE_NULL(cstr, out_err, NULL);

    size = cstr_len_max(cstr, CSTR_SIZE_LIMIT, &hit_limit, NULL);
    if ( hit_limit == true ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_TOO_BIG);
        goto exit_err;
    }

    ret = malloc(size + 1);
    if ( ret == NULL ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_ALLOC);
        goto exit_err;
    }
    memset(ret, 0, size + 1);

    memcpy(ret, cstr, size);

    return (const char *) ret;
exit_err:
    LIB_FREE(ret);
    return NULL;
}

bool
cstr_eq(const char *_r_ cstr1, u64 size1,
        const char *_r_ cstr2, u64 size2, enum err *_r_ out_err)
{
    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, false);
    LIB_ARG_MUST_NOT_BE_NULL(cstr1, out_err, false);
    LIB_ARG_MUST_NOT_BE_NULL(cstr2, out_err, false);

    if ( size1 != size2 ) return false;

    for ( u64 i = 0; i < size1; ++i ) {
        if ( cstr1[i] != cstr2[i] ) return false;
    }

    return true;
}

bool
cstr_endswith(const char *_r_ cstr, u64 size,
              const char *_r_ end, u64 end_size, enum err *_r_ out_err)
{
    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, false);
    LIB_ARG_MUST_NOT_BE_NULL(cstr, out_err, false);
    LIB_ARG_MUST_NOT_BE_NULL(end, out_err, false);

    if ( size < end_size ) return false;

    u64 i = size - 1;
    u64 j = end_size - 1;
_loop:
    if ( cstr[i] != end[j] ) return false;
    if ( j == 0 ) return true;
    --i;
    --j;
    goto _loop;
}

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

# 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 */

# define STR_DESTROY(Str) \
    if ( (Str).should_be_freed == true ) LIB_FREE((Str).data)

# define DA2STR(Da, Str) \
    (Str).data = (Da).data; \
    (Str).size = (Da).size; \
    (Str).should_be_freed = true;

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;
    bool (*f)(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_tokenizer str_tokenize_func(struct str str, bool (*f)(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 DA DEF ------------------------------- */

# define DA_FOREACH(Da, Var) \
    for ( typeof((Da).data) (Var) = (Da).data; \
          (Var) < ((Da).data + (Da).size); \
          ++(Var) )

# define DA_FOREACH_INDEX(Da, VarI) \
    for ( typeof((Da).size) (VarI) = 0; \
          (VarI) < (Da).size; \
          ++(VarI) )

# define DA_APPEND(Da, Val, OutErr) \
    do { \
        if ( (OutErr) != NULL ) { \
            if ( *(OutErr) != ERR_OK ) { \
                break; \
            } \
        } \
        if ( (Da).size + 1 >= (Da).cap ) { \
            (Da).cap = (CAST(u64, ((Da).cap == 0) * 32) + (Da).cap) * 2; \
            (Da).data = realloc((Da).data, sizeof(*(Da).data) * (Da).cap); \
            if ( (Da).data == NULL ) { \
                LIB_SET_IF_NOT_NULL((OutErr), ERR_FAILED_REALLOC); \
                break; \
            } \
            memset((Da).data + (Da).size, 0, \
                   sizeof(*(Da).data) * ((Da).cap - (Da).size)); \
        } \
        (Da).data[(Da).size++] = (Val); \
        LIB_SET_IF_NOT_NULL((OutErr), ERR_OK); \
    } while(0)

# define DA_APPEND_DATA(Da, Val, ItemNum, ErrVar) \
    do { \
        if ( (ErrVar) != NULL ) { \
            if ( *(ErrVar) != ERR_OK ) { \
                break; \
            } \
        } \
        if ( (Da).size + (ItemNum) >= (Da).cap ) { \
            (Da).cap += (ItemNum); \
            (Da).cap *= 2; \
            (Da).data = realloc((Da).data, (Da).cap); \
            if ( (Da).data == NULL ) { \
                LIB_SET_IF_NOT_NULL((ErrVar), ERR_FAILED_REALLOC); \
                break; \
            } \
        } \
        if ( (Da).data == NULL ) { \
            if ( (Da).cap == 0 ) { \
                (Da).cap = (ItemNum) * 2; \
                (Da).size = 0; \
            } \
            (Da).data = malloc(sizeof(*(Da).data) * (Da).cap); \
            if ( (Da).data == NULL ) { \
                LIB_SET_IF_NOT_NULL((ErrVar), ERR_FAILED_ALLOC); \
                break; \
            } \
        } \
        memcpy((Da).data, (Val), sizeof(*(Da).data) * (ItemNum)); \
        (Da).size += ItemNum; \
        LIB_SET_IF_NOT_NULL((ErrVar), ERR_OK); \
    } while(0)

# define DAV_APPEND(Da, Val, ValSize, ErrVar) \
    do { \
        if ( ErrVar != NULL ) { \
            if ( *ErrVar != ERR_OK ) { \
                break; \
            } \
        } \
        /* printf("ValSize -> %5ld\tused_bytes -> %5ld\tcap_bytes -> %5ld\n", (ValSize), (Da).used_bytes, (Da).cap_bytes); */ \
        if ( (Da).used_bytes + (ValSize) >= (Da).cap_bytes ) { \
            (Da).cap_bytes *= 2; \
            (Da).data = realloc((Da).data, (Da).cap_bytes); \
            if ( (Da).data == NULL ) { \
                LIB_SET_IF_NOT_NULL((ErrVar), ERR_FAILED_REALLOC); \
                break; \
            } \
        } \
        if ( (Da).data == NULL ) { \
            if ( (Da).cap_bytes == 0 ) { \
                (Da).cap_bytes = 32; \
                (Da).size = 0; \
                (Da).used_bytes = 0; \
            } \
            (Da).data = malloc(sizeof(u64) * (Da).cap_bytes); \
            if ( (Da).data == NULL ) { \
                LIB_SET_IF_NOT_NULL((ErrVar), ERR_FAILED_ALLOC); \
                break; \
            } \
        } \
        memcpy(CAST(u8*, (Da).data) + (Da).used_bytes, (Val), (ValSize)); \
        (Da).used_bytes += (ValSize); \
        ++(Da).size; \
        LIB_SET_IF_NOT_NULL((ErrVar), ERR_OK); \
    } while(0)

/* ----------------------------- END DA DEF -------------------------------- */

/* ---------------------------- START MAP DEF ------------------------------ */

# if defined(WANT_MAP) || defined(WANT_ALL)

#  define MAP_FOR_EACH(Map, VIndex, VKey) \
    for ( (VIndex = (Map)->indexs.data, VKey = (Map)->keys.data); \
          VIndex < ((Map)->indexs.data + (Map)->indexs.size); \
          (++VIndex, VKey = CAST(struct key_bytes *, CAST(u8*, VKey->key) + VKey->size)) )

#  define MAP_FOR_EACH_INDEXS(Map, Var) \
    for ( Var = (Map)->indexs.data; \
          Var < ((Map)->indexs.data + (Map)->indexs.size); \
          ++Var )

#  define MAP_GET_INDEX(Map, Index) \
    ((struct map_item *)(((u8*)(Map)->data) + ((Map)->_item_size * (Index))))

struct key_bytes {
    u64 size;
    u8 key[1];
};

struct map_item {
    u64 key[4];
    u8  item[1];
};

struct map {
    struct map_item *data;
    u64 size;
    struct {
        u64 *data;
        u64 size;
        u64 cap;
    } indexs;
    struct {
        struct key_bytes *data;
        u64 size;
        u64 used_bytes;
        u64 cap_bytes;
    } keys;
    u64 _cap;
    u64 _size_bytes;
    u64 _item_size;
    u64 _elem_size;
    u64 *(*hash)(void *key, u64 key_size);
};

struct map map_create(u64 elem_size, u64 cap, enum err *_r_ out_err);

enum err map_inc_cap(struct map *_r_ m, enum err *_r_ out_err);

enum err map_add_hashed_key(struct map *_r_ m, u64 *_r_ key, void *_r_ elem,
                            enum err *_r_ out_err);
enum err map_add(struct map *_r_ m, void *_r_ key, u64 key_size, void *_r_ elem,
                 enum err *_r_ out_err);

void *map_get(struct map *_r_ m, void *_r_ key, u64 key_size, enum err *_r_ out_err);

enum err map_set_hashed_key(struct map *_r_ m, u64 *_r_ key, void *_r_ elem,
                            enum err *_r_ out_err);
enum err map_set(struct map *_r_ m, void *_r_ key, u64 key_size,
                 void *_r_ elem, enum err *_r_ out_err);

void *map_pop(struct map *_r_ m, void *_r_ key, u64 key_size, enum err *_r_ out_err);

enum err map_destroy(struct map *_r_ m, enum err *_r_ out_err);

u64 *map_default_hasher(void *key, u64 key_size);

#  if defined(IMP) || defined(IMP_MAP)

struct map
map_create(u64 elem_size, u64 cap, enum err *out_err)
{
    struct map empty = {0};
    struct map m = {0};

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, empty);

    m._cap = cap;
    m._elem_size = elem_size;
    m._item_size = SIZEOF_MEMBER(struct map_item, key) + m._elem_size + 1;
    m._size_bytes = m._item_size * m._cap;
    m.data = malloc(m._size_bytes);
    if ( m.data == NULL ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_ALLOC);
        goto err_exit;
    }
    memset(m.data, 0, m._size_bytes);

    m.indexs.cap = m._cap;
    m.indexs.data = malloc(sizeof(*m.indexs.data) * m.indexs.cap);
    if ( m.indexs.data == NULL ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_ALLOC);
        goto err_exit;
    }
    memset(m.indexs.data, 0, sizeof(*m.indexs.data) * m.indexs.cap);

    m.keys.cap_bytes = m._cap;
    m.keys.data = malloc(sizeof(u64) * m.keys.cap_bytes);
    if ( m.keys.data == NULL ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_ALLOC);
        goto err_exit;
    }
    memset(m.keys.data, 0, m.keys.cap_bytes);

    m.hash = map_default_hasher;

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return m;
err_exit:
    if ( m.data        != NULL ) free(m.data);
    if ( m.indexs.data != NULL ) free(m.indexs.data);
    if ( m.keys.data   != NULL ) free(m.keys.data);
    return empty;
}

enum err
map_inc_cap(struct map *_r_ m, enum err *_r_ out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    struct map old = {0};
    struct map nm = {0};
    u64 *index = 0;

    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);

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

    if ( out_err != NULL ) {
        perr = out_err;
    }

    nm = map_create(m->_elem_size, (m->_cap * 2), perr);
    if ( *perr != ERR_OK ) {
        goto err_exit;
    }

    MAP_FOR_EACH_INDEXS(m, index) {
        struct map_item *mi = MAP_GET_INDEX(m, (*index));
        map_add_hashed_key(&nm, mi->key, mi->item, perr);
        if ( *perr != ERR_OK ) {
            goto err_exit;
        }
    }

    old = *m;

    m->data        = nm.data;
    m->size        = nm.size;
    m->indexs      = nm.indexs;
    m->keys        = nm.keys;
    m->_cap        = nm._cap;
    m->_size_bytes = nm._size_bytes;
    m->_item_size  = nm._item_size;
    m->_elem_size  = nm._elem_size;
    m->hash        = nm.hash;

    map_destroy(&old, NULL);

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
err_exit:
    map_destroy(&nm, NULL);
    return *perr;
}

enum err
map_add_hashed_key(struct map *_r_ m, u64 *_r_ key, void *_r_ elem,
                   enum err *_r_ out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    u64 i = 0;
    struct map_item *it = NULL;

    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);

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

    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(key, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(elem, out_err);

    if ( out_err != NULL ) {
        perr = out_err;
    }

    i = (key[0] + key[1] + key[2] + key[3]) % m->_cap;

    do {
        if ( i >= m->_cap ) {
            map_inc_cap(m, perr);
            if ( *perr != ERR_OK ) {
                return *perr;
            }
        }
        it = MAP_GET_INDEX(m, i++);
    } while (it->key[0] != 0 || it->key[1] != 0
             || it->key[2] != 0 || it->key[3] != 0);
    /*
    printf("cap=%-5ld\telem_size=%-5ld\tsize_bytes=%-5ld\n",
           m->_cap, m->_elem_size, m->_size_bytes);
    */

    memcpy(it->key, key, sizeof(u64) * 4);
    memcpy(it->item, elem, m->_elem_size);

    DA_APPEND(m->indexs, i-1, perr);
    if ( *perr != ERR_OK ) {
        memset(it->key, 0, sizeof(u64) * 4);
        memset(it->item, 0, m->_elem_size);
        return *perr;
    }

    *perr = ERR_OK;
    return ERR_OK;
}

enum err
map_add(struct map *m, void *key, u64 key_size, void *elem, enum err *out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    struct key_bytes *sb = NULL;

    u64 *hash = NULL;

    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);

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

    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(key, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(elem, out_err);

    if ( out_err != NULL ) {
        perr = out_err;
    }

    hash = m->hash(key, key_size);
    if ( hash == NULL ) {
        *perr = ERR_FAILED_HASHING;
        return *perr;
    }

    map_add_hashed_key(m, hash, elem, perr);
    free(hash);

    if ( *perr != ERR_OK ) {
        return *perr;
    }

    sb = malloc(SIZEOF_MEMBER(struct key_bytes, size) + key_size);
    if ( sb == NULL ) {
        *perr = ERR_FAILED_ALLOC;
        return *perr;
    }
    memset(sb, 0, SIZEOF_MEMBER(struct key_bytes, size) + key_size);

    sb->size = key_size;
    memcpy(sb->key, key, key_size);


    DAV_APPEND(
        m->keys, sb,
        SIZEOF_MEMBER(struct key_bytes, size) + key_size,
        perr
    );

    /*
    do {
        if (perr != ((void * ) 0)) {
            if ( * perr != ERR_OK) {
                break;
            }
        }
        if ((m -> keys).used_bytes + (sizeof(((struct key_bytes * ) 0) -> size) + key_size) >= (m -> keys).cap_bytes) {
            (m -> keys).cap_bytes *= 2;
            (m -> keys).data = realloc((m -> keys).data, (m -> keys).cap_bytes);
            if ((m -> keys).data == ((void * ) 0)) {
                if ((perr) != ((void * ) 0)) {
                    *(perr) = ERR_FAILED_REALLOC;
                };
                break;
            }
        }
        if ((m -> keys).data == ((void * ) 0)) {
            if ((m -> keys).cap_bytes == 0) {
                (m -> keys).cap_bytes = 32;
                (m -> keys).size = 0;
                (m -> keys).used_bytes = 0;
            }(m -> keys).data = malloc(sizeof(u64) * (m -> keys).cap_bytes);
            if ((m -> keys).data == ((void * ) 0)) {
                if ((perr) != ((void * ) 0)) {
                    *(perr) = ERR_FAILED_ALLOC;
                };
                break;
            }
        }
        memcpy(((u8 * )((m -> keys).data)) + (m -> keys).used_bytes, (sb), (sizeof(((struct key_bytes * ) 0) -> size) + key_size));
        (m -> keys).used_bytes += (sizeof(((struct key_bytes * ) 0) -> size) + key_size);
        ++(m -> keys).size;
        if ((perr) != ((void * ) 0)) {
            *(perr) = ERR_OK;
        };
    } while (0);
    */

    free(sb);
    if ( *perr != ERR_OK ) {
        return *perr;
    }

    *perr = ERR_OK;
    return ERR_OK;
}

void *
map_get(struct map *m, void *key, u64 key_size, enum err *out_err)
{
    u64 i = 0;
    struct map_item *it = NULL;
    u64 *hash = NULL;

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, NULL);

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

    LIB_ARG_MUST_NOT_BE_NULL(key , out_err, NULL);

    hash = m->hash(key, key_size);
    i = (hash[0] + hash[1] + hash[2] + hash[3]) % m->_cap;

    do {
        if ( i >= m->_cap ) {
            LIB_SET_IF_NOT_NULL(out_err, ERR_NOT_FOUND);
            free(hash);
            return NULL;
        }
        it = MAP_GET_INDEX(m, i++);
    } while (it->key[0] != hash[0] || it->key[1] != hash[1]
             || it->key[2] != hash[2] || it->key[3] != hash[3]);

    free(hash);

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return it->item;
}

enum err
map_set_hashed_key(struct map *_r_ m, u64 *_r_ key, void *_r_ elem,
                   enum err *_r_ out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    u64 i = 0;
    struct map_item *it = NULL;
    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);

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

    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(key, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(elem, out_err);

    if ( out_err != NULL ) {
        perr = out_err;
    }

    i = (key[0] + key[1] + key[2] + key[3]) % m->_cap;

    do {
        if ( i >= m->_cap ) {
            *perr = ERR_NOT_FOUND;
            return *perr;
        }
        it = MAP_GET_INDEX(m, i++);
    } while (it->key[0] != key[0] || it->key[1] != key[1]
             || it->key[2] != key[2] || it->key[3] != key[3]);

    memcpy(it->item, elem, m->_elem_size);

    *perr = ERR_OK;
    return ERR_OK;
}

enum err
map_set(struct map *_r_ m, void *_r_ key, u64 key_size, void *_r_ elem,
        enum err *_r_ out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    u64 *hash = NULL;

    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);

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

    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(key, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(elem, out_err);

    if ( out_err != NULL ) {
        perr = out_err;
    }

    hash = m->hash(key, key_size);
    if ( hash == NULL ) {
        *perr = ERR_FAILED_HASHING;
        return *perr;
    }

    map_set_hashed_key(m, hash, elem, perr);
    if ( *perr != ERR_OK ) {
        free(hash);
        return *perr;
    }

    free(hash);

    *perr = ERR_OK;
    return ERR_OK;
}

void *map_pop(struct map *m, void *key, u64 key_size, enum err *out_err);

enum err
map_destroy(struct map *m, 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(m, out_err);

    if ( m->data        != NULL ) free(m->data);
    if ( m->indexs.data != NULL ) free(m->indexs.data);
    if ( m->keys.data   != NULL ) free(m->keys.data);

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}

u64 *
map_default_hasher(void *key, u64 key_size)
{
    u64 i = 0;
    u64 *k = NULL;

    k = malloc(sizeof(u64) * 4);
    if ( k == NULL ) {
        return NULL;
    }

    k[0] = 0;
    k[1] = 0;
    k[2] = 0;
    k[3] = 0;

    for ( i = 0; i < key_size; ++i ) {
        k[0] += ((u8*)key)[i] * 31;
        k[1] += ((u8*)key)[i] * 127;
        k[2] += ((u8*)key)[i] * 233;
        k[3] += ((u8*)key)[i] * 353;
    }

    return k;
}

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

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

/* ----------------------------- END MAP 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);

struct dir {
    const char *path;
    struct stat stat;
};

struct dirs {
    struct dir *data;
    u64 size;
    u64 cap;
};

struct __dir_list_args {
    bool stat;
    bool recurse;
    bool with_self;
    bool with_previous;
};
#  define dir_list_cstr(Path, OutErr, ...) \
    _dir_list_cstr( \
        (Path), \
        (struct __dir_list_args) {.stat=true, __VA_ARGS__}, \
        (OutErr) \
    )
struct dirs _dir_list_cstr(const char *path, struct __dir_list_args args,
                           enum err *out_err);

enum err dir_list_destroy(struct dirs *dirs, enum err *out_err);

#  if defined(IMP_PATH) || defined(IMP)

struct dirs
_dir_list_cstr(const char *path, struct __dir_list_args args,
               enum err *out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    struct dirs empty = {0};
    struct dirs dirs = {0};

    DIR *d = NULL;
    struct dirent *de = NULL;

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

    if ( out_err != NULL ) perr = out_err;

    d = opendir(path);
    if ( d == NULL ) {
        *perr = ERR_FAILED_OPENDIR;
        goto exit_err;
    }

    errno = 0;
    de = readdir(d);
    while ( de != NULL ) {
        struct dir dir = {0};

        if ( args.with_self == false ) {
            if ( cstr_eq(CSTR_WITH_SIZE(de->d_name, perr), ".", 1, perr) ) {
                goto _continue;
            }
        }

        if ( args.with_previous == false ) {
            if ( cstr_eq(CSTR_WITH_SIZE(de->d_name, perr), "..", 2, perr) ) {
                goto _continue;
            }
        }

        if ( args.recurse == true ) {
        }

        dir.path = cstr_dup(
            temp_sprintf("%s/%s", path, de->d_name),
            perr
        );
        if ( *perr != ERR_OK ) goto exit_err;

        if ( args.stat == true ) {
            *perr = stat(dir.path, &dir.stat);
            if ( *perr != ERR_OK ) {
                *perr = ERR_FAILED_STAT;
                goto exit_err;
            }
        }

        DA_APPEND(dirs, dir, perr);
        if ( *perr != ERR_OK ) goto exit_err;

    _continue:
        de = readdir(d);
    }

    if ( errno != 0 ) {
        *perr = ERR_FAILED_READDIR;
        goto exit_err;
    }

    closedir(d);

    *perr = ERR_OK;
    return dirs;
exit_err:
    if ( d != NULL ) closedir(d);
    dir_list_destroy(&dirs, NULL);
    return empty;
}

enum err
dir_list_destroy(struct dirs *_r_ dirs, enum err *_r_ out_err)
{
    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(dirs, out_err);

    if ( dirs->data != NULL ) {
        DA_FOREACH(*dirs, it) {
            LIB_FREE(it->path);
        }
    }
    LIB_FREE(dirs->data);

    dirs->data = NULL;
    dirs->size = 0;
    dirs->cap = 0;

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}

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

# 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);
};

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;
    struct str string;

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

struct tokenizer {
    u64 i;

    struct tokenizer_options opts;

    struct token last;

    struct path src;
    struct str code;

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


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

/* In normal use this function does not fail! */
struct token tokenizer_next_token(struct tokenizer *tkn, enum err *out_err);
enum token_type tokenizer_next_token_type(struct tokenizer *tkn,
                                          enum err *out_err);
struct token tokenizer_peek_token(struct tokenizer *tkn, enum err *out_err);
enum token_type tokenizer_peek_token_type(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 ) {
        goto eof_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 ) {
        tk.type = TK_ID;
        tk.loc_start = tkn->i-1;

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

        tk.string.data = buf.data;
        tk.string.size = buf.size;

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

    if ( tkn->opts.is_digit(tkn, c) == true ) {
        tk.type = TK_NUM_LIT;
        tk.loc_start = tkn->i-1;

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

        tk.string.data = buf.data;
        tk.string.size = buf.size;
        tk.loc_end = tkn->i-1;
        goto exit;
    }

    if ( tkn->opts.is_str_lit_start(tkn, c) == true ) {
        tk.type = TK_STR_LIT;
        tk.loc_start = tkn->i-1;

        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.string.data = buf.data;
        tk.string.size = buf.size - 1;
        tk.loc_end = tkn->i-1;
        goto exit;
    }

    tk.type = c;

    if ( tkn->i >= tkn->code.size ) {
eof_tk:
        tk.type = TK_EOF;
    }

    tk.loc_start = tkn->i-1;
    tk.loc_end   = tkn->i-1;

exit:
    tkn->last = tk;
    return tk;
}

enum token_type
tokenizer_next_token_type(struct tokenizer *tkn, enum err *out_err)
{
    struct token tk = {0};
    enum err err = ERR_OK;
    enum err *perr = &err;

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, TK_INVALID);

    LIB_ARG_MUST_NOT_BE_NULL(tkn, out_err, TK_INVALID);

    if ( out_err != NULL ) {
        perr = out_err;
    }

    tk = tokenizer_next_token(tkn, perr);
    if ( *perr != ERR_OK ) {
        return TK_INVALID;
    }

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return tk.type;
}

struct token
tokenizer_peek_token(struct tokenizer *tkn, enum err *out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    u64 last_i = 0;
    struct token empty = {0};
    struct token tk = {0};

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

    if ( out_err != NULL ) {
        perr = out_err;
    }

    last_i = tkn->i;
    tk = tokenizer_next_token(tkn, perr);
    tkn->i = last_i;

    return tk;
}

enum token_type
tokenizer_peek_token_type(struct tokenizer *tkn, enum err *out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    struct token tk = {0};

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, TK_INVALID);
    LIB_ARG_MUST_NOT_BE_NULL(tkn, out_err, TK_INVALID);

    tk = tokenizer_peek_token(tkn, perr);

    return tk.type;
}

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 ( 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};
    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 ( tk.type != TK_ID ) {
        tkn->i = pi;
        return false;
    }

    if ( str_eq_str(tk.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;

        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 > 0 ) {
                --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 CMD DEF ------------------------------ */

# if defined(WANT_CMD) || defined(WANT_ALL)

#  ifdef unix
#   define CC "cc"
#   define CC_FLAGS "-std=gnu99", "-m64", "-O2", "-pedantic", \
    "-Wall", "-Wextra", "-Wshadow", "-Wcast-align", "-Wunused", \
    "-Wconversion", "-Wmisleading-indentation", "-Wdouble-promotion", \
    "-Wformat=2", "-Wbad-function-cast", "-Wmissing-declarations", \
    "-Wmissing-prototypes", "-Wnested-externs", "-Werror", \
    "-Wno-override-init"
#  else
#   error "No CC for this platform"
#  endif

struct procs {
    i32 *data;
    u64 size;
    u64 cap;
};

enum err procs_wait(struct procs *_r_ procs, enum err *_r_ out_err);

struct cmd {
    struct {
        char **data;
        u64 size;
        u64 cap;
    } args;
    struct {
        char **data;
        u64 size;
        u64 cap;
    } envs;
};

struct __cmd_create_args {
    bool copy_env;
};
#  define cmd_create(Exe, OutErr, ...) \
    _cmd_create( \
        (Exe), \
        (struct __cmd_create_args) { \
            .copy_env=true, \
            __VA_ARGS__ \
        }, \
        (OutErr) \
    )
#  define cmd_create_ns(Exe, OutErr, ...) \
    cmd_create( \
        ((struct str) { \
            .data=(Exe), \
            .size=cstr_len_max((Exe), STR_SIZE_LIMIT, NULL, (OutErr)) \
        }), \
        (OutErr), \
        __VA_ARGS__ \
    )
struct cmd _cmd_create(struct str exe, struct __cmd_create_args args,
                       enum err *_r_ out_err);

#  define cmd_set_exe_ns(Cmd, Exe, OutErr) \
    cmd_set_exe( \
        (Cmd), \
        (struct str) { \
            .data=(Exe), \
            .size=cstr_len_max((Exe), STR_SIZE_LIMIT, NULL, (OutErr)) \
        }, \
        (OutErr) \
    )
enum err cmd_set_exe(struct cmd *_r_ cmd, struct str exe,
                     enum err *_r_ out_err);

#  define cmd_append_arg_ns(Cmd, Arg, OutErr) \
    cmd_append_arg( \
        (Cmd), \
        (struct str) { \
            .data=(Arg), \
            .size=cstr_len_max((Arg), STR_SIZE_LIMIT, NULL, (OutErr)) \
        }, \
        (OutErr) \
    )
enum err cmd_append_arg(struct cmd *_r_ cmd, struct str arg,
                        enum err *_r_ out_err);

#  define cmd_append_args_cstrs(Cmd, OutErr, ...) \
    _cmd_append_args_cstrs( \
        (Cmd), \
        (const char *[]){__VA_ARGS__, NULL}, \
        (OutErr) \
    )
enum err _cmd_append_args_cstrs(struct cmd *_r_ cmd, const char **args,
                                enum err *_r_ out_err);

#  define cmd_append_env_ns(Cmd, Arg, OutErr) \
    cmd_append_env( \
        (Cmd), \
        (struct str) { \
            .data=(Arg), \
            .size=cstr_len_max((Arg), STR_SIZE_LIMIT, NULL, (OutErr)) \
        }, \
        (OutErr) \
    )
enum err cmd_append_env(struct cmd *_r_ cmd, struct str env,
                        enum err *_r_ out_err);

struct __cmd_exec_args {
    struct procs *procs;
    bool reset;
    bool search_path;
};
#  define cmd_exec(Cmd, OutErr, ...) \
    _cmd_exec( \
        (Cmd), \
        (struct __cmd_exec_args) { \
            .reset=true, \
            .search_path=true, \
            __VA_ARGS__ \
        }, \
        (OutErr) \
    )
enum err _cmd_exec(struct cmd *_r_ cmd, struct __cmd_exec_args args,
                   enum err *_r_ out_err);

enum err cmd_clear_args(struct cmd *cmd, enum err *out_err);
enum err cmd_destroy(struct cmd *cmd, enum err *out_err);

struct __cmd_go_rebuild_yourself_args {
    const char **deps;
    const char **opts;
    bool debug;
};

#  define GO_REBUILD_YOURSELF(...) \
    _cmd_go_rebuild_yourself( \
        __FILE__, \
        argv, \
        (struct __cmd_go_rebuild_yourself_args){.debug=false, __VA_ARGS__} \
    )
void _cmd_go_rebuild_yourself(const char *src, char **argv,
                              struct __cmd_go_rebuild_yourself_args args);


#  if defined(IMP_CMD) || defined(IMP)

enum err
procs_wait(struct procs *_r_ procs, enum err *_r_ out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    i32 status = 0;

    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(procs, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(procs->data, out_err);

    if ( out_err != NULL ) {
        perr = out_err;
    }

    DA_FOREACH(*procs, it) {
        if ( waitpid(*it, &status, 0) < 0 ) {
            *perr = ERR_FAILED_WAITPID;
            continue;
        }

        if ( WIFEXITED(status) == true ) {
            if ( WEXITSTATUS(status) != 0 ) {
                *perr = ERR_CMD_NON_ZERO_EXIT_CODE;
            }
        }
    }

    return *perr;
}

struct cmd
_cmd_create(struct str exe, struct __cmd_create_args args,
            enum err *_r_ out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    struct cmd empty = {0};
    struct cmd cmd = {0};

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, empty);
    LIB_ARG_MUST_NOT_BE_NULL(exe.data, out_err, empty);

    if ( out_err != NULL ) {
        perr = out_err;
    }

    cmd.args.cap = 32;
    cmd.args.data = malloc(sizeof(*cmd.args.data) * cmd.args.cap);
    if ( cmd.args.data == NULL ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_ALLOC);
        goto exit_err;
    }
    memset(cmd.args.data, 0, sizeof(*cmd.args.data) * cmd.args.cap);
    cmd_append_arg(&cmd, exe, perr);
    if ( *perr != ERR_OK ) {
        goto exit_err;
    }

    cmd.envs.cap = 32;
    cmd.envs.data = malloc(sizeof(*cmd.envs.data) * cmd.envs.cap);
    if ( cmd.envs.data == NULL ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_ALLOC);
        goto exit_err;
    }
    memset(cmd.envs.data, 0, sizeof(*cmd.envs.data) * cmd.envs.cap);

    if ( args.copy_env == true && environ != NULL ) {
        char **envs = environ;
        while ( *envs != NULL ) {
            cmd_append_env_ns(&cmd, *envs, perr);
            if ( *perr != ERR_OK ) goto exit_err;
            ++envs;
        }
    }

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return cmd;
exit_err:
    cmd_destroy(&cmd, perr);
    return (struct cmd){0};
}

enum err
cmd_set_exe(struct cmd *_r_ cmd, struct str exe, enum err *_r_ out_err)
{
    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(cmd, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(exe.data, out_err);

    if ( cmd->args.data[0] != NULL ) {
        LIB_FREE(cmd->args.data[0]);
    }

    cmd->args.data[0] = malloc(exe.size+1);
    if ( cmd->args.data[0] == NULL ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_ALLOC);
        return ERR_FAILED_ALLOC;
    }
    cmd->args.data[0][exe.size] = 0;
    memcpy(cmd->args.data[0], exe.data, exe.size);

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}

enum err
cmd_append_arg(struct cmd *_r_ cmd, struct str arg, enum err *_r_ out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    char *t = NULL;

    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(cmd, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(cmd->args.data, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(arg.data, out_err);

    if ( out_err != NULL ) {
        perr = out_err;
    }

    t = malloc(arg.size + 1);
    if ( t == NULL ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_ALLOC);
        return ERR_FAILED_ALLOC;
    }
    t[arg.size] = 0;
    memcpy(t, arg.data, arg.size);

    DA_APPEND(cmd->args, t, perr);

    return *perr;
}

enum err
_cmd_append_args_cstrs(struct cmd *_r_ cmd, const char **args,
                       enum err *_r_ out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(cmd, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(cmd->args.data, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(args, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(args[0], out_err);

    if ( out_err != NULL ) {
        perr = out_err;
    }

    while ( *args != NULL ) {
        cmd_append_arg_ns(cmd, *args, perr);
        if ( *perr != ERR_OK ) return *perr;
        ++args;
    }

    return *perr;
}

enum err
cmd_append_env(struct cmd *_r_ cmd, struct str env, enum err *_r_ out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    char *t = NULL;

    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(cmd, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(cmd->args.data, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(env.data, out_err);

    if ( out_err != NULL ) {
        perr = out_err;
    }

    t = malloc(env.size + 1);
    if ( t == NULL ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_ALLOC);
        return ERR_FAILED_ALLOC;
    }
    t[env.size] = 0;
    memcpy(t, env.data, env.size);

    DA_APPEND(cmd->envs, t, perr);

    return *perr;
}

enum err
_cmd_exec(struct cmd *_r_ cmd, struct __cmd_exec_args args,
          enum err *_r_ out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    i32 pid = -1;
    i32 status = 0;

    LIB_ARG_IF_NOT_NULL_MUST_BE_RET_IT(out_err, ERR_OK);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(cmd, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(cmd->args.data, out_err);

    if ( out_err != NULL ) {
        perr = out_err;
    }

    cmd->args.data[cmd->args.size] = NULL;

    pid = fork();
    if ( pid < 0 ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_FORK);
        return ERR_FAILED_FORK;
    }

    if ( pid == 0 ) {
        (
            (args.search_path == true)
                ? execvpe
                : execve
        ) (cmd->args.data[0], cmd->args.data, cmd->envs.data);
        exit(1);
    }

    if ( args.reset == true ) {
        cmd_clear_args(cmd, NULL);
    }

    if ( args.procs != NULL ) {
        DA_APPEND(*args.procs, pid, perr);
        return *perr;
    }

    if ( waitpid(pid, &status, 0) < 0 ) {
        LIB_SET_IF_NOT_NULL(out_err, ERR_FAILED_WAITPID);
        return ERR_FAILED_WAITPID;
    }

    if ( WIFEXITED(status) == true ) {
        if ( WEXITSTATUS(status) != 0 ) {
            LIB_SET_IF_NOT_NULL(out_err, ERR_CMD_NON_ZERO_EXIT_CODE);
            return ERR_CMD_NON_ZERO_EXIT_CODE;
        }
    }

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}

enum err
cmd_clear_args(struct cmd *cmd, 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(cmd, out_err);
    LIB_ARG_MUST_NOT_BE_NULL_SET_RET_ERR(cmd->args.data, out_err);

    DA_FOREACH(cmd->args, it) {
        LIB_FREE(*it);
    }
    cmd->args.size = 0;

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}

enum err
cmd_destroy(struct cmd *cmd, 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(cmd, out_err);

    if ( cmd->args.data != NULL ) {
        DA_FOREACH(cmd->args, it) {
            LIB_FREE(*it);
        }
    }
    LIB_FREE(cmd->args.data);

    if ( cmd->envs.data != NULL ) {
        DA_FOREACH(cmd->envs, it) {
            LIB_FREE(*it);
        }
    }
    LIB_FREE(cmd->envs.data);

    cmd->args.data = NULL;
    cmd->args.cap = 0;
    cmd->args.size = 0;
    cmd->envs.data = NULL;
    cmd->envs.cap = 0;
    cmd->envs.size = 0;

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}

#   ifdef _SYS_STAT_H
void
_cmd_go_rebuild_yourself(const char *src, char **argv,
                         struct __cmd_go_rebuild_yourself_args args)
{
    enum err err = ERR_OK;
    struct stat src_stat = {0};
    struct stat exec_stat = {0};

    if ( err == ERR_OK ) err = stat(src, &src_stat);
    if ( err == ERR_OK ) err = stat(argv[0] , &exec_stat);

    if ( err == ERR_OK ) if ( args.deps != NULL ) {
        while ( *args.deps != NULL ) {
            struct stat tmp_stat = {0};
            err = stat(*args.deps++, &tmp_stat);
            if ( err != ERR_OK ) break;
            if ( tmp_stat.st_mtime > src_stat.st_mtime ) {
                src_stat = tmp_stat;
            }
        }
    }

    if ( err == ERR_OK ) do {
        if ( src_stat.st_mtime > exec_stat.st_mtime ) {
            struct cmd cmd = cmd_create_ns(CC, &err);

            cmd_append_args_cstrs(&cmd, &err, CC_FLAGS);

            if ( args.debug == true ) {
                cmd_append_args_cstrs(
                    &cmd, &err,
                    "-O0", "-ggdb", "-fsanitize=address"
                );
            }

            if ( args.opts != NULL ) {
                _cmd_append_args_cstrs(&cmd, args.opts, &err);
            }

            cmd_append_args_cstrs(
                &cmd, &err,
                src, "-o", temp_sprintf("%s.new", argv[0])
            );
            cmd_exec(&cmd, &err);
            cmd_destroy(&cmd, &err);
            if ( err == ERR_OK ) err = rename(
                temp_sprintf("%s.new", argv[0]), argv[0]
            );

            if ( err == ERR_OK ) execv(argv[0], argv);
            if ( err == ERR_OK ) exit(0);
        }
    } while(0);

    if ( err != ERR_OK ) {
        fprintf(stderr, "Failed recompiling myself: %s\n", err2cstr(err));
        exit(1);
    }
}
#   endif /* _SYS_STAT_H */

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

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

/* ----------------------------- END CMD 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 ------------------------------- */

/* ---------------------------- START SLOP DEF ----------------------------- */

# if defined(WANT_SLOP) || defined(WANT_ALL)

u8 *slop_file_slurp(const char *_r_ path, u64 *_r_ out_size,
                    enum err *_r_ out_err);
enum err slop_file_save(const char *_r_ path, u8 *_r_ data, u64 size,
                        enum err *_r_ out_err);

#  if defined(IMP) || defined(IMP_SLOP)

u8 *
slop_file_slurp(const char *_r_ path, u64 *_r_ out_size, enum err *_r_ out_err)
{
    enum err err = ERR_OK;
    enum err *perr = &err;

    struct path p = {0};
    struct file f = {0};

    LIB_ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, NULL);

    LIB_ARG_MUST_NOT_BE_NULL(path, out_err, NULL);

    if ( out_err != NULL ) {
        perr = out_err;
    }

    p = path_from_cstr_ns(path, perr);
    f = path_file_read_all(&p, perr);
    if ( out_size != NULL ) {
        *out_size = f.size;
    }

    return f.data;
}

enum err slop_file_save(const char *_r_ path, u8 *_r_ data, u64 size,
                        enum err *_r_ out_err);

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

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

/* ----------------------------- END SLOP 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 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)) + 1);
    memset((void *)ret.data, 0, (ret.size * sizeof(*ret.data)) + 1);

    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_tokenizer
str_tokenize_func(struct str str, bool (*f)(char c))
{
    struct str_tokenizer st = {0};
    st.str = str;
    st.f = f;
    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;

    if ( st->f != NULL ) {
        while ( (! st->f(str.data[str.size]))
                && st->cur < st->str.size ) {
            ++str.size;
            ++st->cur;
        }
    } else {
        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 = ERR_OK;

    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 = ERR_OK;
    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 */
