#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include "../../include/cleonos_syscall.h"

#ifdef putc
#undef putc
#endif

#define DG_HEAP_SIZE (32U * 1024U * 1024U)
#define DG_MAX_MEM_FD 64
#define DG_PATH_MAX 192
#define DG_ENV_LINE_MAX 256
#define DG_STDIO_MAGIC 0x44474D46U
#define DG_SERIAL_LOG_CHUNK 176U
#define DG_SERIAL_LOG_PREFIX "[DOOM] "

struct dg_alloc_hdr {
    size_t size;
    int free;
    struct dg_alloc_hdr *next;
    struct dg_alloc_hdr *prev;
};

struct dg_mem_fd {
    int used;
    int writable;
    int dirty;
    size_t pos;
    size_t size;
    size_t cap;
    unsigned char *data;
    char path[DG_PATH_MAX];
};

struct dg_stream {
    unsigned int magic;
    int fd;
    int eof_flag;
    int err_flag;
};

static unsigned char g_dg_heap[DG_HEAP_SIZE];
static struct dg_alloc_hdr *g_dg_heap_head = (struct dg_alloc_hdr *)0;
static int g_dg_heap_ready = 0;
static struct dg_mem_fd g_dg_fds[DG_MAX_MEM_FD];
static struct dg_stream g_dg_stdin_stream = {DG_STDIO_MAGIC, 0, 0, 0};
static struct dg_stream g_dg_stdout_stream = {DG_STDIO_MAGIC, 1, 0, 0};
static struct dg_stream g_dg_stderr_stream = {DG_STDIO_MAGIC, 2, 0, 0};
static int g_dg_errno = 0;
static unsigned short g_dg_ctype_table[384];
static int32_t g_dg_toupper_table[384];
static int32_t g_dg_tolower_table[384];
static int g_dg_ctype_ready = 0;

FILE *stdin = (FILE *)(void *)&g_dg_stdin_stream;
FILE *stdout = (FILE *)(void *)&g_dg_stdout_stream;
FILE *stderr = (FILE *)(void *)&g_dg_stderr_stream;

int *__errno_location(void) {
    return &g_dg_errno;
}

static int dg_is_digit(int ch) {
    return (ch >= '0' && ch <= '9') ? 1 : 0;
}

static int dg_is_upper(int ch) {
    return (ch >= 'A' && ch <= 'Z') ? 1 : 0;
}

static int dg_is_lower(int ch) {
    return (ch >= 'a' && ch <= 'z') ? 1 : 0;
}

static int dg_is_alpha(int ch) {
    return (dg_is_upper(ch) != 0 || dg_is_lower(ch) != 0) ? 1 : 0;
}

static int dg_is_xdigit(int ch) {
    return (dg_is_digit(ch) != 0 || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) ? 1 : 0;
}

static int dg_is_space_char(int ch) {
    return (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f') ? 1 : 0;
}

static int dg_to_lower_ascii(int ch) {
    return (dg_is_upper(ch) != 0) ? (ch - 'A' + 'a') : ch;
}

static void dg_init_ctype_table(void) {
    int c;

    if (g_dg_ctype_ready != 0) {
        return;
    }

    for (c = -128; c < 256; c++) {
        unsigned short flags = 0U;
        int ch = (c < 0) ? (c + 256) : c;
        int upper = dg_is_lower(ch) ? (ch - 'a' + 'A') : ch;
        int lower = dg_is_upper(ch) ? (ch - 'A' + 'a') : ch;

        if (c == -1) {
            upper = -1;
            lower = -1;
        }

        if (dg_is_upper(ch) != 0) {
            flags |= _ISupper;
        }
        if (dg_is_lower(ch) != 0) {
            flags |= _ISlower;
        }
        if (dg_is_alpha(ch) != 0) {
            flags |= _ISalpha;
        }
        if (dg_is_digit(ch) != 0) {
            flags |= _ISdigit;
        }
        if (dg_is_xdigit(ch) != 0) {
            flags |= _ISxdigit;
        }
        if (dg_is_space_char(ch) != 0) {
            flags |= _ISspace;
        }
        if (ch == ' ' || ch == '\t') {
            flags |= _ISblank;
        }
        if (ch < 32 || ch == 127) {
            flags |= _IScntrl;
        }
        if (ch >= 32 && ch <= 126) {
            flags |= _ISprint;
        }
        if (ch >= 33 && ch <= 126) {
            flags |= _ISgraph;
        }
        if ((flags & (_ISalpha | _ISdigit)) != 0U) {
            flags |= _ISalnum;
        }
        if ((flags & (_ISgraph | _ISalnum)) != 0U && (flags & _ISalnum) == 0U) {
            flags |= _ISpunct;
        }

        g_dg_ctype_table[c + 128] = flags;
        g_dg_toupper_table[c + 128] = (int32_t)upper;
        g_dg_tolower_table[c + 128] = (int32_t)lower;
    }

    g_dg_ctype_ready = 1;
}

const unsigned short int **__ctype_b_loc(void) {
    static const unsigned short int *table_ptr = (const unsigned short int *)(void *)(g_dg_ctype_table + 128);
    dg_init_ctype_table();
    return &table_ptr;
}

const int32_t **__ctype_toupper_loc(void) {
    static const int32_t *table_ptr = (const int32_t *)(void *)(g_dg_toupper_table + 128);
    dg_init_ctype_table();
    return &table_ptr;
}

const int32_t **__ctype_tolower_loc(void) {
    static const int32_t *table_ptr = (const int32_t *)(void *)(g_dg_tolower_table + 128);
    dg_init_ctype_table();
    return &table_ptr;
}

int system(const char *command) {
    (void)command;
    errno = ENOSYS;
    return -1;
}

static size_t dg_strlen(const char *text) {
    size_t len = 0U;

    if (text == (const char *)0) {
        return 0U;
    }

    while (text[len] != '\0') {
        len++;
    }

    return len;
}

static void dg_copy(char *dst, size_t dst_size, const char *src) {
    size_t i = 0U;

    if (dst == (char *)0 || dst_size == 0U) {
        return;
    }

    if (src == (const char *)0) {
        dst[0] = '\0';
        return;
    }

    while (src[i] != '\0' && i + 1U < dst_size) {
        dst[i] = src[i];
        i++;
    }

    dst[i] = '\0';
}

static int dg_starts_with(const char *text, const char *prefix) {
    size_t i = 0U;

    if (text == (const char *)0 || prefix == (const char *)0) {
        return 0;
    }

    while (prefix[i] != '\0') {
        if (text[i] != prefix[i]) {
            return 0;
        }
        i++;
    }

    return 1;
}

static int dg_path_exists(const char *path) {
    return (cleonos_sys_fs_stat_type(path) == (u64)-1) ? 0 : 1;
}

static void dg_join_path(char *out_path, size_t out_size, const char *dir, const char *name) {
    size_t dlen;
    size_t i = 0U;

    if (out_path == (char *)0 || out_size == 0U) {
        return;
    }

    out_path[0] = '\0';

    if (dir == (const char *)0 || name == (const char *)0) {
        return;
    }

    dlen = dg_strlen(dir);

    while (i < dlen && i + 1U < out_size) {
        out_path[i] = dir[i];
        i++;
    }

    if (i > 0U && out_path[i - 1U] != '/' && i + 1U < out_size) {
        out_path[i++] = '/';
    }

    {
        size_t j = 0U;
        while (name[j] != '\0' && i + 1U < out_size) {
            out_path[i++] = name[j++];
        }
    }

    out_path[i] = '\0';
}

static void dg_resolve_read_path(const char *path, char *out_path, size_t out_size) {
    char candidate[DG_PATH_MAX];

    if (path == (const char *)0 || out_path == (char *)0 || out_size == 0U) {
        return;
    }

    if (path[0] == '/') {
        dg_copy(out_path, out_size, path);
        return;
    }

    dg_join_path(candidate, sizeof(candidate), "/temp", path);
    if (dg_path_exists(candidate) != 0) {
        dg_copy(out_path, out_size, candidate);
        return;
    }

    dg_join_path(candidate, sizeof(candidate), "/shell", path);
    if (dg_path_exists(candidate) != 0) {
        dg_copy(out_path, out_size, candidate);
        return;
    }

    dg_join_path(candidate, sizeof(candidate), "/", path);
    dg_copy(out_path, out_size, candidate);
}

static void dg_resolve_write_path(const char *path, char *out_path, size_t out_size) {
    if (path == (const char *)0 || out_path == (char *)0 || out_size == 0U) {
        return;
    }

    if (path[0] == '/') {
        dg_copy(out_path, out_size, path);
        return;
    }

    dg_join_path(out_path, out_size, "/temp", path);
}

static int dg_is_temp_path(const char *path) {
    return dg_starts_with(path, "/temp");
}

static int dg_fd_slot_from_fd(int fd) {
    int slot = fd - 3;

    if (slot < 0 || slot >= DG_MAX_MEM_FD) {
        return -1;
    }

    if (g_dg_fds[slot].used == 0) {
        return -1;
    }

    return slot;
}

static int dg_fd_alloc_slot(void) {
    int i;

    for (i = 0; i < DG_MAX_MEM_FD; i++) {
        if (g_dg_fds[i].used == 0) {
            g_dg_fds[i].used = 1;
            g_dg_fds[i].writable = 0;
            g_dg_fds[i].dirty = 0;
            g_dg_fds[i].pos = 0U;
            g_dg_fds[i].size = 0U;
            g_dg_fds[i].cap = 0U;
            g_dg_fds[i].data = (unsigned char *)0;
            g_dg_fds[i].path[0] = '\0';
            return i;
        }
    }

    return -1;
}

static int dg_fd_ensure_cap(struct dg_mem_fd *file, size_t need) {
    unsigned char *new_buf;
    size_t new_cap;

    if (file == (struct dg_mem_fd *)0) {
        errno = EINVAL;
        return -1;
    }

    if (need <= file->cap) {
        return 0;
    }

    new_cap = (file->cap == 0U) ? 256U : file->cap;
    while (new_cap < need) {
        if (new_cap > ((size_t)-1) / 2U) {
            errno = ENOMEM;
            return -1;
        }
        new_cap *= 2U;
    }

    new_buf = (unsigned char *)realloc(file->data, new_cap);
    if (new_buf == (unsigned char *)0) {
        errno = ENOMEM;
        return -1;
    }

    file->data = new_buf;
    file->cap = new_cap;
    return 0;
}

static int dg_fd_flush_slot(struct dg_mem_fd *file) {
    u64 wrote;

    if (file == (struct dg_mem_fd *)0 || file->used == 0) {
        return 0;
    }

    if (file->writable == 0 || file->dirty == 0) {
        return 0;
    }

    if (dg_is_temp_path(file->path) == 0) {
        errno = EACCES;
        return -1;
    }

    wrote = cleonos_sys_fs_write(file->path, (const char *)file->data, (u64)file->size);
    if (wrote != (u64)file->size) {
        errno = EIO;
        return -1;
    }

    file->dirty = 0;
    return 0;
}

static void dg_serial_log_bytes(const void *buffer, size_t size) {
    const unsigned char *src = (const unsigned char *)buffer;
    static const char prefix[] = DG_SERIAL_LOG_PREFIX;
    char line[DG_SERIAL_LOG_CHUNK + sizeof(prefix) + 1U];
    size_t prefix_len = sizeof(prefix) - 1U;
    size_t offset = 0U;
    int first = 1;

    if (src == (const unsigned char *)0 || size == 0U) {
        return;
    }

    while (offset < size) {
        size_t room = DG_SERIAL_LOG_CHUNK;
        size_t used = 0U;

        if (first != 0 && prefix_len < sizeof(line)) {
            memcpy(line, prefix, prefix_len);
            used = prefix_len;
            first = 0;
        }

        while (offset < size && room > 0U) {
            unsigned char ch = src[offset++];

            if (ch == '\0') {
                ch = ' ';
            } else if (ch < 32U && ch != '\n' && ch != '\r' && ch != '\t') {
                ch = ' ';
            }

            line[used++] = (char)ch;
            room--;
        }

        if (used > 0U) {
            (void)cleonos_sys_log_write(line, (u64)used);
        }
    }
}

void *malloc(size_t size) {
    struct dg_alloc_hdr *cur;
    size_t aligned;

    if (size == 0U) {
        size = 1U;
    }

    aligned = (size + 15U) & ~(size_t)15U;

    if (g_dg_heap_ready == 0) {
        if (DG_HEAP_SIZE <= sizeof(struct dg_alloc_hdr) + 16U) {
            errno = ENOMEM;
            return (void *)0;
        }

        g_dg_heap_head = (struct dg_alloc_hdr *)(void *)g_dg_heap;
        g_dg_heap_head->size = DG_HEAP_SIZE - sizeof(struct dg_alloc_hdr);
        g_dg_heap_head->free = 1;
        g_dg_heap_head->next = (struct dg_alloc_hdr *)0;
        g_dg_heap_head->prev = (struct dg_alloc_hdr *)0;
        g_dg_heap_ready = 1;
    }

    cur = g_dg_heap_head;

    while (cur != (struct dg_alloc_hdr *)0) {
        if (cur->free != 0 && cur->size >= aligned) {
            size_t remain = cur->size - aligned;

            if (remain > sizeof(struct dg_alloc_hdr) + 16U) {
                struct dg_alloc_hdr *tail =
                    (struct dg_alloc_hdr *)(void *)((unsigned char *)(void *)(cur + 1) + aligned);
                tail->size = remain - sizeof(struct dg_alloc_hdr);
                tail->free = 1;
                tail->next = cur->next;
                tail->prev = cur;
                if (cur->next != (struct dg_alloc_hdr *)0) {
                    cur->next->prev = tail;
                }
                cur->next = tail;
                cur->size = aligned;
            }

            cur->free = 0;
            return (void *)(cur + 1);
        }

        cur = cur->next;
    }

    errno = ENOMEM;
    return (void *)0;
}

void free(void *ptr) {
    struct dg_alloc_hdr *hdr;

    if (ptr == (void *)0) {
        return;
    }

    hdr = ((struct dg_alloc_hdr *)ptr) - 1;
    hdr->free = 1;

    if (hdr->next != (struct dg_alloc_hdr *)0 && hdr->next->free != 0) {
        struct dg_alloc_hdr *next = hdr->next;
        hdr->size += sizeof(struct dg_alloc_hdr) + next->size;
        hdr->next = next->next;
        if (next->next != (struct dg_alloc_hdr *)0) {
            next->next->prev = hdr;
        }
    }

    if (hdr->prev != (struct dg_alloc_hdr *)0 && hdr->prev->free != 0) {
        struct dg_alloc_hdr *prev = hdr->prev;
        prev->size += sizeof(struct dg_alloc_hdr) + hdr->size;
        prev->next = hdr->next;
        if (hdr->next != (struct dg_alloc_hdr *)0) {
            hdr->next->prev = prev;
        }
    }
}

void *calloc(size_t nmemb, size_t size) {
    size_t total;
    void *ptr;

    if (nmemb == 0U || size == 0U) {
        total = 1U;
    } else if (nmemb > ((size_t)-1) / size) {
        errno = ENOMEM;
        return (void *)0;
    } else {
        total = nmemb * size;
    }

    ptr = malloc(total);
    if (ptr != (void *)0) {
        memset(ptr, 0, total);
    }

    return ptr;
}

void *realloc(void *ptr, size_t size) {
    struct dg_alloc_hdr *old_hdr;
    size_t old_size;
    void *new_ptr;

    if (ptr == (void *)0) {
        return malloc(size);
    }

    if (size == 0U) {
        free(ptr);
        return (void *)0;
    }

    old_hdr = ((struct dg_alloc_hdr *)ptr) - 1;
    old_size = old_hdr->size;

    if (old_size >= size) {
        return ptr;
    }

    if (old_hdr->next != (struct dg_alloc_hdr *)0 && old_hdr->next->free != 0 &&
        old_size + sizeof(struct dg_alloc_hdr) + old_hdr->next->size >= size) {
        struct dg_alloc_hdr *next = old_hdr->next;
        old_hdr->size += sizeof(struct dg_alloc_hdr) + next->size;
        old_hdr->next = next->next;
        if (old_hdr->next != (struct dg_alloc_hdr *)0) {
            old_hdr->next->prev = old_hdr;
        }
        return ptr;
    }

    new_ptr = malloc(size);
    if (new_ptr == (void *)0) {
        return (void *)0;
    }

    memcpy(new_ptr, ptr, (old_size < size) ? old_size : size);
    free(ptr);
    return new_ptr;
}

double atof(const char *text) {
    const char *p = text;
    double value = 0.0;
    double frac = 0.1;
    int sign = 1;

    if (p == (const char *)0) {
        return 0.0;
    }

    while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
        p++;
    }

    if (*p == '-') {
        sign = -1;
        p++;
    } else if (*p == '+') {
        p++;
    }

    while (*p >= '0' && *p <= '9') {
        value = (value * 10.0) + (double)(*p - '0');
        p++;
    }

    if (*p == '.') {
        p++;
        while (*p >= '0' && *p <= '9') {
            value += (double)(*p - '0') * frac;
            frac *= 0.1;
            p++;
        }
    }

    return (double)sign * value;
}

int strcasecmp(const char *left, const char *right) {
    unsigned char a;
    unsigned char b;

    if (left == right) {
        return 0;
    }

    while (*left != '\0' && *right != '\0') {
        a = (unsigned char)dg_to_lower_ascii((unsigned char)*left);
        b = (unsigned char)dg_to_lower_ascii((unsigned char)*right);
        if (a != b) {
            return (a < b) ? -1 : 1;
        }
        left++;
        right++;
    }

    if (*left == *right) {
        return 0;
    }

    return ((unsigned char)*left < (unsigned char)*right) ? -1 : 1;
}

int strncasecmp(const char *left, const char *right, size_t size) {
    size_t i;

    if (size == 0U || left == right) {
        return 0;
    }

    for (i = 0U; i < size; i++) {
        unsigned char a = (unsigned char)dg_to_lower_ascii((unsigned char)left[i]);
        unsigned char b = (unsigned char)dg_to_lower_ascii((unsigned char)right[i]);

        if (a != b) {
            return (a < b) ? -1 : 1;
        }

        if (left[i] == '\0' || right[i] == '\0') {
            break;
        }
    }

    return 0;
}

char *strdup(const char *text) {
    size_t len;
    char *out;

    len = dg_strlen(text);
    out = (char *)malloc(len + 1U);
    if (out == (char *)0) {
        return (char *)0;
    }

    memcpy(out, text, len + 1U);
    return out;
}

char *getenv(const char *name) {
    static char line[DG_ENV_LINE_MAX];
    static char value[DG_ENV_LINE_MAX];
    u64 envc;
    u64 i;
    size_t nlen;

    if (name[0] == '\0') {
        return (char *)0;
    }

    nlen = dg_strlen(name);
    envc = cleonos_sys_proc_envc();

    for (i = 0ULL; i < envc; i++) {
        if (cleonos_sys_proc_env(i, line, (u64)sizeof(line)) == 0ULL) {
            continue;
        }

        if (strncmp(line, name, nlen) == 0 && line[nlen] == '=') {
            dg_copy(value, sizeof(value), line + nlen + 1U);
            return value;
        }
    }

    return (char *)0;
}

time_t time(time_t *out_time) {
    time_t value = (time_t)(cleonos_sys_timer_ticks() / 100ULL);

    if (out_time != (time_t *)0) {
        *out_time = value;
    }

    return value;
}

int access(const char *path, int mode) {
    char resolved[DG_PATH_MAX];
    (void)mode;

    dg_resolve_read_path(path, resolved, sizeof(resolved));
    if (dg_path_exists(resolved) == 0) {
        errno = ENOENT;
        return -1;
    }

    return 0;
}

int mkdir(const char *path, mode_t mode) {
    char resolved[DG_PATH_MAX];
    (void)mode;

    if (path == (const char *)0) {
        errno = EINVAL;
        return -1;
    }

    dg_resolve_write_path(path, resolved, sizeof(resolved));
    if (dg_is_temp_path(resolved) == 0) {
        errno = EACCES;
        return -1;
    }

    if (cleonos_sys_fs_mkdir(resolved) == 0ULL) {
        errno = EIO;
        return -1;
    }

    return 0;
}

int unlink(const char *path) {
    char resolved[DG_PATH_MAX];

    dg_resolve_write_path(path, resolved, sizeof(resolved));
    if (dg_is_temp_path(resolved) == 0) {
        errno = EACCES;
        return -1;
    }

    if (cleonos_sys_fs_remove(resolved) == 0ULL) {
        errno = EIO;
        return -1;
    }

    return 0;
}

int remove(const char *path) {
    return unlink(path);
}

int rename(const char *old_path, const char *new_path) {
    char src[DG_PATH_MAX];
    char dst[DG_PATH_MAX];
    u64 file_size;
    char *buf;
    u64 read_len;
    int rc = -1;

    if (old_path == (const char *)0 || new_path == (const char *)0) {
        errno = EINVAL;
        return -1;
    }

    dg_resolve_read_path(old_path, src, sizeof(src));
    dg_resolve_write_path(new_path, dst, sizeof(dst));

    if (dg_is_temp_path(dst) == 0) {
        errno = EACCES;
        return -1;
    }

    file_size = cleonos_sys_fs_stat_size(src);
    if (file_size == (u64)-1) {
        errno = ENOENT;
        return -1;
    }

    buf = (char *)malloc((size_t)file_size + 1U);
    if (buf == (char *)0) {
        return -1;
    }

    read_len = cleonos_sys_fs_read(src, buf, file_size);
    if (read_len != file_size) {
        errno = EIO;
        goto done;
    }

    if (cleonos_sys_fs_write(dst, buf, read_len) != read_len) {
        errno = EIO;
        goto done;
    }

    if (cleonos_sys_fs_remove(src) == 0ULL) {
        errno = EIO;
        goto done;
    }

    rc = 0;
done:
    free(buf);
    return rc;
}

int open(const char *path, int flags, ...) {
    int slot;
    int fd;
    struct dg_mem_fd *file;
    int writable;
    char resolved[DG_PATH_MAX];

    writable = ((flags & O_ACCMODE) != O_RDONLY) ? 1 : 0;
    if (writable != 0) {
        dg_resolve_write_path(path, resolved, sizeof(resolved));
        if (dg_is_temp_path(resolved) == 0) {
            errno = EACCES;
            return -1;
        }
    } else {
        dg_resolve_read_path(path, resolved, sizeof(resolved));
    }

    slot = dg_fd_alloc_slot();
    if (slot < 0) {
        errno = EMFILE;
        return -1;
    }

    file = &g_dg_fds[slot];
    file->writable = writable;
    dg_copy(file->path, sizeof(file->path), resolved);

    if (writable == 0 || (flags & O_APPEND) != 0) {
        u64 size = cleonos_sys_fs_stat_size(resolved);

        if (size == (u64)-1) {
            if (writable == 0) {
                file->used = 0;
                errno = ENOENT;
                return -1;
            }
        } else if (size > 0ULL) {
            if (dg_fd_ensure_cap(file, (size_t)size) != 0) {
                free(file->data);
                file->used = 0;
                file->data = (unsigned char *)0;
                file->cap = 0U;
                return -1;
            }

            if (cleonos_sys_fs_read(resolved, (char *)file->data, size) != size) {
                free(file->data);
                file->used = 0;
                file->data = (unsigned char *)0;
                file->cap = 0U;
                errno = EIO;
                return -1;
            }

            file->size = (size_t)size;
            file->pos = ((flags & O_APPEND) != 0) ? file->size : 0U;
        }
    }

    if ((flags & O_TRUNC) != 0) {
        file->size = 0U;
        file->pos = 0U;
        file->dirty = 1;
    }

    fd = slot + 3;
    return fd;
}

int close(int fd) {
    int slot = dg_fd_slot_from_fd(fd);
    struct dg_mem_fd *file;

    if (fd >= 0 && fd <= 2) {
        return 0;
    }

    if (slot < 0) {
        errno = EBADF;
        return -1;
    }

    file = &g_dg_fds[slot];

    if (dg_fd_flush_slot(file) != 0) {
        return -1;
    }

    free(file->data);
    file->used = 0;
    file->writable = 0;
    file->dirty = 0;
    file->pos = 0U;
    file->size = 0U;
    file->cap = 0U;
    file->data = (unsigned char *)0;
    file->path[0] = '\0';
    return 0;
}

ssize_t read(int fd, void *out_buf, size_t size) {
    int slot;
    struct dg_mem_fd *file;
    size_t left;
    size_t n;

    if (out_buf == (void *)0) {
        errno = EINVAL;
        return -1;
    }

    if (size == 0U) {
        return 0;
    }

    if (fd == 0) {
        u64 got = cleonos_sys_fd_read(0ULL, out_buf, (u64)size);
        if (got == (u64)-1) {
            errno = EIO;
            return -1;
        }
        return (ssize_t)got;
    }

    slot = dg_fd_slot_from_fd(fd);
    if (slot < 0) {
        errno = EBADF;
        return -1;
    }

    file = &g_dg_fds[slot];
    left = (file->pos < file->size) ? (file->size - file->pos) : 0U;
    n = (size < left) ? size : left;

    if (n > 0U) {
        memcpy(out_buf, file->data + file->pos, n);
        file->pos += n;
    }

    return (ssize_t)n;
}

ssize_t write(int fd, const void *buf, size_t size) {
    int slot;
    struct dg_mem_fd *file;

    if (buf == (const void *)0) {
        errno = EINVAL;
        return -1;
    }

    if (size == 0U) {
        return 0;
    }

    if (fd == 1 || fd == 2) {
        u64 wrote = cleonos_sys_fd_write((u64)fd, buf, (u64)size);
        if (wrote == (u64)-1) {
            errno = EIO;
            return -1;
        }
        dg_serial_log_bytes(buf, (size_t)wrote);
        return (ssize_t)wrote;
    }

    slot = dg_fd_slot_from_fd(fd);
    if (slot < 0) {
        errno = EBADF;
        return -1;
    }

    file = &g_dg_fds[slot];
    if (file->writable == 0) {
        errno = EBADF;
        return -1;
    }

    if (dg_fd_ensure_cap(file, file->pos + size) != 0) {
        return -1;
    }

    memcpy(file->data + file->pos, buf, size);
    file->pos += size;
    if (file->pos > file->size) {
        file->size = file->pos;
    }
    file->dirty = 1;

    return (ssize_t)size;
}

off_t lseek(int fd, off_t offset, int whence) {
    int slot = dg_fd_slot_from_fd(fd);
    struct dg_mem_fd *file;
    long long base = 0LL;
    long long next;

    if (slot < 0) {
        errno = EBADF;
        return (off_t)-1;
    }

    file = &g_dg_fds[slot];

    if (whence == SEEK_SET) {
        base = 0LL;
    } else if (whence == SEEK_CUR) {
        base = (long long)file->pos;
    } else if (whence == SEEK_END) {
        base = (long long)file->size;
    } else {
        errno = EINVAL;
        return (off_t)-1;
    }

    next = base + (long long)offset;
    if (next < 0LL) {
        errno = EINVAL;
        return (off_t)-1;
    }

    file->pos = (size_t)next;
    return (off_t)file->pos;
}

int isatty(int fd) {
    return (fd >= 0 && fd <= 2) ? 1 : 0;
}

static struct dg_stream *dg_stream_ptr(FILE *stream) {
    struct dg_stream *s = (struct dg_stream *)(void *)stream;

    if (s == (struct dg_stream *)0 || s->magic != DG_STDIO_MAGIC) {
        return (struct dg_stream *)0;
    }

    return s;
}

FILE *dg_fopen(const char *path, const char *mode) {
    int flags = O_RDONLY;
    int fd;
    struct dg_stream *stream;

    if (path == (const char *)0 || mode == (const char *)0 || mode[0] == '\0') {
        errno = EINVAL;
        return (FILE *)0;
    }

    if (mode[0] == 'r') {
        flags = O_RDONLY;
    } else if (mode[0] == 'w') {
        flags = O_WRONLY | O_CREAT | O_TRUNC;
    } else if (mode[0] == 'a') {
        flags = O_WRONLY | O_CREAT | O_APPEND;
    } else {
        errno = EINVAL;
        return (FILE *)0;
    }

    if (strchr(mode, '+') != (char *)0) {
        flags &= ~(O_RDONLY | O_WRONLY);
        flags |= O_RDWR;
    }

    fd = open(path, flags, 0644);
    if (fd < 0) {
        return (FILE *)0;
    }

    stream = (struct dg_stream *)malloc(sizeof(struct dg_stream));
    if (stream == (struct dg_stream *)0) {
        (void)close(fd);
        return (FILE *)0;
    }

    stream->magic = DG_STDIO_MAGIC;
    stream->fd = fd;
    stream->eof_flag = 0;
    stream->err_flag = 0;
    return (FILE *)(void *)stream;
}

int dg_fclose(FILE *stream) {
    struct dg_stream *s = dg_stream_ptr(stream);
    int rc;

    if (s == (struct dg_stream *)0) {
        errno = EINVAL;
        return EOF;
    }

    if (s == &g_dg_stdin_stream || s == &g_dg_stdout_stream || s == &g_dg_stderr_stream) {
        return 0;
    }

    rc = close(s->fd);
    free(s);
    return (rc == 0) ? 0 : EOF;
}

size_t dg_fread(void *out_buf, size_t size, size_t nmemb, FILE *stream) {
    struct dg_stream *s = dg_stream_ptr(stream);
    size_t want;
    ssize_t got;

    if (s == (struct dg_stream *)0 || out_buf == (void *)0 || size == 0U || nmemb == 0U) {
        return 0U;
    }

    if (nmemb > ((size_t)-1) / size) {
        s->err_flag = 1;
        errno = EINVAL;
        return 0U;
    }

    want = size * nmemb;
    got = read(s->fd, out_buf, want);
    if (got < 0) {
        s->err_flag = 1;
        return 0U;
    }

    if ((size_t)got < want) {
        s->eof_flag = 1;
    }

    return (size == 0U) ? 0U : ((size_t)got / size);
}

size_t dg_fwrite(const void *buf, size_t size, size_t nmemb, FILE *stream) {
    struct dg_stream *s = dg_stream_ptr(stream);
    size_t want;
    ssize_t wrote;

    if (s == (struct dg_stream *)0 || buf == (const void *)0 || size == 0U || nmemb == 0U) {
        return 0U;
    }

    if (nmemb > ((size_t)-1) / size) {
        s->err_flag = 1;
        errno = EINVAL;
        return 0U;
    }

    want = size * nmemb;
    wrote = write(s->fd, buf, want);
    if (wrote < 0) {
        s->err_flag = 1;
        return 0U;
    }

    return (size_t)wrote / size;
}

int putc(int ch, FILE *stream) {
    unsigned char out = (unsigned char)(ch & 0xFF);
    return (dg_fwrite(&out, 1U, 1U, stream) == 1U) ? (int)out : EOF;
}

int dg_fseek(FILE *stream, long offset, int whence) {
    struct dg_stream *s = dg_stream_ptr(stream);

    if (s == (struct dg_stream *)0) {
        errno = EINVAL;
        return -1;
    }

    if (lseek(s->fd, (off_t)offset, whence) < 0) {
        s->err_flag = 1;
        return -1;
    }

    s->eof_flag = 0;
    return 0;
}

long dg_ftell(FILE *stream) {
    struct dg_stream *s = dg_stream_ptr(stream);
    off_t pos;

    if (s == (struct dg_stream *)0) {
        errno = EINVAL;
        return -1L;
    }

    pos = lseek(s->fd, 0, SEEK_CUR);
    if (pos < (off_t)0) {
        s->err_flag = 1;
        return -1L;
    }

    return (long)pos;
}

int dg_fflush(FILE *stream) {
    struct dg_stream *s;
    int slot;

    if (stream == (FILE *)0) {
        return 0;
    }

    s = dg_stream_ptr(stream);
    if (s == (struct dg_stream *)0) {
        errno = EINVAL;
        return EOF;
    }

    slot = dg_fd_slot_from_fd(s->fd);
    if (slot >= 0) {
        if (dg_fd_flush_slot(&g_dg_fds[slot]) != 0) {
            s->err_flag = 1;
            return EOF;
        }
    }

    return 0;
}

char *dg_fgets(char *out_text, int size, FILE *stream) {
    struct dg_stream *s = dg_stream_ptr(stream);
    int i = 0;
    char ch = '\0';

    if (s == (struct dg_stream *)0 || out_text == (char *)0 || size <= 1) {
        return (char *)0;
    }

    while (i + 1 < size) {
        ssize_t got = read(s->fd, &ch, 1U);

        if (got <= 0) {
            if (got < 0) {
                s->err_flag = 1;
            } else {
                s->eof_flag = 1;
            }
            break;
        }

        out_text[i++] = ch;
        if (ch == '\n') {
            break;
        }
    }

    if (i == 0) {
        return (char *)0;
    }

    out_text[i] = '\0';
    return out_text;
}

int dg_feof(FILE *stream) {
    struct dg_stream *s = dg_stream_ptr(stream);
    return (s == (struct dg_stream *)0) ? 1 : s->eof_flag;
}

int dg_fileno(FILE *stream) {
    struct dg_stream *s = dg_stream_ptr(stream);

    if (s == (struct dg_stream *)0) {
        errno = EINVAL;
        return -1;
    }

    return s->fd;
}

int dg_vfprintf(FILE *stream, const char *fmt, va_list args) {
    struct dg_stream *s = dg_stream_ptr(stream);
    int len;
    char local[512];
    va_list args_copy;

    if (s == (struct dg_stream *)0 || fmt == (const char *)0) {
        errno = EINVAL;
        return -1;
    }

    va_copy(args_copy, args);
    len = vsnprintf(local, sizeof(local), fmt, args_copy);
    va_end(args_copy);
    if (len < 0) {
        s->err_flag = 1;
        return -1;
    }

    if ((size_t)len < sizeof(local)) {
        if (write(s->fd, local, (size_t)len) < 0) {
            s->err_flag = 1;
            return -1;
        }
        return len;
    }

    {
        size_t cap = (size_t)len + 1U;
        char *dyn = (char *)malloc(cap);
        int written_len;

        if (dyn == (char *)0) {
            s->err_flag = 1;
            return -1;
        }

        va_copy(args_copy, args);
        written_len = vsnprintf(dyn, cap, fmt, args_copy);
        va_end(args_copy);

        if (written_len < 0 || write(s->fd, dyn, (size_t)written_len) < 0) {
            s->err_flag = 1;
            return -1;
        }

        return written_len;
    }
}

int dg_fprintf(FILE *stream, const char *fmt, ...) {
    va_list args;
    int rc;

    va_start(args, fmt);
    rc = dg_vfprintf(stream, fmt, args);
    va_end(args);
    return rc;
}

void dg_perror(const char *text) {
    if (text != (const char *)0 && text[0] != '\0') {
        (void)dg_fprintf(stderr, "%s: errno=%d\n", text, errno);
    } else {
        (void)dg_fprintf(stderr, "errno=%d\n", errno);
    }
}

int dg_sscanf(const char *text, const char *fmt, ...) {
    const char *p = text;
    const char *f = fmt;
    int assigned = 0;
    va_list args;

    if (text == (const char *)0 || fmt == (const char *)0) {
        return 0;
    }

    va_start(args, fmt);

    while (*f != '\0') {
        if (*f != '%') {
            if (dg_is_space_char((unsigned char)*f) != 0) {
                while (dg_is_space_char((unsigned char)*p) != 0) {
                    p++;
                }
                f++;
                continue;
            }

            if (*p != *f) {
                break;
            }

            p++;
            f++;
            continue;
        }

        f++;
        if (*f == '\0') {
            break;
        }

        while (dg_is_space_char((unsigned char)*p) != 0) {
            p++;
        }

        if (*f == 'd' || *f == 'i') {
            int *out_i = va_arg(args, int *);
            char *endp = (char *)p;
            long v = strtol(p, &endp, (*f == 'i') ? 0 : 10);
            if (endp == p) {
                break;
            }
            if (out_i != (int *)0) {
                *out_i = (int)v;
            }
            p = endp;
            assigned++;
        } else if (*f == 'u' || *f == 'x' || *f == 'o') {
            unsigned int *out_u = va_arg(args, unsigned int *);
            int base = (*f == 'x') ? 16 : ((*f == 'o') ? 8 : 10);
            char *endp = (char *)p;
            unsigned long v = strtoul(p, &endp, base);
            if (endp == p) {
                break;
            }
            if (out_u != (unsigned int *)0) {
                *out_u = (unsigned int)v;
            }
            p = endp;
            assigned++;
        } else if (*f == 'f') {
            float *out_f = va_arg(args, float *);
            char *endp = (char *)p;
            double v = atof(p);
            while (*endp == '+' || *endp == '-' || *endp == '.' || (*endp >= '0' && *endp <= '9')) {
                endp++;
            }
            if (endp == p) {
                break;
            }
            if (out_f != (float *)0) {
                *out_f = (float)v;
            }
            p = endp;
            assigned++;
        } else if (*f == 's') {
            char *out_s = va_arg(args, char *);
            int copied = 0;
            if (out_s == (char *)0) {
                break;
            }
            while (*p != '\0' && dg_is_space_char((unsigned char)*p) == 0) {
                out_s[copied++] = *p++;
            }
            if (copied == 0) {
                break;
            }
            out_s[copied] = '\0';
            assigned++;
        } else if (*f == 'c') {
            char *out_c = va_arg(args, char *);
            if (*p == '\0' || out_c == (char *)0) {
                break;
            }
            *out_c = *p++;
            assigned++;
        } else {
            break;
        }

        f++;
    }

    va_end(args);
    return assigned;
}
