//Deian Stefan
//sfsh - Sugar Free Shell
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define MAXLINE 1024
#define MAXARG  1024
#define MAXOPS	   3

#define OP_UNDEF	0x0 
#define OP_IN		0x1	/*	<	*/
#define OP_OUT	    0x2	/*	>	*/
#define OP_ERR	    0x3	/*	2>	*/
#define OP_OUT_APP  0xA	/*	>>	*/
#define OP_ERR_APP  0xB	/*	2>>	*/
#define OP_MASK    	0X7	/* 	apped	*/

#define SFSH_VERSION 1.618


struct r_op {
	int op;
	char fname[MAXARG];
};
	
void prompt() {
	struct passwd *pwd;
	printf("%s %.3f-$ ",(pwd=getpwuid(getuid()))?pwd->pw_name:"",SFSH_VERSION);
}

int is_op(char *buf) {
	return (!strcmp(buf,"<"))?OP_IN:(!strcmp(buf,">"))?OP_OUT:(!strcmp(buf,">>"))?OP_OUT_APP:(!strcmp(buf,"2>"))?OP_ERR:(!strcmp(buf,"2>>"))?OP_ERR_APP:OP_UNDEF;
}

int execute(char *cmd[],struct r_op ops[],int dangling_fd) {
	
	int pid,cfd,status,i;
	struct rusage ru;
	char **cf;
	struct timeval real_time[2];

	switch(pid=fork()) {
		case -1:
			fprintf(stderr,"fork() failed to to create process %s: %s\n",cmd[0],strerror(errno));
			return -1;
		case 0:
			if(dangling_fd) { 
				if(close(dangling_fd)) {
				       fprintf(stderr,"Faild to close file descriptor %d: %s\n",dangling_fd,strerror(errno));
				       exit(-1);
				} //dont close stdin, close a file if it was opened in parent
			}
			cf=cmd;
			cf++;

			fprintf(stderr,"---------------------------------------------------------\n");
			fprintf(stderr,"| Executing command %s with arguments \"",cmd[0]);
			while(*cf!=NULL) {
				fprintf(stderr,"%s ",*cf++);
			}
			fprintf(stderr,"\"\n");
			fprintf(stderr,"---------------------------------------------------------\n\n");

			for(i=0;i<MAXOPS;i++) {
				if((ops[i]).op) { // redirect io
					switch((ops[i]).op) {
						case OP_IN:
							cfd=open((ops[i]).fname,O_RDONLY);
							break;
						case OP_OUT:
						case OP_ERR:
							cfd=open((ops[i]).fname,O_WRONLY|O_CREAT|O_TRUNC,0644);
							break;
						case OP_OUT_APP:
						case OP_ERR_APP:
							cfd=open((ops[i]).fname,O_WRONLY|O_CREAT|O_APPEND,0644);
							break;
						default:
							fprintf(stderr,"Could not determine file mode.\n");
							exit(-1);
					}
					if(cfd<0) {
						fprintf(stderr,"Failed to open file %s: %s\n",(ops[i]).fname,strerror(errno));
						exit(-1);
					}
					if(dup2(cfd,i)<0) {
						fprintf(stderr,"Failed to dup2 file %s to fd %d: %s\n",(ops[i]).fname,i,strerror(errno));
						exit(-1);
					}
					if(close(cfd)) {
					       fprintf(stderr,"Faild to close file descriptor %d: %s\n",cfd,strerror(errno));
					       exit(-1);
					}
				}
			}

			if(execvp(cmd[0],cmd)) {
				fprintf(stderr,"Failed to exec %s: %s\n",cmd[0],strerror(errno));
				exit(-1); //exit forked process
			}
			break;

		default:


			if(gettimeofday(&real_time[0],NULL)) {
				fprintf(stderr,"gettimeofday() failed: %s\n",strerror(errno));
				return -1;
			}
			if(wait3(&status,0,&ru) <0) {
				fprintf(stderr,"wait3() failed: %s\n",strerror(errno));
				return -1;
			} else {

				if(gettimeofday(&real_time[1],NULL)) {
					fprintf(stderr,"gettimeofday() failed: %s\n",strerror(errno));
					return -1;
				}
				fprintf(stderr,"\n---------------------------------------------------------\n");
				fprintf(stderr,"|Command returned with return code %d,\n",WEXITSTATUS(status));
				fprintf(stderr,"|%ld.%.6d real seconds, ",real_time[1].tv_sec-real_time[0].tv_sec,real_time[1].tv_usec-real_time[0].tv_usec);
				fprintf(stderr,"%ld.%.6d user seconds, ",ru.ru_utime.tv_sec,ru.ru_utime.tv_usec);
				fprintf(stderr,"%ld.%.6d system seconds, \n",ru.ru_stime.tv_sec,ru.ru_stime.tv_usec);
				if(WIFSIGNALED(status)) {
					fprintf(stderr,"Command exited with signal %d.\n",WTERMSIG(status));
					fprintf(stderr,"---------------------------------------------------------\n");
					return -1;
				} else if(WIFSTOPPED(status)) {
					fprintf(stderr,"Command stopped with signal %d.\n",WSTOPSIG(status));
					fprintf(stderr,"---------------------------------------------------------\n");
					return -1;
				}
				fprintf(stderr,"---------------------------------------------------------\n");
			}
			break;
	}
	
	return 0;
}

// command arguments < file 1 > file2 2> file3
int parse(char *line,int dangling_fd) {
	char *at, *cmd[MAXARG],*buf;
	int cmd_done=0,op=0,opf=0,isline=0;
	struct r_op ops[MAXOPS]; // 0 - stdin, 1 - stdout, 2 - stderr redirection
	char **cf=cmd;

	(ops[0]).op=(ops[1]).op=(ops[2]).op=OP_UNDEF;

	if(!strlen(line)) { return -1; }//empty line 
	line[strlen(line)-1]='\0'; //chomp
	if((at=strchr(line,'#'))) { *at='\0'; } //get rid of comment

	while((strlen(line))) {
		if(!(buf=malloc(sizeof(char)*MAXARG))){
			fprintf(stderr,"Could not allocate buffer memory for parsing: %s\n",strerror(errno));
			return -1;
		}

		if(sscanf(line,"%s",buf)!=1) { break; }
		isline=1;

		if(is_op(buf)) {
			if(!cmd_done) { cmd_done=1; *cf=NULL; }
			if(op&&!opf) {
				fprintf(stderr,"Syntax error near unexpected token `%s'.\n",buf);
				return -1; // avoid lines like: cat < > 
			}
			op=is_op(buf);
			opf=0;
			line=strstr(line,buf)+strlen(buf);
			ops[(OP_MASK&op)-1].op=op;
			at=ops[(OP_MASK&op)-1].fname;
			cf=&at;
			//lines like: cat < file1 file2 -- will result only in file2 being used
		} else {
			if(!op&&!(*cf=malloc(sizeof(char)*MAXARG))){
				fprintf(stderr,"Could not allocate argument memory for parsing: %s\n",strerror(errno));
				return -1;
			}
			strncpy(*cf,buf,MAXARG-1);
			(*cf)[MAXARG-1]='\0'; 
			line=strstr(line,*cf)+strlen(*cf);
			if(!op) {
				cf++;
			} else {
				opf=1;
			}
		}
		free(buf);
	}
	if(!isline) { return -1; } //empty line

	if(!cmd_done) { cmd_done=1; *cf=(char *)0; } // when no io redirection operators

	if(op&&!opf) { //trailing io redirection operator
		fprintf(stderr,"Syntax error at the end of line.\n");
		return -1;
	}


	if(!strcmp(cmd[0],"exit")) { 
		if(close(dangling_fd)) { 
		       fprintf(stderr,"Faild to close file descriptor %d: %s\n",dangling_fd,strerror(errno));
		       exit(-1);
		}
		exit(0); 
	} //exit from shell
	if(!strcmp(cmd[0],"about")) {
		fprintf(stderr,"sfsh %.3f - Sugar Free Shell written by Deian Stefan <stefan@cooper.edu>.\n",SFSH_VERSION);
		return 0;
	}
	return 	execute(cmd,ops,dangling_fd);
}


int main(int argc, char *argv[]) {
	char line[MAXLINE];
	FILE *fp;
	fp=stdin;

	if(argc==2) {
		if(!(fp=fopen(argv[1],"r"))) {
			fprintf(stderr,"Failed to open script %s: %s\n",argv[1],strerror(errno));
			return -1;
		}
	}
			
	(fp!=stdin)?:prompt();
	while(fgets(line,sizeof(line),fp)) {
		parse(line,fileno(fp));
		(fp!=stdin)?:prompt();
	}
	if(ferror(fp)) {
		fprintf(stderr,"Failed to read command from stream: %s\n",strerror(errno));
		return -1;
	}
	return 0;
}

