/*
Copyright (C) 2015-2019 The University of Notre Dame
This software is distributed under the GNU General Public License.
See the file LICENSE for details.
*/

#include "kernel/syscall.h"
#include "kernel/gfxstream.h"
#include "syscall_handler.h"
#include "console.h"
#include "keyboard.h"
#include "process.h"
#include "kmalloc.h"
#include "kobject.h"
#include "cdromfs.h"
#include "string.h"
#include "memorylayout.h"
#include "main.h"
#include "fs.h"
#include "kobject.h"
#include "pagetable.h"
#include "clock.h"
#include "rtc.h"
#include "elf.h"
#include "kmalloc.h"
#include "page.h"
#include "ata.h"
#include "window.h"
#include "is_valid.h"
#include "bcache.h"

/*
syscall_handler() is responsible for decoding system calls
as they arrive, converting raw integers into the appropriate
types, depending on the system call number.  Then, each
individual handler routine checks the validity of each
argument (fd in range, valid path, etc) and invokes the
underlying system within the kernel.  Ideally, each of these
handler functions should be short (only a few lines)
and simply make use of functionality within other kernel modules.

sys_run/fork/exec are notable exceptions and could benefit
from simplification.
*/

/*
Here follow the handlers for each individual system call
For all of these system calls, a return value of zero or
greater indiciates success, and return of less than zero
indicates an error and the reason.
*/

int sys_debug(const char *str)
{
	if(!is_valid_string(str)) return KERROR_INVALID_ADDRESS;
	printf("%s", str);
	return 0;
}

int sys_process_yield()
{
	process_yield();
	return 0;
}

int sys_process_exit(int status)
{
	process_exit(status);
	return 0;
}

/* Helper routines to duplicate/free an argv array locally */

static char **argv_copy(int argc, const char **argv)
{
	char **pp;

	pp = kmalloc(sizeof(char *) * argc);
	int i;
	for(i = 0; i < argc; i++) {
		pp[i] = strdup(argv[i]);
	}

	return pp;
}

static void argv_delete(int argc, char **argv)
{
	int i;
	for(i = 0; i < argc; i++) {
		kfree(argv[i]);
	}
	kfree(argv);
}

/*
process_run() creates a child process in a more efficient
way than fork/exec by creating the child without duplicating
the memory state, then loading
*/

int sys_process_run( int fd, int argc, const char **argv)
{
	if(!is_valid_object_type(fd,KOBJECT_FILE)) return KERROR_INVALID_OBJECT;

	struct kobject *k = current->ktable[fd];

	/* Copy argv into kernel memory. */
	char **copy_argv = argv_copy(argc, argv);

	/* Create the child process */
	struct process *p = process_create();
	process_inherit(current, p);

	/* SWITCH TO ADDRESS SPACE OF CHILD PROCESS */
	struct pagetable *old_pagetable = current->pagetable;
	current->pagetable = p->pagetable;
	pagetable_load(p->pagetable);

	/* Attempt to load the program image. */
	addr_t entry;
	int r = elf_load(p, k->data.file, &entry);
	if(r >= 0) {
		/* If load succeeded, reset stack and pass arguments */
		process_stack_reset(p, PAGE_SIZE);
		process_kstack_reset(p, entry);
		process_pass_arguments(p, argc, copy_argv);
	}

	/* SWITCH BACK TO ADDRESS SPACE OF PARENT PROCESS */
	current->pagetable = old_pagetable;
	pagetable_load(old_pagetable);

	/* Delete the argument and path copies. */
	argv_delete(argc, copy_argv);

	/* If any error happened, return in the context of the parent */
	if(r < 0) {
		if(r == KERROR_EXECUTION_FAILED) {
			process_delete(p);
		}
		return r;
	}

	/* Otherwise, launch the new child process. */
	process_launch(p);
	return p->pid;
}

/* Function creates a child process with the standard window replaced by wd */
int sys_process_wrun( int fd, int argc, const char **argv, int *fds, int fd_len)
{
	if(!is_valid_object_type(fd,KOBJECT_FILE)) return KERROR_INVALID_OBJECT;
	struct kobject *k = current->ktable[fd];

	/* Copy argv array into kernel memory. */
	char **copy_argv = argv_copy(argc, argv);

	/* Create the child process */
	struct process *p = process_create();

	process_selective_inherit(current, p, fds, fd_len);

	/* SWITCH TO ADDRESS SPACE OF CHILD PROCESS */
	struct pagetable *old_pagetable = current->pagetable;
	current->pagetable = p->pagetable;
	pagetable_load(p->pagetable);

	/* Attempt to load the program image. */
	addr_t entry;
	int r = elf_load(p, k->data.file, &entry);
	if(r >= 0) {
		/* If load succeeded, reset stack and pass arguments */
		process_stack_reset(p, PAGE_SIZE);
		process_kstack_reset(p, entry);
		process_pass_arguments(p, argc, copy_argv);
	}

	/* SWITCH BACK TO ADDRESS SPACE OF PARENT PROCESS */
	current->pagetable = old_pagetable;
	pagetable_load(old_pagetable);

	/* Delete the argument copy. */
	argv_delete(argc, copy_argv);

	/* If any error happened, return in the context of the parent */
	if(r < 0) {
		if(r == KERROR_EXECUTION_FAILED) {
			process_delete(p);
		}
		return r;
	}

	/* Otherwise, launch the new child process. */
	process_launch(p);
	return p->pid;
}

int sys_process_exec( int fd, int argc, const char **argv)
{
	if(!is_valid_object_type(fd,KOBJECT_FILE)) return KERROR_INVALID_OBJECT;
	struct kobject *k = current->ktable[fd];

	addr_t entry;

	/* Duplicate the arguments into kernel space */
	char **copy_argv = argv_copy(argc, argv);

	/* Attempt to load the program image into this process. */
	int r = elf_load(current, k->data.file, &entry);

	/* On failure, return only if our address space is not corrupted. */
	if(r < 0) {
		if(r == KERROR_EXECUTION_FAILED) {
			process_kill(current->pid);
		}
		argv_delete(argc, copy_argv);
		return r;
	}

	/* Reset the stack and pass in the program arguments */
	process_stack_reset(current, PAGE_SIZE);
	process_kstack_reset(current, entry);
	process_pass_arguments(current, argc, copy_argv);

	/* Delete the local copy of the arguments. */
	argv_delete(argc, copy_argv);

	/*
	   IMPORTANT: Following a successful exec, we cannot return via
	   the normal path, because our stack has been reset to that
	   of a fresh process.  We must switch in order to jump
	   to the new stack properly.
	 */

	process_yield();

	/* NOTREACHED */
	return 0;
}

int sys_process_fork()
{
	struct process *p = process_create();
	p->ppid = current->pid;
	pagetable_delete(p->pagetable);
	p->pagetable = pagetable_duplicate(current->pagetable);
	process_inherit(current, p);
	process_kstack_copy(current, p);
	process_launch(p);
	return p->pid;
}

int sys_process_self()
{
	return current->pid;
}

int sys_process_parent()
{
	return current->ppid;
}

int sys_process_kill(int pid)
{
	return process_kill(pid);
}

int sys_process_reap(int pid)
{
	return process_reap(pid);
}

int sys_process_wait(struct process_info *info, int timeout)
{
	if(!is_valid_pointer(info,sizeof(*info))) return KERROR_INVALID_ADDRESS;
	return process_wait_child(0, info, timeout);
}

int sys_process_sleep(unsigned int ms)
{
	clock_wait(ms);
	return 0;
}

int sys_process_stats(struct process_stats *s, int pid)
{
	if(!is_valid_pointer(s,sizeof(*s))) return KERROR_INVALID_ADDRESS;
	return process_stats(pid, s);
}

int sys_process_heap(int delta)
{
	process_data_size_set(current, current->vm_data_size + delta);
	return PROCESS_ENTRY_POINT + current->vm_data_size;
}

int sys_object_list( int fd, char *buffer, int length)
{
	if(!is_valid_object(fd)) return KERROR_INVALID_OBJECT;
	if(!is_valid_pointer(buffer,length)) return KERROR_INVALID_ADDRESS;
	if(kobject_get_type(current->ktable[fd])!=KOBJECT_DIR) return KERROR_NOT_A_DIRECTORY;
	return kobject_list(current->ktable[fd],buffer,length);
}

int sys_open_file( int fd, const char *path, int mode, kernel_flags_t flags)
{
	if(!is_valid_object(fd)) return KERROR_INVALID_OBJECT;
	if(!is_valid_path(path)) return KERROR_INVALID_PATH;

	int newfd = process_available_fd(current);
	if(newfd<0) return KERROR_OUT_OF_OBJECTS;

	struct kobject *newobj;
	int result = kobject_lookup(current->ktable[fd],path,&newobj);

	if(result>=0) {
		current->ktable[newfd] = newobj;
		return newfd;
	} else {
		return result;
	}
}

int sys_open_dir( int fd, const char *path, kernel_flags_t flags )
{
	if(!is_valid_object(fd)) return KERROR_INVALID_OBJECT;
	if(!is_valid_path(path)) return KERROR_INVALID_PATH;

       	int newfd = process_available_fd(current);
	if(newfd<0) return KERROR_OUT_OF_OBJECTS;

	struct kobject *newobj;
	int result;

	if(flags&KERNEL_FLAGS_CREATE) {
		newobj = kobject_create_dir_from_dir( current->ktable[fd], path );
		if(newobj) {
			result = 0;
		} else {
			result = KERROR_NOT_FOUND;
		}
	} else {
		result = kobject_lookup( current->ktable[fd], path, &newobj );
	}

	if(result>=0) {
		current->ktable[newfd] = newobj;
		return newfd;
	} else {
		return result;
	}
}

int sys_open_console(int wd)
{
	if(!is_valid_object_type(wd,KOBJECT_WINDOW)) return KERROR_INVALID_OBJECT;

	int fd = process_available_fd(current);
	if(fd<0) return KERROR_OUT_OF_OBJECTS;

	current->ktable[fd] = kobject_create_console_from_window(current->ktable[wd]);
	return fd;
}


int sys_open_window(int wd, int x, int y, int w, int h)
{
	if(!is_valid_object_type(wd,KOBJECT_WINDOW)) return KERROR_INVALID_OBJECT;

	struct kobject *k = current->ktable[wd];

	int fd = process_available_fd(current);
	if(fd<0) return KERROR_OUT_OF_OBJECTS;

	k = kobject_create_window_from_window(k,x,y,w,h);
	if(!k) {
		// XXX choose better errno
		return KERROR_INVALID_REQUEST;
	}

	current->ktable[fd] = k;

	return fd;
}

int sys_open_pipe()
{
	int fd = process_available_fd(current);
	if(fd < 0) {
		return KERROR_NOT_FOUND;
	}
	struct pipe *p = pipe_create();
	if(!p) {
		return KERROR_NOT_FOUND;
	}
	current->ktable[fd] = kobject_create_pipe(p);
	return fd;
}

int sys_object_type(int fd)
{
	if(!is_valid_object(fd)) return KERROR_INVALID_OBJECT;

	int fd_type = kobject_get_type(current->ktable[fd]);
	if(!fd_type)
		return 0;
	return fd_type;
}

int sys_object_copy( int src, int dst )
{
	if(!is_valid_object(src)) return KERROR_INVALID_OBJECT;
	if(dst>PROCESS_MAX_OBJECTS) return KERROR_INVALID_OBJECT;

	if(dst < 0) {
		dst = process_available_fd(current);
		if(dst<0) return KERROR_NOT_FOUND;
	}

	sys_object_close(dst);

	current->ktable[dst] = kobject_copy(current->ktable[src]);

	return src;
}

int sys_object_read(int fd, void *data, int length, kernel_io_flags_t flags )
{
	if(!is_valid_object(fd)) return KERROR_INVALID_OBJECT;
	if(!is_valid_pointer(data,length)) return KERROR_INVALID_ADDRESS;

	struct kobject *p = current->ktable[fd];
	return kobject_read(p, data, length, flags);
}

int sys_object_write(int fd, void *data, int length, kernel_io_flags_t flags )
{
	if(!is_valid_object(fd)) return KERROR_INVALID_OBJECT;
	if(!is_valid_pointer(data,length)) return KERROR_INVALID_ADDRESS;

	struct kobject *p = current->ktable[fd];
	return kobject_write(p, data, length, flags);
}

int sys_object_seek(int fd, int offset, int whence)
{
	if(!is_valid_object(fd)) return KERROR_INVALID_OBJECT;

	// XXX add kobject method here
	return KERROR_NOT_IMPLEMENTED;
}

int sys_object_remove( int fd, const char *name )
{
	if(!is_valid_object(fd)) return KERROR_INVALID_OBJECT;
	if(!is_valid_path(name)) return KERROR_INVALID_PATH;
	return kobject_remove( current->ktable[fd], name );
}

int sys_object_close(int fd)
{
	if(!is_valid_object(fd)) return KERROR_INVALID_OBJECT;

	struct kobject *p = current->ktable[fd];
	kobject_close(p);
	current->ktable[fd] = 0;
	return 0;
}

int sys_object_set_tag(int fd, char *tag)
{
	if(!is_valid_object(fd)) return KERROR_INVALID_OBJECT;
	kobject_set_tag(current->ktable[fd], tag);
	return 0;
}

int sys_object_get_tag(int fd, char *buffer, int buffer_size)
{
	if(!is_valid_object(fd)) return KERROR_INVALID_OBJECT;
	return kobject_get_tag(current->ktable[fd], buffer, buffer_size);
}

int sys_object_size(int fd, int *dims, int n)
{
	if(!is_valid_object(fd)) return KERROR_INVALID_OBJECT;
	if(!is_valid_pointer(dims,sizeof(*dims)*n)) return KERROR_INVALID_ADDRESS;

	struct kobject *p = current->ktable[fd];
	return kobject_size(p, dims, n);
}

int sys_object_max()
{
	int max_fd = process_object_max(current);
	return max_fd;
}

int sys_system_stats(struct system_stats *s)
{
	if(!is_valid_pointer(s,sizeof(*s))) return KERROR_INVALID_ADDRESS;

	struct rtc_time t = { 0 };
	rtc_read(&t);
	s->time = rtc_time_to_timestamp(&t) - boottime;

	struct ata_count a = ata_stats();
	for(int i = 0; i < 4; i++) {
		s->blocks_written[i] = a.blocks_written[i];
		s->blocks_read[i] = a.blocks_read[i];
	}

	return 0;
}

int sys_bcache_stats(struct bcache_stats * s)
{
	if(!is_valid_pointer(s,sizeof(*s))) return KERROR_INVALID_ADDRESS;
	bcache_get_stats( s );
	return 0;
}

int sys_bcache_flush()
{
	bcache_flush_all();
	return 0;
}

int sys_system_time( uint32_t *tm )
{
	if(!is_valid_pointer(tm,sizeof(*tm))) return KERROR_INVALID_ADDRESS;
	struct rtc_time t;
	rtc_read(&t);
	*tm = rtc_time_to_timestamp(&t);
	return 0;
}

int sys_system_rtc( struct rtc_time *t )
{
	if(!is_valid_pointer(t,sizeof(*t))) return KERROR_INVALID_ADDRESS;
	rtc_read(t);
	return 0;
}

int sys_device_driver_stats(const char * name, struct device_driver_stats * stats)
{
	if(!is_valid_string(name)) return KERROR_INVALID_ADDRESS;
	if(!is_valid_pointer(stats,sizeof(*stats))) return KERROR_INVALID_ADDRESS;

	device_driver_get_stats(name, stats);
	return 0;
}

#ifdef KERNEL_SYSCALL_EXT
  #include "kernel_syscall_ext.c"  
#endif

int32_t syscall_handler(syscall_t n, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e)
{
	if((n < MAX_SYSCALL) && current) {
		current->stats.syscall_count[n]++;
	}
	switch (n) {
	case SYSCALL_DEBUG:
		return sys_debug((const char *) a);
	case SYSCALL_PROCESS_YIELD:
		return sys_process_yield();
	case SYSCALL_PROCESS_EXIT:
		return sys_process_exit(a);
	case SYSCALL_PROCESS_RUN:
		return sys_process_run(a, b, (const char **)c);
	case SYSCALL_PROCESS_WRUN:
		return sys_process_wrun(a, b, (const char **) c, (int *) d, e);
	case SYSCALL_PROCESS_FORK:
		return sys_process_fork();
	case SYSCALL_PROCESS_EXEC:
		return sys_process_exec(a, b, (const char **) c);
	case SYSCALL_PROCESS_SELF:
		return sys_process_self();
	case SYSCALL_PROCESS_PARENT:
		return sys_process_parent();
	case SYSCALL_PROCESS_KILL:
		return sys_process_kill(a);
	case SYSCALL_PROCESS_REAP:
		return sys_process_reap(a);
	case SYSCALL_PROCESS_WAIT:
		return sys_process_wait((struct process_info *) a, b);
	case SYSCALL_PROCESS_SLEEP:
		return sys_process_sleep(a);
	case SYSCALL_PROCESS_STATS:
		return sys_process_stats((struct process_stats *) a, b);
	case SYSCALL_PROCESS_HEAP:
		return sys_process_heap(a);
	case SYSCALL_OPEN_FILE:
		return sys_open_file(a, (const char *)b, c, d);
	case SYSCALL_OPEN_DIR:
		return sys_open_dir(a, (const char *)b, c );
	case SYSCALL_OPEN_WINDOW:
		return sys_open_window(a, b, c, d, e);
	case SYSCALL_OPEN_CONSOLE:
		return sys_open_console(a);
	case SYSCALL_OPEN_PIPE:
		return sys_open_pipe();
	case SYSCALL_OBJECT_TYPE:
		return sys_object_type(a);
	case SYSCALL_OBJECT_COPY:
		return sys_object_copy(a,b);
	case SYSCALL_OBJECT_READ:
		return sys_object_read(a, (void *) b, c, d );
	case SYSCALL_OBJECT_LIST:
		return sys_object_list(a, (char *) b, (int) c);
	case SYSCALL_OBJECT_WRITE:
		return sys_object_write(a, (void *) b, c, d);
	case SYSCALL_OBJECT_SEEK:
		return sys_object_seek(a, b, c);
	case SYSCALL_OBJECT_REMOVE:
		return sys_object_remove(a,(const char*)b);
	case SYSCALL_OBJECT_CLOSE:
		return sys_object_close(a);
	case SYSCALL_OBJECT_SET_TAG:
		return sys_object_set_tag(a, (char *) b);
	case SYSCALL_OBJECT_GET_TAG:
		return sys_object_get_tag(a, (char *) b, c);
	case SYSCALL_OBJECT_SIZE:
		return sys_object_size(a, (int *) b, c);
	case SYSCALL_OBJECT_MAX:
		return sys_object_max(a);
	case SYSCALL_SYSTEM_STATS:
		return sys_system_stats((struct system_stats *) a);
	case SYSCALL_BCACHE_STATS:
		return sys_bcache_stats((struct bcache_stats *) a);
	case SYSCALL_BCACHE_FLUSH:
		return sys_bcache_flush();
	case SYSCALL_SYSTEM_TIME:
		return sys_system_time((uint32_t*)a);
	case SYSCALL_SYSTEM_RTC:
		return sys_system_rtc((struct rtc_time *) a);
	case SYSCALL_DEVICE_DRIVER_STATS:
		return sys_device_driver_stats((char *) a, (struct device_driver_stats *) b);
#ifdef KERNEL_SYSCALL_EXT
  KERNEL_SYSCALL_EXT_SWITCH
#endif
	default:
		return KERROR_INVALID_SYSCALL;
	}
}
