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

#include "kernel/types.h"
#include "kernel/error.h"
#include "kernel/ascii.h"
#include "kshell.h"
#include "console.h"
#include "string.h"
#include "rtc.h"
#include "kmalloc.h"
#include "page.h"
#include "process.h"
#include "main.h"
#include "fs.h"
#include "syscall_handler.h"
#include "clock.h"
#include "kernelcore.h"
#include "bcache.h"
#include "printf.h"

static int kshell_mount( const char *devname, int unit, const char *fs_type)
{
	if(current->ktable[KNO_STDDIR]) {
		printf("root filesystem already mounted, please unmount first\n");
		return -1;
	}

	struct device *dev = device_open(devname,unit);
	if(dev) {
		struct fs *fs = fs_lookup(fs_type);
		if(fs) {
			struct fs_volume *v = fs_volume_open(fs,dev);
			if(v) {
				struct fs_dirent *d = fs_volume_root(v);
				if(d) {
					current->ktable[KNO_STDDIR] = kobject_create_dir(d);
					return 0;
				} else {
					printf("mount: couldn't find root dir on %s unit %d!\n",device_name(dev),device_unit(dev));
					return -1;
				}
				fs_volume_close(v);
			} else {
				printf("mount: couldn't mount %s on %s unit %d\n",fs_type,device_name(dev),device_unit(dev));
				return -1;
			}
		} else {
			printf("mount: invalid fs type: %s\n", fs_type);
			return -1;
		}
		device_close(dev);
	} else {
		printf("mount: couldn't open device %s unit %d\n",devname,unit);
		return -1;
	}

	return -1;
}

static int kshell_automount()
{
	int i;

	for(i=0;i<4;i++) {
		printf("automount: trying atapi unit %d...\n",i);
		if(kshell_mount("atapi",i,"cdromfs")==0) return 0;
	}

	for(i=0;i<4;i++) {
		printf("automount: trying ata unit %d...\n",i);
		if(kshell_mount("ata",i,"simplefs")==0) return 0;
	}

	printf("automount: no bootable devices available.\n");
	return -1;
}

/*
Install software from the cdrom volume unit src
to the disk volume dst by performing a recursive copy.
XXX This needs better error checking.
*/

int kshell_install( const char *src_device_name, int src_unit, const char *dst_device_name, int dst_unit )
{
	struct fs *srcfs = fs_lookup("cdromfs");
	struct fs *dstfs = fs_lookup("diskfs");

	if(!srcfs || !dstfs) return KERROR_NOT_FOUND;

	struct device *srcdev = device_open(src_device_name,src_unit);
	struct device *dstdev = device_open(dst_device_name,dst_unit);

	if(!srcdev || !dstdev) return KERROR_NOT_FOUND;

	struct fs_volume *srcvolume = fs_volume_open(srcfs,srcdev);
	struct fs_volume *dstvolume = fs_volume_open(dstfs,dstdev);

	if(!srcvolume || !dstvolume) return KERROR_NOT_FOUND;

	struct fs_dirent *srcroot = fs_volume_root(srcvolume);
	struct fs_dirent *dstroot = fs_volume_root(dstvolume);

	printf("copying %s unit %d to %s unit %d...\n",src_device_name,src_unit,dst_device_name,dst_unit);

	fs_dirent_copy(srcroot, dstroot,0);

	fs_dirent_close(dstroot);
	fs_dirent_close(srcroot);

	fs_volume_close(srcvolume);
	fs_volume_close(dstvolume);

	device_close(srcdev);

	bcache_flush_device(dstdev);
	device_close(dstdev);

	return 0;
}

static int kshell_printdir(const char *d, int length)
{
	while(length > 0) {
		printf("%s\n", d);
		int len = strlen(d) + 1;
		d += len;
		length -= len;
	}
	return 0;
}

static int kshell_listdir(const char *path)
{
	struct fs_dirent *d = fs_resolve(path);
	if(d) {
		int buffer_length = 1024;
		char *buffer = kmalloc(buffer_length);
		if(buffer) {
			int length = fs_dirent_list(d, buffer, buffer_length);
			if(length>=0) {
				kshell_printdir(buffer, length);
			} else {
				printf("list: %s is not a directory\n", path);
			}
			kfree(buffer);
		}
	} else {
		printf("list: %s does not exist\n", path);
	}

	return 0;
}

static int kshell_execute(int argc, const char **argv)
{
	const char *cmd = argv[0];

	if(!strcmp(cmd, "start")) {
		if(argc > 1) {
			int fd = sys_open_file(KNO_STDDIR,argv[1],0,0);
			if(fd>=0) {
				int pid = sys_process_run(fd, argc - 1,  &argv[1]);
				if(pid > 0) {
					printf("started process %d\n", pid);
					process_yield();
				} else {
					printf("couldn't start %s\n", argv[1]);
				}
				sys_object_close(fd);
			} else {
				printf("couldn't find %s\n",argv[1]);
			}
		} else {
			printf("run: requires argument.\n");
		}
	} else if(!strcmp(cmd, "exec")) {
		if(argc > 1) {
			int fd = sys_open_file(KNO_STDDIR,argv[1],0,0);
			if(fd>=0) {
				sys_process_exec(fd, argc - 1, &argv[1]);
				process_yield();
				printf("couldn't exec %s\n", argv[1]);
			} else {
				printf("couldn't find %s\n",argv[1]);
			}
		} else {
			printf("exec: requires argument.\n");
		}
	} else if(!strcmp(cmd, "run")) {
		if(argc > 1) {
			int fd = sys_open_file(KNO_STDDIR,argv[1],0,0);
			if(fd>=0) {
				int pid = sys_process_run(fd, argc - 1, &argv[1]);
				if(pid > 0) {
					printf("started process %d\n", pid);
					process_yield();
					struct process_info info;
					process_wait_child(pid, &info, -1);
					printf("process %d exited with status %d\n", info.pid, info.exitcode);
					process_reap(info.pid);
				} else {
					printf("couldn't start %s\n", argv[1]);
				}
			} else {
				printf("couldn't find %s\n",argv[1]);
			}
		} else {
			printf("run: requires argument\n");
		}
	} else if(!strcmp(cmd, "automount")) {
		kshell_automount();
	} else if(!strcmp(cmd, "mount")) {
		if(argc==4) {
			int unit;
			if(str2int(argv[2], &unit)) {
				kshell_mount(argv[1],unit,argv[3]);
			} else {
				printf("mount: expected unit number but got %s\n", argv[2]);
			}
		} else {
			printf("mount: requires device, unit, and fs type\n");
		}
	} else if(!strcmp(cmd, "umount")) {
		if(current->ktable[KNO_STDDIR]) {
			printf("unmounting root directory\n");
			sys_object_close(KNO_STDDIR);
		} else {
			printf("nothing currently mounted\n");
		}
	} else if(!strcmp(cmd, "reap")) {
		if(argc > 1) {
			int pid;
			if(str2int(argv[1], &pid)) {
				if(process_reap(pid)) {
					printf("reap failed!\n");
				} else {
					printf("process %d reaped\n", pid);
				}
			} else {
				printf("reap: expected process id but got %s\n", argv[1]);
			}
		} else {
			printf("reap: requires argument\n");
		}
	} else if(!strcmp(cmd, "kill")) {
		if(argc > 1) {
			int pid;
			if(str2int(argv[1], &pid)) {
				process_kill(pid);
			} else {
				printf("kill: expected process id number but got %s\n", argv[1]);
			}
		} else {
			printf("kill: requires argument\n");
		}

	} else if(!strcmp(cmd, "wait")) {
		struct process_info info;
		if(process_wait_child(0, &info, 5000) > 0) {
			printf("process %d exited with status %d\n", info.pid, info.exitcode);
		} else {
			printf("wait: timeout\n");
		}
	} else if(!strcmp(cmd, "list")) {
		if(argc > 1) {
			kshell_listdir(argv[1]);
		} else {
			kshell_listdir(".");
		}
	} else if(!strcmp(cmd, "mkdir")) {
		if(argc == 3) {
			struct fs_dirent *dir = fs_resolve(argv[1]);
			if(dir) {
				struct fs_dirent *n = fs_dirent_mkdir(dir,argv[2]);
				if(!n) {
					printf("mkdir: couldn't create %s\n",argv[2]);
				} else {
					fs_dirent_close(n);
				}
				fs_dirent_close(dir);
			} else {
				printf("mkdir: couldn't open %s\n",argv[1]);
			}
		} else {
			printf("use: mkdir <parent-dir> <dirname>\n");
		}
	} else if(!strcmp(cmd, "format")) {
		if(argc == 4) {
			int unit;
			if(str2int(argv[2], &unit)) {
				struct fs *f = fs_lookup(argv[3]);
				if(f) {
					struct device *d = device_open(argv[1],unit);
					if(d) {
						fs_volume_format(f,d);
					} else {
						printf("couldn't open device %s unit %d\n",argv[1],unit);
					}
				} else {
					printf("invalid fs type: %s\n", argv[3]);
				}
			} else {
				printf("format: expected unit number but got %s\n", argv[2]);
			}
		}
	} else if(!strcmp(cmd,"install")) {
		if(argc==5) {
			int src, dst;
			str2int(argv[2], &src);
			str2int(argv[4], &dst);
			kshell_install(argv[1],src,argv[3],dst);
		} else {
			printf("install: expected src-device-name src-unit dest-device-name dest-unit\n");
		}

	} else if(!strcmp(cmd, "remove")) {
		if(argc == 3) {
			struct fs_dirent *dir = fs_resolve(argv[1]);
			if(dir) {
				int result = fs_dirent_remove(dir,argv[2]);
				if(result<0) {
					printf("remove: couldn't remove %s\n",argv[2]);
				}
				fs_dirent_close(dir);
			} else {
				printf("remove: couldn't open %s\n",argv[1]);
			}
		} else {
			printf("use: remove <parent-dir> <filename>\n");
		}
	} else if(!strcmp(cmd, "time")) {
		struct rtc_time time;
		rtc_read(&time);
		printf("%d-%d-%d %d:%d:%d\n", time.year, time.month, time.day, time.hour, time.minute, time.second);
	} else if(!strcmp(cmd, "reboot")) {
		reboot();
	} else if(!strcmp(cmd, "bcache_stats")) {
		struct bcache_stats stats;
		bcache_get_stats(&stats);
		printf("%d rhit %d rmiss %d whit %d wmiss %d wback\n",
			stats.read_hits,stats.read_misses,
			stats.write_hits,stats.write_misses,
			stats.writebacks);
	} else if(!strcmp(cmd,"bcache_flush")) {
		bcache_flush_all();
	} else if(!strcmp(cmd, "help")) {
		printf("Kernel Shell Commands:\nrun <path> <args>\nstart <path> <args>\nkill <pid>\nreap <pid>\nwait\nlist\nautomount\nmount <device> <unit> <fstype>\numount\nformat <device> <unit><fstype>\ninstall atapi <srcunit> ata <dstunit>\nmkdir <path>\nremove <path>time\nbcache_stats\nbcache_flush\nreboot\nhelp\n\n");
	} else {
		printf("%s: command not found\n", argv[0]);
	}
	return 0;
}

int kshell_readline(char *line, int length)
{
	int i = 0;
	while(i < (length - 1)) {
		char c = console_getchar(&console_root);
		if(c == ASCII_CR) {
			line[i] = 0;
			printf("\n");
			return 1;
		} else if(c == ASCII_BS) {
			if(i > 0) {
				putchar(c);
				i--;
			}
		} else if(c >= 0x20 && c <= 0x7E) {
			putchar(c);
			line[i] = c;
			i++;
		}
	}

	return 0;
}


int kshell_launch()
{
	char line[1024];
	const char *argv[100];
	int argc;

	while(1) {
		printf("kshell> ");
		kshell_readline(line, sizeof(line));

		argc = 0;
		argv[argc] = strtok(line, " ");
		while(argv[argc]) {
			argc++;
			argv[argc] = strtok(0, " ");
		}

		if(argc > 0) {
			kshell_execute(argc, argv);
		}
	}

	return 0;
}