/* vim: tabstop=4 shiftwidth=4 noexpandtab
 * This file is part of ToaruOS and is released under the terms
 * of the NCSA / University of Illinois License - see LICENSE.md
 * Copyright (C) 2012-2014 Kevin Lange
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/times.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/utsname.h>
#include <sys/termios.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdarg.h>
#include <utime.h>
#include <signal.h>

#include <_ansi.h>
#include <errno.h>

#include "syscall.h"
#include <bits/dirent.h>

#include <syscall_nums.h>

extern void *malloc(size_t size);
extern void free(void *ptr);
extern void *calloc(size_t nmemb, size_t size);
extern void *realloc(void *ptr, size_t size);

extern void _init();
extern void _fini();

DEFN_SYSCALL1(exit,  0, int);
DEFN_SYSCALL1(print, 1, const char *);
DEFN_SYSCALL3(open,  2, const char *, int, int);
DEFN_SYSCALL3(read,  3, int, char *, int);
DEFN_SYSCALL3(write, 4, int, char *, int);
DEFN_SYSCALL1(close, 5, int);
DEFN_SYSCALL2(gettimeofday, 6, void *, void *);
DEFN_SYSCALL3(execve, 7, char *, char **, char **);
DEFN_SYSCALL0(fork, 8);
DEFN_SYSCALL0(getpid, 9);
DEFN_SYSCALL1(sbrk, 10, int);
DEFN_SYSCALL0(getgraphicsaddress, 11);
DEFN_SYSCALL1(uname, 12, void *);
DEFN_SYSCALL5(openpty, 13, int *, int *, char *, void *, void *);
DEFN_SYSCALL3(lseek, 14, int, int, int);
DEFN_SYSCALL2(fstat, 15, int, void *);
DEFN_SYSCALL1(setgraphicsoffset, 16, int);
DEFN_SYSCALL1(wait, 17, unsigned int);
DEFN_SYSCALL0(getgraphicswidth,  18);
DEFN_SYSCALL0(getgraphicsheight, 19);
DEFN_SYSCALL0(getgraphicsdepth,  20);
DEFN_SYSCALL0(mkpipe, 21);
DEFN_SYSCALL2(dup2, 22, int, int);
DEFN_SYSCALL0(getuid, 23);
DEFN_SYSCALL1(setuid, 24, unsigned int);
DEFN_SYSCALL1(kernel_string_XXX, 25, char *);
DEFN_SYSCALL0(reboot, 26);
DEFN_SYSCALL3(readdir, 27, int, int, void *);
DEFN_SYSCALL1(chdir, 28, char *);
DEFN_SYSCALL2(getcwd, 29, char *, size_t);
DEFN_SYSCALL3(clone, 30, uintptr_t, uintptr_t, void *);
DEFN_SYSCALL1(sethostname, 31, char *);
DEFN_SYSCALL1(gethostname, 32, char *);
DEFN_SYSCALL0(mousedevice, 33);
DEFN_SYSCALL2(mkdir, 34, char *, unsigned int);
DEFN_SYSCALL2(shm_obtain, 35, char *, size_t *);
DEFN_SYSCALL1(shm_release, 36, char *);
DEFN_SYSCALL2(send_signal, 37, uint32_t, uint32_t);
DEFN_SYSCALL2(signal, 38, uint32_t, void *);
DEFN_SYSCALL2(share_fd, 39, int, int);
DEFN_SYSCALL1(get_fd, 40, int);
DEFN_SYSCALL0(gettid, 41);
DEFN_SYSCALL0(yield, 42);
DEFN_SYSCALL2(system_function, 43, int, char **);
DEFN_SYSCALL1(open_serial, 44, int);
DEFN_SYSCALL2(sleepabs,  45, unsigned long, unsigned long);
DEFN_SYSCALL2(nanosleep,  46, unsigned long, unsigned long);
DEFN_SYSCALL3(ioctl, 47, int, int, void *);
DEFN_SYSCALL2(access, 48, char *, int);
DEFN_SYSCALL2(stat, 49, char *, void *);
DEFN_SYSCALL2(chmod, 50, char *, mode_t);
DEFN_SYSCALL1(umask, 51, mode_t);
DEFN_SYSCALL1(unlink, 52, char *);
DEFN_SYSCALL3(waitpid, 53, int, int *, int);
DEFN_SYSCALL1(pipe, 54, int *);
DEFN_SYSCALL5(mount, SYS_MOUNT, char *, char *, char *, unsigned long, void *);
DEFN_SYSCALL2(symlink, SYS_SYMLINK, char *, char *);
DEFN_SYSCALL3(readlink, SYS_READLINK, char *, char *, int);
DEFN_SYSCALL2(lstat, SYS_LSTAT, char *, void *);
DEFN_SYSCALL2(fswait, SYS_FSWAIT, int, int *);
DEFN_SYSCALL3(fswait2, SYS_FSWAIT2, int, int *,int);
DEFN_SYSCALL3(chown, SYS_CHOWN, char *, int, int);

static int toaru_debug_stubs_enabled(void) {
	static int checked = 0;
	static int enabled = 0;
	if (!checked) {
		char * t = getenv("TOARU_DEBUG_STUBS");
		checked = 1;
		if (t && !strcmp(t,"1")) {
			enabled = 1;
		}
	}
	return enabled;
}

#define DEBUG_STUB(...) \
	if (toaru_debug_stubs_enabled()) { \
		fprintf(stderr, "\033[1;32mUserspace Debug\033[0m pid%d ", getpid()); fprintf(stderr, __VA_ARGS__); \
	}


extern char ** environ;

int ioctl(int fd, int request, void * argp);

#define DEFAULT_PATH ".:/bin:/usr/bin"

// --- Process Control ---

int _exit(int val){
	_fini();
	return syscall_exit(val);
}

int execve(const char *name, char * const argv[], char * const envp[]) {
	return syscall_execve((char*)name,(char**)argv,(char**)envp);
}

int execvp(const char *file, char *const argv[]) {
	if (file && (!strstr(file, "/"))) {
		/* We don't quite understand "$PATH", so... */
		char * path = getenv("PATH");
		if (!path) {
			path = DEFAULT_PATH;
		}
		char * xpath = strdup(path);
		int found = 0;
		char * p, * tokens[10], * last;
		int i = 0;
		for ((p = strtok_r(xpath, ":", &last)); p; p = strtok_r(NULL, ":", &last)) {
			int r;
			struct stat stat_buf;
			char * exe = malloc(strlen(p) + strlen(file) + 2);
			strcpy(exe, p);
			strcat(exe, "/");
			strcat(exe, file);

			r = stat(exe, &stat_buf);
			if (r != 0) {
				continue;
			}
			if (!(stat_buf.st_mode & 0111)) {
				continue; /* XXX not technically correct; need to test perms */
			}
			return execve(exe, argv, environ);
		}
		free(xpath);
		errno = ENOENT;
		return -1;
	} else if (file) {
		return execve(file, argv, environ);
	}
	errno = ENOENT;
	return -1;
}

int execv(const char * file, char *const argv[]) {
	return execve(file,argv,environ);
}

/*
 * getpid -- only one process, so just return 1.
 */
int getpid() {
	return syscall_getpid();
}

/* Fork. Duh. */
int fork(void) {
	return syscall_fork();
}

int uname(struct utsname *__name) {
	return syscall_uname((void *)__name);
}


/*
 * kill -- go out via exit...
 */
int kill(int pid, int sig) {
	return syscall_send_signal(pid, sig);
}

sighandler_t signal(int signum, sighandler_t handler) {
	return (sighandler_t)syscall_signal(signum, (void *)handler);
}

#if 0
int raise(int sig) {
	kill(getpid(), sig);
	return 0;
}
#endif

int waitpid(int pid, int *status, int options) {
	/* XXX: status, options? */
	int i = syscall_waitpid(pid, status, options);
	if (i < 0) {
		errno = -i;
		return -1;
	}
	return i;
}

int wait(int *status) {
	return waitpid(-1, status, 0);
}

// --- I/O ---

int isatty(int fd) {
	int dtype = ioctl(fd, IOCTLDTYPE, NULL);
	if (dtype == IOCTL_DTYPE_TTY) return 1;
	errno = EINVAL;
	return 0;
}


int close(int file) {
	return syscall_close(file);
}

int link(char *old, char *new) {
	DEBUG_STUB("[debug] pid %d: link(%s, %s);\n", getpid(), old, new);
	errno = EMLINK;
	return -1;
}

int lseek(int file, int ptr, int dir) {
	return syscall_lseek(file,ptr,dir);
}

int open(const char *name, int flags, ...) {
	va_list argp;
	int mode;
	int result;
	va_start(argp, flags);
	if (flags & O_CREAT) mode = va_arg(argp, int);
	va_end(argp);

	result = syscall_open(name, flags, mode);
	if (result == -1) {
		if (flags & O_CREAT) {
			errno = EACCES;
		} else {
			errno = ENOENT;
		}
	} else if (result < 0) {
		errno = -result;
	}
	return result;
}

int read(int file, char *ptr, int len) {
	return syscall_read(file,ptr,len);
}

int creat(const char *path, mode_t mode) {
	return open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
}

int fstat(int file, struct stat *st) {
	syscall_fstat(file, st);
	return 0;
}

int stat(const char *file, struct stat *st){
	int ret = syscall_stat((char *)file, (void *)st);
	if (ret >= 0) {
		return ret;
	} else {
		errno = ENOENT; /* meh */
		memset(st, 0x00, sizeof(struct stat));
		return ret;;
	}
}

int write(int file, char *ptr, int len) {
	return syscall_write(file,ptr,len);
}

/*
 * sbrk: request a larger heap
 * [the kernel will give this to us]
 */
caddr_t sbrk(int nbytes){
	return (caddr_t)syscall_sbrk(nbytes);
}

// --- Other ---
int gettimeofday(struct timeval *p, void *z){
	return syscall_gettimeofday(p,z);
}

int pipe(int fildes[2]) {
	int ret = syscall_pipe((int *)fildes);
	if (ret < 0) {
		errno = -ret;
		return -1;
	}
	return ret;
}

char *getcwd(char *buf, size_t size) {
	if (!buf) buf = malloc(size);
	return (char *)syscall_getcwd(buf, size);
}

char *getwd(char *buf) {
	return getcwd(buf, 256);
}

int lstat(const char *path, struct stat *st) {
	int ret = syscall_lstat((char *)path, (void *)st);
	if (ret >= 0) {
		return ret;
	} else {
		errno = -ret;
		memset(st, 0x00, sizeof(struct stat));
		return ret;
	}
}

int mkdir(const char *pathname, mode_t mode) {
	int ret = syscall_mkdir((char *)pathname, mode);
	if (ret < 0) {
		errno = -ret;
		return -1;
	}
	return ret;
}

int chdir(const char *path) {
	return syscall_chdir((char*)path);
}

unsigned int sleep(unsigned int seconds) {
	syscall_nanosleep(seconds, 0);
	return 0;
}

int usleep(useconds_t usec) {
	syscall_nanosleep(0, usec / 10000);
	return 0;
}


char _username[256];
char *getlogin(void) {
#define LINE_LEN 4096
	FILE * passwd = fopen("/etc/passwd", "r");
	char line[LINE_LEN];
	
	int uid = syscall_getuid();

	while (fgets(line, LINE_LEN, passwd) != NULL) {

		line[strlen(line)-1] = '\0';

		char *p, *tokens[10], *last;
		int i = 0;
		for ((p = strtok_r(line, ":", &last)); p;
				(p = strtok_r(NULL, ":", &last)), i++) {
			if (i < 511) tokens[i] = p;
		}
		tokens[i] = NULL;

		if (atoi(tokens[2]) == uid) {
			memcpy(_username, tokens[0], strlen(tokens[0])+1);
			break;
		}
	}
	fclose(passwd);

	return (char *)&_username;
}

int dup2(int oldfd, int newfd) {
	return syscall_dup2(oldfd, newfd);
}

DIR * opendir (const char * dirname) {
	int fd = open(dirname, O_RDONLY);
	if (fd == -1) {
		return NULL;
	}

	DIR * dir = (DIR *)malloc(sizeof(DIR));
	dir->fd = fd;
	dir->cur_entry = -1;
	return dir;
}

int closedir (DIR * dir) {
	if (dir && (dir->fd != -1)) {
		return close(dir->fd);
	} else {
		return -1;
	}
}

struct dirent * readdir (DIR * dirp) {
	static struct dirent ent;

	int ret = syscall_readdir(dirp->fd, ++dirp->cur_entry, &ent);
	if (ret != 0) {
		memset(&ent, 0, sizeof(struct dirent));
		return NULL;
	}

	return &ent;
}

void pre_main(int (*main)(int,char**), int argc, char * argv[]) {
	unsigned int x = 0;
	unsigned int nulls = 0;
	for (x = 0; 1; ++x) {
		if (!argv[x]) {
			++nulls;
			if (nulls == 2) {
				break;
			}
			continue;
		}
		if (nulls == 1) {
			environ = &argv[x];
			break;
		}
	}
	_init();
	_exit(main(argc, argv));
}


/* XXX Unimplemented functions */
unsigned int alarm(unsigned int seconds) {
	DEBUG_STUB("alarm(%s);\n", seconds);
	return 0;
}

clock_t times(struct tms *buf) {
	/* TODO: times() */
	return -1;
}


int  fcntl(int fd, int cmd, ...) {
	if (cmd == F_GETFD || cmd == F_SETFD) {
		return 0;
	}
	DEBUG_STUB("[user/debug] Unsupported operation [fcntl]\n");
	/* Not supported */
	return -1;
}

mode_t umask(mode_t mask) {
	return syscall_umask(mask);
}

int chmod(const char *path, mode_t mode) {
	int result = syscall_chmod((char *)path, mode);
	if (result < 0) {
		errno = -result;
		result = -1;
	}
	return result;
}

int unlink(char *name) {
	return syscall_unlink(name);
}

int access(const char *pathname, int mode) {
	int result = syscall_access((char *)pathname, mode);
	if (result < 0) {
		errno = ENOENT; /* XXX */
	}
	return result;
}

long pathconf(char *path, int name) {
	DEBUG_STUB("[user/debug] Unsupported operation [pathconf]\n");
	/* Not supported */
	return 0;
}


int utime(const char *filename, const struct utimbuf *times) {
	DEBUG_STUB("[user/debug] Unsupported operation [utime]\n");
	return 0;
}

int chown(const char *path, uid_t owner, gid_t group) {
	int result = syscall_chown((char *)path, owner, group);
	if (result < 0) {
		errno = -result;
		result = -1;
	}
	return result;
}

int rmdir(const char *pathname) {
	DEBUG_STUB("[user/debug] Unsupported operation [rmdir]\n");
	return 0;
}


char *ttyname(int fd) {
	errno = ENOTTY;
	return NULL;
}

long sysconf(int name) {
	switch (name) {
		case 8:
			return 4096;
		case 11:
			return 10000;
		default:
			DEBUG_STUB("sysconf(%d);\n", name);
			return -1;
	}
}

int ioctl(int fd, int request, void * argp) {
	return syscall_ioctl(fd, request, argp);
}

/* termios */
speed_t cfgetispeed(const struct termios * tio) {
	return 0;
}
speed_t cfgetospeed(const struct termios * tio) {
	return 0;
}

int cfsetispeed(struct termios * tio, speed_t speed) {
	/* hahahaha, yeah right */
	return 0;
}

int cfsetospeed(struct termios * tio, speed_t speed) {
	return 0;
}

int tcdrain(int i) {
	DEBUG_STUB("tcdrain(%d)\n", i);
	return 0;
}

int tcflow(int fd, int arg) {
	return ioctl(fd, TCXONC, arg);
}

int tcflush(int fd, int arg) {
	return ioctl(fd, TCFLSH, arg);
}

pid_t tcgetsid(int fd) {
	DEBUG_STUB("tcgetsid(%d)\n", fd);
	return getpid();
}

int tcsendbreak(int fd, int arg) {
	return ioctl(fd, TCSBRK, arg);
}

int tcgetattr(int fd, struct termios * tio) {
	return ioctl(fd, TCGETS, tio);
}

int tcsetattr(int fd, int actions, struct termios * tio) {
	switch (actions) {
		case TCSANOW:
			return ioctl(fd, TCSETS, tio);
		case TCSADRAIN:
			return ioctl(fd, TCSETSW, tio);
		case TCSAFLUSH:
			return ioctl(fd, TCSETSF, tio);
		default:
			return 0;
	}
}

int tcsetpgrp(int fd, pid_t pgrp) {
	return ioctl(fd, TIOCSPGRP, &pgrp);
}

pid_t tcgetpgrp(int fd) {
	pid_t pgrp;
	ioctl(fd, TIOCGPGRP, &pgrp);
	return pgrp;
}

int fpathconf(int file, int name) {
	DEBUG_STUB("fpathconf(%d,%d)\n", file, name);
	return 0;
}

int setuid(uid_t uid) {
	return syscall_setuid(uid);
}

int getuid() {
	return syscall_getuid();
}

int getgid() {
	return getuid();
}

int getpgrp() {
	/* XXX */
	return getgid();
}

int geteuid() {
	return getuid();
}

int getegid() {
	return getgid();
}

int getgroups(int size, gid_t list[]) {
	DEBUG_STUB("getgroups(...);\n");
	return 0;
}

pid_t wait3(int *status, int options, void *rusage) {
	return wait(status);
}

int dup(int oldfd) {
	return dup2(oldfd, -1);
}

int sched_yield(void) {
	return syscall_yield();
}

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset) {
	DEBUG_STUB("sigprocmask(%d, 0x%x, 0x%x);\n", how, set, oldset);
	return -1;
}

int sigsuspend(const sigset_t * mask) {
	DEBUG_STUB("sigsuspend(0x%x);\n", mask);
	syscall_yield();
	return -1;
}

int setpgid(pid_t pid, pid_t pgid) {
	DEBUG_STUB("setpgid(%d,%d);\n", pid, pgid);
	return -1;
}

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)  {

	sighandler_t old;

	if (act) {
		old = signal(signum, act->sa_handler);
	} else {
		/* We don't have a way to query, so we need to set to something, then
		 * set back to whatever it was... XXX */
		old = signal(signum, NULL);
		signal(signum, old);
	}

	if (oldact) {
		oldact->sa_handler = old;
	}

	if (act) {
		DEBUG_STUB("sigaction(%d,...,0x%x);\n", signum, act->sa_flags);
	}

	return 0;
}

pid_t getppid() {
	DEBUG_STUB("getppid()\n");
	return 0;
}


void sync() {
	DEBUG_STUB("sync();\n");
}

int mount(char * source, char * target, char * type, unsigned long flags, void * data) {
	int r = syscall_mount(source, target, type, flags, data);

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

	return r;
}

int symlink(char * target, char * name) {
	int r = syscall_symlink(target, name);

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

	return r;
}

int readlink(char * name, char * buf, size_t len) {
	int r = syscall_readlink(name, buf, len);

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

	return r;
}
