///                MentOS, The Mentoring Operating system project
/// @file syscall.c
/// @brief System Call management functions.
/// @copyright (c) 2014-2021 This file is distributed under the MIT License.
/// See LICENSE.md for details.

#include "devices/fpu.h"
#include "mem/kheap.h"
#include "system/syscall.h"
#include "descriptor_tables/isr.h"
#include "sys/errno.h"
#include "kernel.h"
#include "process/process.h"
#include "process/scheduler.h"
#include "sys/utsname.h"
#include "fs/ioctl.h"
#include "hardware/timer.h"

#include "ipc/msg.h"
#include "ipc/sem.h"
#include "ipc/shm.h"

/// The signature of a function call.
typedef int (*SystemCall)();

/// The list of function call.
SystemCall sys_call_table[SYSCALL_NUMBER];

/// Last interupt stack frame
static pt_regs *current_interrupt_stack_frame;

/// @brief A Not Implemented (NI) system-call.
/// @return Always returns -ENOSYS.
/// @details
/// Linux provides a "not implemented" system call, sys_ni_syscall(), which does
/// nothing except return ENOSYS, the error corresponding to an invalid
/// system call. This function is used to "plug the hole" in the rare event that
/// a syscall is removed or otherwise made unavailable.
static inline int sys_ni_syscall()
{
    return -ENOSYS;
}

void syscall_init()
{
    // Initialize the list of system calls.
    for (uint32_t it = 0; it < SYSCALL_NUMBER; ++it) {
        sys_call_table[it] = sys_ni_syscall;
    }

    sys_call_table[__NR_exit]           = (SystemCall)sys_exit;
    sys_call_table[__NR_read]           = (SystemCall)sys_read;
    sys_call_table[__NR_write]          = (SystemCall)sys_write;
    sys_call_table[__NR_open]           = (SystemCall)sys_open;
    sys_call_table[__NR_close]          = (SystemCall)sys_close;
    sys_call_table[__NR_stat]           = (SystemCall)sys_stat;
    sys_call_table[__NR_fstat]          = (SystemCall)sys_fstat;
    sys_call_table[__NR_mkdir]          = (SystemCall)sys_mkdir;
    sys_call_table[__NR_rmdir]          = (SystemCall)sys_rmdir;
    sys_call_table[__NR_unlink]         = (SystemCall)sys_unlink;
    sys_call_table[__NR_getdents]       = (SystemCall)sys_getdents;
    sys_call_table[__NR_lseek]          = (SystemCall)sys_lseek;
    sys_call_table[__NR_getpid]         = (SystemCall)sys_getpid;
    sys_call_table[__NR_getsid]         = (SystemCall)sys_getsid;
    sys_call_table[__NR_setsid]         = (SystemCall)sys_setsid;
    sys_call_table[__NR_getgid]         =(SystemCall)sys_getgid;
    sys_call_table[__NR_setgid]         =(SystemCall)sys_setgid;
    sys_call_table[__NR_getppid]        = (SystemCall)sys_getppid;
    sys_call_table[__NR_sigaction]      = (SystemCall)sys_sigaction;
    sys_call_table[__NR_fork]           = (SystemCall)sys_fork;
    sys_call_table[__NR_execve]         = (SystemCall)sys_execve;
    sys_call_table[__NR_nice]           = (SystemCall)sys_nice;
    sys_call_table[__NR_kill]           = (SystemCall)sys_kill;
    sys_call_table[__NR_reboot]         = (SystemCall)sys_reboot;
    sys_call_table[__NR_uname]          = (SystemCall)sys_uname;
    sys_call_table[__NR_sigreturn]      = (SystemCall)sys_sigreturn;
    sys_call_table[__NR_waitpid]        = (SystemCall)sys_waitpid;
    sys_call_table[__NR_chdir]          = (SystemCall)sys_chdir;
    sys_call_table[__NR_fchdir]         = (SystemCall)sys_fchdir;
    sys_call_table[__NR_time]           = (SystemCall)sys_time;
    sys_call_table[__NR_sigprocmask]    = (SystemCall)sys_sigprocmask;
    sys_call_table[__NR_brk]            = (SystemCall)sys_brk;
    sys_call_table[__NR_signal]         = (SystemCall)sys_signal;
    sys_call_table[__NR_ioctl]          = (SystemCall)sys_ioctl;
    sys_call_table[__NR_sched_setparam] = (SystemCall)sys_sched_setparam;
    sys_call_table[__NR_sched_getparam] = (SystemCall)sys_sched_getparam;
    sys_call_table[__NR_nanosleep]      = (SystemCall)sys_nanosleep;
    sys_call_table[__NR_getcwd]         = (SystemCall)sys_getcwd;
    sys_call_table[__NR_waitperiod]     = (SystemCall)sys_waitperiod;
    sys_call_table[__NR_msgctl]         = (SystemCall)sys_msgctl;
    sys_call_table[__NR_msgget]         = (SystemCall)sys_msgget;
    sys_call_table[__NR_msgrcv]         = (SystemCall)sys_msgrcv;
    sys_call_table[__NR_msgsnd]         = (SystemCall)sys_msgsnd;
    sys_call_table[__NR_semctl]         = (SystemCall)sys_semctl;
    sys_call_table[__NR_semget]         = (SystemCall)sys_semget;
    sys_call_table[__NR_semop]          = (SystemCall)sys_semop;
    sys_call_table[__NR_shmat]          = (SystemCall)sys_shmat;
    sys_call_table[__NR_shmctl]         = (SystemCall)sys_shmctl;
    sys_call_table[__NR_shmdt]          = (SystemCall)sys_shmdt;
    sys_call_table[__NR_shmget]         = (SystemCall)sys_shmget;
    sys_call_table[__NR_alarm]          = (SystemCall)sys_alarm;
    sys_call_table[__NR_setitimer]      = (SystemCall)sys_setitimer;
    sys_call_table[__NR_getitimer]      = (SystemCall)sys_getitimer;

    isr_install_handler(SYSTEM_CALL, &syscall_handler, "syscall_handler");
}

pt_regs *get_current_interrupt_stack_frame()
{
    return current_interrupt_stack_frame;
}

void syscall_handler(pt_regs *f)
{
    // Saves current interrupt stack frame
    current_interrupt_stack_frame = f;

    // dbg_print_regs(f);
    // Save current process fpu state.
    switch_fpu();

    // The index of the requested system call.
    uint32_t sc_index = f->eax;

    // The result of the system call.
    int ret;
    if (sc_index >= SYSCALL_NUMBER) {
        ret = ENOSYS;
    } else {
        uintptr_t ptr = (uintptr_t)sys_call_table[sc_index];

        SystemCall func = (SystemCall)ptr;

        uint32_t arg0 = f->ebx;
        uint32_t arg1 = f->ecx;
        uint32_t arg2 = f->edx;
        uint32_t arg3 = f->esi;
        uint32_t arg4 = f->edi;
        if ((sc_index == __NR_fork) ||
            (sc_index == __NR_clone) ||
            (sc_index == __NR_execve) ||
            (sc_index == __NR_sigreturn)) {
            arg0 = (uintptr_t)f;
        }
        ret = func(arg0, arg1, arg2, arg3, arg4);
    }
    f->eax = ret;

    // Schedule next process.
    scheduler_run(f);
    // Restore fpu state.
    unswitch_fpu();
}
