/*
 * Copyright 2011 Marian Ohligs, Chair for Operating Systems,
 *                               RWTH Aachen University
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#undef errno

extern int errno;

void showlogo() {
	printf("\n\n");
	printf("================================================================================\n");
	printf("                               m(etalsvm)shell\n\n");
	printf("================================================================================\n");
}

void help() {
	printf("shell builtin commands: \n");
	printf("cd <path> :\t change current working directory to path\n");
	printf("pwd       :\t show current working directory\n");
	printf("cat <path>:\t print files\n");
	printf("help      :\t this short help\n");
	printf("exit      :\t exit the shell \n");
}

#define MAXARGS    16
#define MAXPATH    128
#define MAXCMDLINE 1024

#define ST_WHITE 0
#define ST_ARG   1
#define ST_QUOTE 2

#define USE_STAT 0

char** splitargs(char* args) {
	static char* ret[MAXARGS+1];
	int i = 0;
	int state = ST_WHITE;
	while (*args != '\0') {
		if (state == ST_WHITE) {
			if (!isspace(*args)) {
				if(*args == '"') {
					args++;
					state = ST_QUOTE;
					if (*args == '\0')
						break;
				}
				else {
					state = ST_ARG;
				}
				ret[i++] = args;
				if (i == MAXARGS)
					break;
			}
		}
		else {
			if ((state == ST_QUOTE && *args == '"') || isspace(*args)) {
				*args = '\0';
				state = ST_WHITE;
			}
		}
		args++;
	}
	ret[i] = NULL;

	return ret;
}

char* ms_getcwd() {
	return getenv("PWD");
}

void ms_setcwd(char* path) {
	static char newpath[MAXPATH];
	static struct stat stats;

	if (path == NULL)
		return;
	
	if (*path == NULL)
		return;
	
	if (*path == '/') {
		strcpy(newpath, path);
	}
	else {
		char* oldpath = ms_getcwd();

		if(oldpath[strlen(oldpath)-1] == '/')
			sprintf(newpath,"%s%s", oldpath, path);
		else
			sprintf(newpath,"%s/%s", oldpath, path);
	}

#if USE_STAT
	if (stat(newpath, &stats)) {
		printf("mshell: %s is not a valid path\n", newpath);
		return;
	}
	if (!S_ISDIR(stats.st_mode)) {
		printf("mshell: %s is not a directory\n", newpath);
		return;
	}
#endif

	setenv("PWD", newpath, 1);
}


int ms_cat(char* filename)
{
	int fd, r = 0;
	char* buffer = malloc(1024*sizeof(char));

	fd = open(filename, 0, "wr");

	if (fd < 0) {
		printf("cat: No such file or directory");
		printf("\n");
		return -1;
	}		

	do {
		r = read(fd, buffer, 1024);
		write(1, buffer, r);	
	} while (r > 0);

	printf("\n");
	close(fd);
	return 0;
}

int main(int argc, char** argv)
{
	char  commandline[MAXCMDLINE];
	char* command;

	int status = 0;
	pid_t pid;
	system("clear");
	showlogo();
	while(1) {
		char** newargv;
		
		printf("mshell> ");
		gets(commandline);
		
		newargv = splitargs(commandline);
		command = newargv[0];
		
		if(command == NULL) {
			continue;
		}
		if(!strcmp(command, "exit")) {
			return 0;
		}
		if(!strcmp(command, "help")) {
			help();
		} else if (!strcmp(command, "pwd")) {
			printf("%s\n", ms_getcwd());
		} else if (!strcmp(command, "cd")) {
			ms_setcwd(newargv[1]);
		} else if (!strcmp(command, "cat")) {
			ms_cat(newargv[1]);
		} else {
			char path[MAXPATH];
			sprintf(path, "/bin/%s", command);
			pid = fork();
			if (pid == 0) { //child
				status = execve(path, newargv, environ);
				if (status)
					printf("mshell: %s: command not found\n", path);
				return errno;
			} else {
				wait(&status);
			}
		}	
	}
	return errno;
}