#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")

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

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

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

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

typedef          long int i64;
typedef unsigned long int u64;

typedef          int i32;
typedef unsigned int u32;

typedef          short int i16;
typedef unsigned short int u16;

typedef          char i8;
typedef unsigned char u8;

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

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

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


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

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

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

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


_VEC2_STRUCT_DEF(i64);
_VEC2_STRUCT_DEF(u64);

_VEC2_STRUCT_DEF(i32);
_VEC2_STRUCT_DEF(u32);

_VEC2_STRUCT_DEF(i16);
_VEC2_STRUCT_DEF(u16);

_VEC2_STRUCT_DEF(i8);
_VEC2_STRUCT_DEF(u8);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

struct str str_dup(struct str str, ALLOC_FUNC);

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

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

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

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

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

bool str_eq_cstr(struct str str, const char *cstr, u64 cstr_size);
bool str_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);

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


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



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

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

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

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

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

# define GETENV_FUNC char *(*getenv)(const char *)

struct str getenv_as_str(GETENV_FUNC, const char *env, enum err *out_err);
struct path getenv_as_path(GETENV_FUNC, 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(GETENV_FUNC, enum err *out_err);
struct path path_get_xdg_data_home(GETENV_FUNC, enum err *out_err);

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

enum gui_unit_type {
    GUT_ABSOLUTE = 0,
    GUT_PERCENTAGE
};

enum gui_unit_reference {
    GUR_TOP_LEFT = 0,
    GUR_CENTER
};

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

enum gui_item_type {
    GIT_BUTTOM = 0
};

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

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

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

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

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

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

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

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

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

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

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

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return empty;
}

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

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

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return true;
}

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

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

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

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

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}

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

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

    LIB_SET_IF_NOT_NULL(out_err, ERR_OK);
    return ERR_OK;
}

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

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

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

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

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

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

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

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

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

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

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

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

#  ifndef _CTYPE_H
#   define LIB_H_CTYPE

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

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

#  endif /* _CTYPE_H */


#  ifndef _STRING_H
#   define LIB_H_STRING

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

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

    return dest;
}

#   define memcpy _str_memcpy
#  endif /* _STRING_H */

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

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

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

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

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

    _ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, 0);

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

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

    ++ret;
    goto _loop;
_loop_end:

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return ret;
}

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

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

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

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

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

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

    if ( cstr_size >= STR_SIZE_LIMIT ) {
        _SET_IF_NOT_NULL(out_err, ERR_TOO_BIG);
        return str;
    }

    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, ALLOC_FUNC, enum err *out_err)
{
    #define BUF_CAP ((u64) 256)
    char *data = NULL;
    char buf[256] = {0};
    u64 buf_size = 0;
    u64 _num = 0;
    struct str ret = {0};

    _ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, STR_EMPTY);

    data = alloc(STR_SIZE_LIMIT);
    ret.should_be_freed = true;

    if ( num < 0 ) {
        /* TODO: Check for cap */
        buf[buf_size++] = '-';
        num *= -1;
    }

    _num = (u64)num;

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

        /* TODO: Check for cap */
        buf[buf_size++] = (char) (mod + 48);

        _num /= 10;

        if ( buf_size >= BUF_CAP ) {
            _loop:
            /* TODO: Check for cap */
            data[ret.size++] = buf[--buf_size];
            if ( buf_size > 0 ) {
                goto _loop;
            }
        }

        if ( _num == 0 ) {
            break;
        }
    }
    if ( buf_size == 0 ) {
        goto exit;
    }

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

    exit:
    data[ret.size] = 0;
    ret.data = data;
    _SET_IF_NOT_NULL(out_err, ERR_OK)
    return ret;
    #undef BUF_CAP
}


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

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

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

    ret.should_be_freed = true;
    return ret;
}

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

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

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

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

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

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

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return res;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

    return str;

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

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

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

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

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

    return str;

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

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

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

    return true;
}

bool
str_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(GETENV_FUNC, const char *name, enum err *out_err)
{
    struct str empty = {0};
    struct str str = {0};
    char *res = NULL;

    if ( getenv == NULL || name == NULL ) {
        _SET_IF_NOT_NULL(out_err, ERR_NULL_ARG);
        return empty;
    }

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

    return str_from_cstr_ns(res, out_err);
}

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

    if ( getenv == NULL || name == NULL ) {
        _SET_IF_NOT_NULL(out_err, ERR_NULL_ARG);
        return empty;
    }

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

    return path_from_str(str, out_err);
}

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

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

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

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

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

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

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

    path.size = str.size;

    _SET_IF_NOT_NULL(out_err, ERR_OK);
    return path;
}

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

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

    return path_from_str(str, out_err);
}

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

    _ARG_IF_NOT_NULL_MUST_BE(out_err, ERR_OK, path);

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

    return path_from_str(str, out_err);
}

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

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

    path = getenv_as_path(getenv, "XDG_STATE_HOME", &err);
    if ( err == ERR_NOT_FOUND ) {
        path = getenv_as_path(getenv, "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(GETENV_FUNC, enum err *out_err)
{
    struct path empty = {0};
    struct path path = {0};
    enum err err;

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

    path = getenv_as_path(getenv, "XDG_DATA_HOME", &err);
    if ( err == ERR_NOT_FOUND ) {
        path = getenv_as_path(getenv, "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 */
