#include "bits/syscall.h"
#include "bits/global.h"
#include <errno.h>

#define ASM_REGISTER(name) \
        register uint64_t name asm (#name)

#define ASM_SYSCALL0(r0)                    ({ \
        ASM_REGISTER(rax) = (uint64_t) (r0); \
        asm volatile ("syscall":::"memory"); \
        rax; \
    })

#define ASM_SYSCALL1(r0, r1)                ({ \
        ASM_REGISTER(rdi) = (uint64_t) (r1); \
        /*
         * Should be the last one because memory accesses for arguments
         * fuck up %rax
         */ \
        ASM_REGISTER(rax) = (uint64_t) (r0); \
        asm volatile ("syscall":::"memory"); \
        rax; \
    })

#define ASM_SYSCALL2(r0, r1, r2)        ({ \
        ASM_REGISTER(rdi) = (uint64_t) (r1); \
        ASM_REGISTER(rsi) = (uint64_t) (r2); \
        /*
         * Should be the last one because memory accesses for arguments
         * fuck up %rax
         */ \
        ASM_REGISTER(rax) = (uint64_t) (r0); \
        asm volatile ("syscall":::"memory", "rax", "rdi", "rsi", "rdx"); \
        rax; \
    })

#define ASM_SYSCALL3(r0, r1, r2, r3)        ({ \
        ASM_REGISTER(rdi) = (uint64_t) (r1); \
        ASM_REGISTER(rsi) = (uint64_t) (r2); \
        ASM_REGISTER(rdx) = (uint64_t) (r3); \
        /*
         * Should be the last one because memory accesses for arguments
         * fuck up %rax
         */ \
        ASM_REGISTER(rax) = (uint64_t) (r0); \
        asm volatile ("syscall":::"memory", "rax", "rdi", "rsi", "rdx"); \
        rax; \
    })

#define SET_ERRNO(t, r)                     ({ \
        t res = (t) r; \
        if (res < 0) { \
            errno = -res; \
        } \
        ((int) res) < 0 ? (t) -1 : res; \
    })

ssize_t write(int fd, const void *buf, size_t count) {
    return SET_ERRNO(ssize_t, ASM_SYSCALL3(SYSCALL_NR_WRITE, fd, buf, count));
}

ssize_t read(int fd, void *buf, size_t count) {
    return SET_ERRNO(ssize_t, ASM_SYSCALL3(SYSCALL_NR_READ, fd, buf, count));
}

ssize_t sys_readdir(int fd, struct dirent *entp) {
    return SET_ERRNO(ssize_t, ASM_SYSCALL2(SYSCALL_NR_READDIR, fd, entp));
}

int open(const char *filename, int flags, int mode) {
    return SET_ERRNO(int, ASM_SYSCALL3(SYSCALL_NR_OPEN, filename, flags, mode));
}

void close(int fd) {
    (void) ASM_SYSCALL1(SYSCALL_NR_CLOSE, fd);
}

int stat(const char *filename, struct stat *st) {
    return SET_ERRNO(int, ASM_SYSCALL2(SYSCALL_NR_STAT, filename, st));
}

int brk(void *addr) {
    return SET_ERRNO(int, ASM_SYSCALL1(SYSCALL_NR_BRK, addr));
}

int fork(void) {
    return SET_ERRNO(int, ASM_SYSCALL0(SYSCALL_NR_FORK));
}

int execve(const char *filename, const char *const argv[], const char *const envp[]) {
    return SET_ERRNO(int, ASM_SYSCALL3(SYSCALL_NR_EXECVE, filename, argv, envp));
}

__attribute__((noreturn)) void exit(int code) {
    (void) ASM_SYSCALL1(SYSCALL_NR_EXIT, code);
    while (1);
}

int kill(int pid, int signum) {
    return SET_ERRNO(int, ASM_SYSCALL2(SYSCALL_NR_KILL, pid, signum));
}

void __kernel_signal(uintptr_t entry) {
    (void) ASM_SYSCALL1(SYSCALL_NRX_SIGNAL, entry);
}

__attribute__((noreturn)) void __kernel_sigret(void) {
    (void) ASM_SYSCALL0(SYSCALL_NRX_SIGRET);
    while (1);
}

// Although sbrk() is implemented in userspace, I guess it should also be here
void *sbrk(intptr_t inc) {
    if (inc == 0) {
        return __cur_brk;
    }

    void *new_brk = (void *) ((uintptr_t) __cur_brk + inc);
    printf("New brk = %p\n", new_brk);

    if (brk(new_brk) != 0) {
        return __cur_brk;
    } else {
        return __cur_brk = new_brk;
    }
}
