/*
 * Authors: Clément Guérin and Alain Greiner (2015)
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>

#define MAX_SIZE	(128)  // max number of characters in one command
#define LOG_DEPTH   (128)  // max number of commands in log
#define MAX_ARGS	(32)   // max number of arguments in a command
#define FIFO_SIZE   (1024) // FIFO depth for recursive ls

#define __unused(a)	(void)a

extern char **environ;

////////////////////////////////////////////////////////////////////////////////
//	   Global Variables
////////////////////////////////////////////////////////////////////////////////

struct {
	char buf[MAX_SIZE];
	size_t count;
} log_entries[LOG_DEPTH];

size_t ptw; // write pointer in log
size_t ptr; // read pointer in log

struct command_t {
	char *name;
	char *desc;
	void (*fn)(int, char **);
};

struct command_t cmd[];

////////////////////////////////////////////////////////////////////////////////
//	   Shell  Commands
////////////////////////////////////////////////////////////////////////////////

static void cmd_cat(int argc, char **argv)
{
	char *path, *buf = NULL;
	struct stat st;
	size_t size;
	int fd;

	if (argc != 2) {
		printf("usage: cat pathname\n");
		return;
	}
	path = argv[1];

	/* open the file */
	fd = open(path, O_RDONLY, 0);
	if (fd < 0) {
		printf("error: cannot open %s\n", path);
		goto exit;
	}

	/* get file size */
	if (stat(path, &st) == -1) {
		printf("error: cannot stat %s\n", path);
		goto exit;
	}
	if (S_ISDIR(st.st_mode)) {
		printf("error: %s is a directory\n", path);
		goto exit;
	}
	size = st.st_size;

	/* mmap the file */
	buf = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
	if (buf == NULL || buf == (char *)-1) {
		printf("error: cannot map %s\n", path);
		goto exit;
	}

	/* set terminating '0' XXX */
	buf[size-1] = 0;

	/* display the file content */
	printf("%s", buf);

exit:
	if (buf != NULL)
		munmap(buf, size);
	if (fd >= 0)
		close(fd);
}

static void cmd_cp(int argc, char **argv)
{
	int src_fd = -1, dst_fd = -1;
	char *srcpath, *dstpath;
	struct stat st;
	size_t size, i;
	char buf[1024];

	if (argc != 3) {
		printf("usage: cp src_pathname dst_pathname\n");
		return;
	}

	srcpath = argv[1];
	dstpath = argv[2];

	/* open the src file */
	src_fd = open(srcpath, O_RDONLY, 0);
	if (src_fd < 0) {
		printf("error: cannot open %s / err = %d\n", srcpath, errno);
		goto exit;
	}

	/* get file size */
	if (stat(srcpath, &st) == -1) {
		printf("error: cannot stat %s\n", srcpath);
		goto exit;
	}
	if (S_ISDIR(st.st_mode)) {
		printf("error: %s is a directory\n", srcpath);
		goto exit;
	}
	size = st.st_size;

	/* open the dst file */
	dst_fd = open(dstpath, O_CREAT|O_TRUNC|O_RDWR, 0);
	if (dst_fd < 0) {
		printf("error: cannot open %s / err = %d\n", dstpath, errno);
		goto exit;
	}
	if (stat(dstpath, &st) == -1) {
		printf("error: cannot stat %s\n", dstpath);
		goto exit;
	}
	if (S_ISDIR(st.st_mode)) {
		printf("error: %s is a directory\n", dstpath);
		goto exit;
	}

	i = 0;
	while (i < size)
	{
		size_t rlen = (size - i < 1024 ? size - i : 1024);
		size_t wlen;
		ssize_t ret;

		/* read the source */
		ret = read(src_fd, buf, rlen);
		if (ret == -1) {
			printf("error: cannot read from file %s\n", srcpath);
			goto exit;
		}
		rlen = (size_t)ret;

		/* write to the destination */
		ret = write(dst_fd, buf, rlen);
		if (ret == -1) {
			printf("error: cannot write to file %s\n", dstpath);
			goto exit;
		}
		wlen = (size_t)ret;

		/* check */
		if (wlen != rlen) {
			printf("error: cannot write on device\n");
			goto exit;
		}

		i += rlen;
	}

exit:
	if (src_fd >= 0)
		close(src_fd);
	if (dst_fd >= 0)
		close(dst_fd);
}

static void cmd_exec(int argc, char **argv)
{
	pid_t pid, wpid;
	int status;
	char *path;

	if (argc != 2) {
		printf("usage: %s prog\n", argv[0]);
		return;
	}

	path = argv[1];
	argv++;

	pid = fork();
	if (pid == 0) {
		/* child process */
		if (execve(path, argv, environ) == -1) {
			printf("error: unable to execve %s\n", path);
		}
		exit(EXIT_FAILURE);
	} else if (pid < 0) {
		/* error forking */
		printf("error: unable to fork\n");
	} else {
		/* parent process */
		do {
			wpid = waitpid(pid, &status, WUNTRACED);
		} while (!WIFEXITED(status) && !WIFSIGNALED(status));
	}
}

static void cmd_help(int argc, char **argv)
{
	size_t i;
	__unused(argc);
	__unused(argv);
	printf("available commands:\n");
	for (i = 0; cmd[i].name; i++) {
		printf("\t%s\t : %s\n", cmd[i].name , cmd[i].desc);
	}
}

static void cmd_kill(int argc, char **argv)
{
	pid_t pid;

	if (argc != 2) {
		printf("usage: %s pid\n", argv[0]);
		return;
	}

	pid = (pid_t)atoi(argv[1]);

	if (kill(pid, SIGKILL) == -1) {
		printf("error: unable to kill %d\n", (int)pid);
	}
}

static void cmd_log(int argc, char **argv)
{
	size_t i;
	__unused(argc);
	__unused(argv);
	printf("--- registered commands ---\n");
	for (i = 0; i < LOG_DEPTH; i++) {
		printf(" - %zu\t: %s\n", i, &log_entries[i].buf);
	}
}

static void cmd_ls(int argc, char **argv)
{
    struct dirent *file;
	char *path;
    DIR *dir;

	if (argc == 1) {
		path = ".";
	} else if (argc == 2) {
		path = argv[1];
	} else {
		printf("usage: ls [path]\n");
		return;
	}

	dir = opendir(path);
	while ((file = readdir(dir)) != NULL)
	{
		printf(" %s\n", file->d_name);
	}
	closedir(dir);
}

static void cmd_mkdir(int argc, char **argv)
{
	char *path;

	if (argc != 2) {
		printf("usage: mkdir pathname\n");
		return;
	}

	path = argv[1];

	if (mkdir(path, 0700) == -1) {
		printf("error: cannot create directory %s\n", path);
	}
}

static void cmd_mv(int argc, char **argv)
{
#if 0
	if (argc < 3)
	{
		printf("  usage : %s src_pathname dst_pathname\n", argv[0]);
		return;
	}

	int ret = giet_fat_rename(argv[1], argv[2]);
	if (ret < 0)
	{
		printf("error : cannot move %s to %s / err = %d\n", argv[1], argv[2], ret );
	}
#endif
}

static void cmd_rm(int argc, char **argv)
{
	char *path;

	if (argc != 2) {
		printf("usage: rm pathname\n");
		return;
	}

	path = argv[1];

	if (remove(path) == -1) {
		printf("error: cannot remove %s\n", path);
	}
}

static void cmd_rmdir(int argc, char **argv)
{
	cmd_rm(argc, argv);
}

static void cmd_cd(int argc, char **argv)
{
	char *path;

	if (argc != 2) {
		printf("usage: cd pathname\n");
		return;
	}

	path = argv[1];

	if (chdir(path) == -1) {
		printf("error: cannot cd to %s\n", path);
	}
}

static void cmd_pwd(int argc, char **argv)
{
	char buf[1024];

	if (argc != 1) {
		printf("usage: pwd\n");
		return;
	}

	if (getcwd(buf, 1024) == NULL) {
		printf("error: unable to get current directory\n");
	} else {
		printf("%s\n", buf);
	}
}

struct command_t cmd[] =
{
	{ "cat",    "display file content",                cmd_cat },
	{ "cd",     "change current directory",            cmd_cd },
	{ "cp",     "replicate a file in file system",     cmd_cp },
	{ "exec",   "start an application",                cmd_exec },
	{ "help",   "list available commands",             cmd_help },
	{ "kill",   "kill an application (all threads)",   cmd_kill },
	{ "log",    "list registered commands",            cmd_log },
	{ "ls",     "list directory entries",              cmd_ls },
	{ "mkdir",  "create a new directory",              cmd_mkdir },
	{ "mv",     "move a file in file system",          cmd_mv },
	{ "pwd",    "print current working directory",     cmd_pwd },
	{ "rm",     "remove a file from file system",      cmd_rm },
	{ "rmdir",  "remove a directory from file system", cmd_rmdir },
	{ NULL,     NULL,								   NULL }
};

///////////////////////////////////////////////////////////////////
// This function analyses one command (with arguments)
///////////////////////////////////////////////////////////////////
static void parse(char *buf)
{
	int argc = 0;
	char *argv[MAX_ARGS];
	int i;
	int len = strlen(buf);

	// build argc/argv
	for (i = 0; i < len; i++) {
		if (buf[i] == ' ') {
			buf[i] = '\0';
		} else if (i == 0 || buf[i - 1] == '\0') {
			if (argc < MAX_ARGS) {
				argv[argc] = &buf[i];
				argc++;
			}
		}
	}

	if (argc > 0) {
		int found = 0;

		argv[argc] = NULL;

		// try to match typed command with built-ins
		for (i = 0; cmd[i].name; i++) {
			if (strcmp(argv[0], cmd[i].name) == 0) {
				cmd[i].fn(argc, argv);
				found = 1;
				break;
			}
		}

		if (!found) {
			printf("\n  undefined command %s\n", argv[0]);
		}
	}
}

int main(int argc, char *argv[])
{
	char c;							  // read character
	char buf[MAX_SIZE];				  // buffer for one command
	size_t count = 0;					  // pointer in buf
	size_t i, j;						  // indexes for loops
	__unused(argc);
	__unused(argv);

	enum fsm_states {
		NORMAL,
		ESCAPE,
		BRAKET,
	};

	memset(&log_entries, 0, sizeof(log_entries));
	ptw = 0;
	ptr = 0;

	printf( "~~~ shell ~~~\n\n" );

	// command buffer initialisation
	memset(buf, 0x20, sizeof(buf));
	count = 0;

	// display first prompt
	printf("# ");

	// This lexical analyser writes one command line in the buf buffer.
	// It is implemented as a 3 states FSM to handle the following sequences:
	// - ESC [ A : up arrow
	// - ESC [ B : down arrow
	// - ESC [ C : right arrow
	// - ESC [ D : left arrow
	// The thee states have the following semantic:
	// - NORMAL : no (ESC) character has been found
	// - ESCAPE : the character (ESC) has been found
	// - BRAKET : the wo characters (ESC,[) have been found
	unsigned int state = NORMAL;

	while (1)
	{
		/*
		 * Certainly not a good idea to use getchar. The escape sequences won't
		 * be handled correctly.
		 */
		c = getchar();

		switch (state)
		{
			case NORMAL:
			{
				if ((c == '\b') || (c == 0x7F))  // backspace => remove one character
				{
					if (count > 0) {
						printf("\b \b");
						count--;
					}
				}
				else if (c == '\n')	 // new line => call parser to execute command
				{
					if (count > 0)
					{
						// complete command
						buf[count] = '\0';

						// register command in log arrays
						strcpy(log_entries[ptw].buf, buf);
						log_entries[ptw].count = count;
						ptw = (ptw + 1) % LOG_DEPTH;
						ptr = ptw;

						// execute command
						printf("\n");
						parse((char *)&buf);

						// reinitialise buffer and display prompt
						for ( i = 0 ; i < sizeof(buf) ; i++ ) buf[i] = 0x20;
						count = 0;
						printf("# ");
					}
				}
				else if (c == '\t')	// tabulation => do nothing
				{
				}
				else if (c == 0x1B)	// ESC => start an escape sequence
				{
					state = ESCAPE;
				}
				else if (c == 0x03)	// ^C  => cancel current command
				{
					for (i = 0; i < count; i++) printf("\b \b");
					for (i = 0; i < sizeof(buf); i++) buf[i] = 0x20;
					count = 0;
				}
				else					 // register character in command buffer
				{
					if (count < sizeof(buf) - 1)
					{
						printf("%c", c);
						buf[count] = c;
						count++;
					}
				}
				break;
			}
			case ESCAPE:
			{
				if (c == '[')		//  valid sequence => continue
				{
					state = BRAKET;
				}
				else				   // invalid sequence => do nothing
				{
					state = NORMAL;
				}
				break;
			}
			case BRAKET:
			{
				if (c == 'D')   // valid  LEFT sequence => move buf pointer left
				{
					if (count > 0)
					{
						printf("\b");
						count--;
					}

					// get next user char
					state = NORMAL;
				}
				else if (c == 'C')   // valid  RIGHT sequence => move buf pointer right
				{
					if (count < sizeof(buf) - 1)
					{
						printf("%c", buf[count]);
						count++;
					}

					// get next user char
					state = NORMAL;
				}
				else if (c == 'A')   // valid  UP sequence => move log pointer backward
				{
					// cancel current command
					for (i = 0; i < count; i++) printf("\b \b");
					count = 0;

					// copy log command into buf
					ptr = (ptr - 1) % LOG_DEPTH;
					strcpy(buf, log_entries[ptr].buf);
					count = log_entries[ptr].count;

					// display log command
					printf("%s", buf);

					// get next user char
					state = NORMAL;
				}
				else if (c == 'B')   // valid  DOWN sequence => move log pointer forward
				{
					// cancel current command
					for (i = 0 ; i < count; i++) printf("\b \b");
					count = 0;

					// copy log command into buf
					ptr = (ptr + 1) % LOG_DEPTH;
					strcpy(buf, log_entries[ptr].buf);
					count = log_entries[ptr].count;

					// display log command
					printf("%s", buf);

					// get next user char
					state = NORMAL;
				}
				else				   // other character => do nothing
				{
					// get next user char
					state = NORMAL;
				}
				break;
			}
		}
	}
}

