/********* CS 414 Assignment 2 **********
 ********* Omar Khan    - ohk2 **********
 ********* Murali Kumar - mk262**********
 */

#include <stdio.h>
#include <stdlib.h>

#ifdef WINDOWS
#include <windows.h>
#include <string.h>
#endif

#ifdef UNIX
#include <strings.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <readline/readline.h>
#include <readline/history.h>
#endif

#include "ourshell.h"

/***************** global variables ***************/

struct backgrndList_t * backgrndList=NULL;    // head of the list of non-terminated background jobs
struct backgrndList_t * backgrndListEnd=NULL; // tail  "      "         "         "
char * history[MAX_HIST];                     // an array to store the history of commands entered
int totalCmds = 0;							  // total number of commands entered till now by the user
int numBackgrndJobs = 1;                      // counter for the job number of background job created
int globalStatus = 0;                         // a status of 1 indicates a background job has terminated


#ifdef UNIX
		sigset_t block_sigchild;              // a sigset_t variable for blocking
#endif


#ifdef UNIX
/* Input   : signum - the signal number returned
 * Purpose : A signal handler for the UNIX shell
 *			 Handles SIGCHLD signal when a background child process terminates
 *           sets globalStatus and the isDone variable corresponding to the 
 *           terminated job to 1
 * Return  : none
 */
void
childHandler(int signum){
	int old_error = errno;
	struct backgrndList_t * j = backgrndList;
	register int pid;

	do
		{
			errno = 0;
			pid = waitpid(WAIT_ANY, NULL, WNOHANG | WUNTRACED);
		}
	while (pid <= 0 && errno == EINTR);

	if (pid <= 0){
		/* There are no more stopped or terminated children */
		errno = old_error;
		return;
	}
	/* check which job in the backgrnd list has terminated and set its isDone to 1*/
	for (j=backgrndList;j!=NULL;j=j->next){
		if (j->pid == pid) {
			j->isDone = 1;
			globalStatus = 1;
		}
	}
	return;
}
#endif


#ifdef WINDOWS
/* Input   : noned
 * Purpose : A signal handler for the WINDOWS shell
 *			 checks if any background process has terminated. If so,
 *           sets globalStatus and the isDone variable corresponding to the 
 *           terminated job to 1
 * Return  : none
 */
void
childHandler(){
	DWORD exitCode;
	struct backgrndList_t * j;
	for (j=backgrndList;j!=NULL;j=j->next){
		GetExitCodeProcess(j->pi.hProcess, &exitCode);
		if (exitCode != STILL_ACTIVE){
			TerminateProcess(j->pi.hProcess, 42);
			j->isDone = 1;	
			globalStatus = 1;
		}
	}
}
#endif


/* Input   : cmdInfo - the structure containing information about the current command
 * Purpose : Remove jobs from background list whose isDone has been set to 1
 *           FREE all malloced memory related to the present command
 * Return  : none
 */
void
jobHandlerAndCleanup(struct cmdInfo_t * cmdInfo){
	char ** i;
#ifdef WINDOWS
	childHandler();
#endif

#ifdef UNIX
	sigprocmask(SIG_BLOCK, &block_sigchild, NULL);
#endif
	if (globalStatus)
	{
		struct backgrndList_t *j;
		struct backgrndList_t *previous;
	
		/* reset global status to 0 */
		globalStatus = 0; 
	
		/* remove those jobs from background list which have isDone = 
		 * and update the head and tail pointers
		 */
		if (backgrndList == backgrndListEnd){
			if (backgrndList->isDone){
				printf("[%d] Done \t\t%s\n",backgrndList->jobNum, backgrndList->cmdLine);
				FREE(backgrndList->cmdLine);
				#ifdef WINDOWS
				CloseHandle(backgrndList->pi.hProcess);
				CloseHandle(backgrndList->pi.hThread);
				#endif
				FREE(backgrndList);
				backgrndList = NULL;
				backgrndListEnd = NULL;
			}
		}
		else{
			while (backgrndList->isDone){
				printf("[%d] Done \t\t%s\n",backgrndList->jobNum, backgrndList->cmdLine);
				FREE(backgrndList->cmdLine);
				#ifdef WINDOWS
				CloseHandle(backgrndList->pi.hProcess);
				CloseHandle(backgrndList->pi.hThread);
				#endif
				j = backgrndList->next;
				FREE(backgrndList);
				backgrndList = j;
			}

			previous=backgrndList;
			j=backgrndList;

			while (j!=NULL){

				if (j->isDone){
					printf("[%d] Done \t\t%s\n",j->jobNum, j->cmdLine);
					previous->next = j->next;
					FREE(j->cmdLine);
					#ifdef WINDOWS
					CloseHandle(j->pi.hProcess);
					CloseHandle(j->pi.hThread);
					#endif
					FREE(j);
					if (previous->next == NULL) backgrndListEnd = previous;
					j = previous->next;
				}
				else{
					previous = j;
					j = j->next;
				}
			}
		}
	}
#ifdef UNIX
	sigprocmask(SIG_UNBLOCK, &block_sigchild, NULL);
#endif

	/* clean up memory from last use */
	
	FREE(cmdInfo->myStdin);
	FREE(cmdInfo->myStdout);
	FREE(cmdInfo->cmd);

	for (i = cmdInfo->args; *i != NULL; i++){
		FREE(*i);
	}
}


/* Input   : cmdLine - the command line entered                 
 * Purpose : checks if the command entered is one of the in-built commands supported by the shell
 * Return  : the corresponding value in the enum BUILTIN_COMMANDS (look in ourshell.h)
 */
int
isBuiltInCommand(char * cmdLine){
  
  if ( strncmp(cmdLine, "exit", strlen("exit")) == 0){
    return EXIT;
  }
  else if ( strncmp(cmdLine, "kill", strlen("kill")) == 0){
    return KILL;
  }
  else if ( strncmp(cmdLine, "jobs", strlen("jobs")) == 0){
    return JOBS;
  }
  else if ( strncmp(cmdLine, "cd", strlen("cd")) == 0){
    return CD;
  }
  else if ( strncmp(cmdLine, "history", strlen("history")) == 0){
    return HISTORY;
  }
  else if ( strncmp(cmdLine, "help", strlen("help")) == 0){
    return HELP;
  }
  else return NO_SUCH_BUILTIN; 
}



/* Input   : cmdLine - the command entered at the shell prompt
 *           cmdInfo - the structure in which to store information about the current command
 * Purpose : Parses the command line string and extracts information to be stored in
 *           the corresponding cmdInfo structure.
 * Return  : OK if a valid command was entered, FAIL otherwise
 */
int
Parser (char * cmdLine, struct cmdInfo_t * cmdInfo){
	
	char * result = NULL;
	char * temp = NULL;
	char delims[] = " ";
	int argnum = 1;
	int argFlag = 1;
	int badcmd = 0;
	char * i;
	char * lastslash;
	 
	/* intializing */
	cmdInfo->builtInArg = 0;
	cmdInfo->isFG = 0;
  
	temp = strdup(cmdLine);
    result = strtok(temp, delims );

    if( result != NULL ) {
        cmdInfo->cmd = strdup(result);
		lastslash=result;
		for (i=result;*i!='\0';i++){
			if (*i == '/') lastslash = i+1;
		}
		cmdInfo->args[0] = strdup(lastslash);
		cmdInfo->args[1] = NULL;
    }
	else return FAIL;

	/* determine if it is an inbuilt command or an external one */
	cmdInfo->p = isBuiltInCommand(cmdInfo->cmd);

	if(cmdInfo->p == KILL) {
		result = strtok(NULL, delims);
		if (result == NULL) return FAIL;
		cmdInfo->builtInArg=atoi(result);
     /* for anything other than KILL<positive number>,return FAIL  */
		if (cmdInfo->builtInArg <= 0) return FAIL; 
	}

	if (cmdInfo->p != NO_SUCH_BUILTIN && cmdInfo->p != CD) return OK;

	result = strtok( NULL, delims );
 
	if (result!= NULL){
		while(1){
	    
			if (strcmp(result,"&") == 0) {return OK;}
		 
			else if ((strcmp(result,"<")) == 0) {
				result = strtok(NULL, delims);
				if (result == NULL){
					printf("No input file \n");
					badcmd = 1;
				}
				else{
					cmdInfo->myStdin = strdup(result);
					result = strtok(NULL, delims);
				}
				argFlag=0;
			}
			else if((strcmp(result,">")) == 0) {
				result = strtok(NULL, delims);
				if (result == NULL){
					printf("No output file \n");
					badcmd = 1;
				}
				else{
					cmdInfo->myStdout = strdup(result);
					result = strtok(NULL, delims);
				}
				argFlag=0;
			}

			if (argFlag==1) {
				cmdInfo->args[argnum++]=strdup(result);
				cmdInfo->args[argnum]=NULL;
				result = strtok(NULL, delims);
			}
			
			if (result == NULL) {
				break;
			}
		}
	}
	cmdInfo->isFG=1;
	FREE(temp);
	FREE(result);
	if (badcmd) {return FAIL;}
	else {return OK;}   
}


/* Input   : cmdInfo - the structure containing information about the current command
 * Purpose : executes the built-in commands : HISTORY, JOBS, CD, KILL and HELP 
 *           the corresponding cmdInfo structure.
 * Return  : OK if a valid command was entered, FAIL otherwise
 */

int executeBuiltInCommand(struct cmdInfo_t * cmdInfo){
  int start;
  int i;
  struct backgrndList_t *j;

  if (cmdInfo->p == HISTORY){
    start = (totalCmds < MAX_HIST)? 0 : (totalCmds - MAX_HIST)%MAX_HIST;
    for(i = 0; i < MIN(totalCmds, MAX_HIST); i++){
      printf("[%d] %s\n", (totalCmds<MAX_HIST)? i+1 : totalCmds - MAX_HIST+1+i, history[(start+i)% MAX_HIST]);
    }
    return OK;
  }

  else if (cmdInfo->p == JOBS){
    for (j=backgrndList;j!=NULL;j=j->next){
      printf("[%d] %s\n", j->jobNum, j->cmdLine);
    }
    return OK;
  }

  else if (cmdInfo->p == CD){
    if (cmdInfo->args[1] == NULL) {return FAIL;}
    else{
		#ifdef UNIX
			if ((chdir(cmdInfo->args[1]) != 0)) {
				printf("No such directory \n");
				return FAIL;
			}
			else {return OK;}
		#endif

		#ifdef WINDOWS
			if ((SetCurrentDirectory(cmdInfo->args[1]) == 0)) {
				printf("No such directory \n");
				return FAIL;
			}
			else {return OK;}
		#endif
	}
  }

  else if (cmdInfo->p == KILL){
    struct backgrndList_t * i;
    for (i=backgrndList;i!=NULL;i=i->next){
      if ((i->jobNum) == (cmdInfo->builtInArg)){
		#ifdef UNIX
				kill(i->pid, SIGKILL);
		#endif

		#ifdef WINDOWS
				TerminateProcess(i->pi.hProcess, 42);
				i->isDone = 1;
		#endif
		return OK;
      }
    }
    fprintf(stderr, "No such process\n");
    return FAIL;
  }

  /*********** should we execute EXIT here *********??? */

  else if (cmdInfo->p == HELP){
	printf("cd <relative or absolute path> \n");
	printf("kill jobNumber \n");
	printf("jobs \n");
	printf("history \n");
	printf("exit \n");
	return OK;
  }
  return FAIL;
}
 
/* Input   : backgrndJob - structure corresponding to the current background job                 
 * Purpose : adds backgrndJob to the list of unterminated background jobs
 * Return  : none
 */
void 
addBackgrndJob(struct backgrndList_t * backgrndJob)
{
	
	backgrndJob->jobNum=numBackgrndJobs++;
	backgrndJob->isDone=0;
     	backgrndJob->next=NULL;

	if (backgrndList==NULL) {
		backgrndList=backgrndJob;
		backgrndListEnd=backgrndJob;
	}
	else {
		backgrndListEnd->next=backgrndJob;
		backgrndListEnd=backgrndJob;
	}
	#ifdef UNIX
	printf("[%d] %d\n",backgrndJob->jobNum, backgrndJob->pid);
	#endif
}


/* Input   : none            
 * Purpose : builds the commands prompt to be displayed at the shell
 *           the prompt displays the current working directory
 * Return  : the command prompt string created
 */
char *
buildPrompt()
{
  char * prompt;
  char * temp;
  size_t templen;

#ifdef WINDOWS
  temp = malloc(1000*sizeof(char));
  GetCurrentDirectory(1000, temp);
#endif

#ifdef UNIX
 temp = (char *) get_current_dir_name();
#endif 

  templen = strlen(temp);
  prompt = malloc((templen+2)*sizeof(char)); 
  strcpy(prompt,temp);

#ifdef UNIX
  prompt[templen]='%';
#endif

#ifdef WINDOWS
  prompt[templen] = '>';
#endif

  prompt[templen+1] = '\0';
  FREE(temp);  
  return prompt;
}


/************* the main function **************/

int 
main (int argc, char **argv)
{
	struct cmdInfo_t cmdInfo;  
	char * cmdLine;
	char * prompt;
	int status;
	char buffer[1000];
	char *i;
	struct backgrndList_t *backgrndJob;

	#ifdef UNIX
	int childPid;
	#endif
	

	#ifdef UNIX
		fprintf(stdout, "This is the UNIX version\n");
		sigemptyset(&block_sigchild);
		sigaddset(&block_sigchild, SIGCHLD);
		signal(SIGCHLD, childHandler);    
	#endif

	#ifdef WINDOWS
		fprintf(stdout, "This is the WINDOWS version\n");
	#endif


    /* the while loop */
	while(1){

		prompt = buildPrompt();
		fputs(prompt, stdout);
		FREE(prompt);
		gets(buffer);
		cmdLine = strdup(buffer);
	
		cmdInfo.cmd = NULL;
		cmdInfo.myStdin = NULL;
		cmdInfo.myStdout = NULL;
		cmdInfo.args[0] = NULL;
	
		if (*cmdLine == '\0') {
			jobHandlerAndCleanup(&cmdInfo);
			continue;
		}  

		if (cmdLine[0] == '!'){
			int start = (totalCmds <= MAX_HIST)? 1 : (totalCmds - MAX_HIST + 1); 
			int i = atoi(cmdLine + 1);
			FREE(cmdLine);
			if (i < 0){
				i = totalCmds + i + 1;
			}
			if (i >= start && i <= totalCmds){
				cmdLine = strdup(history[(i-1)%MAX_HIST]);
				/* set buffer to cmdLine as well */
				
				strcpy(buffer, history[(i-1)%MAX_HIST]);
			}
			else{
				fprintf(stderr, "Event not found\n");
				continue;
			}
		}
	
		status = Parser(cmdLine, &cmdInfo);
	
		/* increment totalCmd and add to history array */
 
		if (totalCmds >= MAX_HIST){
			FREE(history[(totalCmds % MAX_HIST)]);
		}
		history[(totalCmds % MAX_HIST)] = cmdLine;
		totalCmds++;
	 

		if (status == FAIL) { 
			printf("Bad command!\n");
		}
		else if (cmdInfo.p != NO_SUCH_BUILTIN){
			executeBuiltInCommand(&cmdInfo);
		}
		else{
	  
			#ifdef UNIX
				sigprocmask (SIG_BLOCK, &block_sigchild, NULL);

				childPid = fork();
				/* if we are in the child */
				if (childPid == 0){
					if (cmdInfo.myStdin != NULL){
						int file = open(cmdInfo.myStdin, O_RDONLY);
						if (file == -1){
						  printf("%s: No such file or directory \n", cmdInfo.myStdin);
						  sigprocmask(SIG_UNBLOCK, &block_sigchild, NULL);
						  exit(1);
						}
						dup2(file, STDIN_FILENO);
						close(file);
					}

					if (cmdInfo.myStdout != NULL){
						int file;
						/* umask is not working for some reason
							* creates a file with write permission only
						mode_t mask = umask(0);
						umask(mask);*/
						file = open(cmdInfo.myStdout, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
						dup2(file, STDOUT_FILENO);
						close(file);
					}
					if (execvp(cmdInfo.cmd,cmdInfo.args) == -1){
					  printf("%s: command not found \n", cmdInfo.cmd);
					  exit(1);
					}
					
				}
				else{
					if (cmdInfo.isFG) {
						waitpid(childPid, NULL, 0);
					}
					else {
						backgrndJob = malloc(sizeof(struct backgrndList_t));
						backgrndJob->pid=childPid;
						backgrndJob->cmdLine=strdup(cmdLine);
						addBackgrndJob(backgrndJob);
					}
				}
			
				/* unblock SIGCHLD */
				sigprocmask(SIG_UNBLOCK, &block_sigchild, NULL);
		
			#endif

			#ifdef WINDOWS

				STARTUPINFO si;
				PROCESS_INFORMATION pi;
				BOOL fInheritHandles=FALSE;
				SECURITY_ATTRIBUTES saAttr; 

				ZeroMemory( &si, sizeof(si) );
				si.cb = sizeof(si);
				ZeroMemory( &pi, sizeof(pi) );
								
				if (cmdInfo.myStdin || cmdInfo.myStdout){
					si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
					si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
					si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
					si.dwFlags = STARTF_USESTDHANDLES;
					fInheritHandles=TRUE;
					saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
					saAttr.bInheritHandle = TRUE; 
					saAttr.lpSecurityDescriptor = NULL;
				}
				
				/* check for I/O redirection and set stdin and stdout appropriately */
				if (cmdInfo.myStdin != NULL){
				
					si.hStdInput = CreateFile(cmdInfo.myStdin, GENERIC_READ, 0, &saAttr, 
											OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
				}

				if (cmdInfo.myStdout != NULL){
					
					si.hStdOutput = CreateFile(cmdInfo.myStdout, GENERIC_WRITE, 0, &saAttr, 
											CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 
				}


				/* filter out the proper command to be passed to CreateProcess 
				 * after getting rid of everything beyond arguments(if any) 
				 */
				for (i=buffer;*i !='\0';i++) {
					if (*i=='&' || *i =='<' || *i == '>') {
						*i='\0'; 
						break;
					}
				}
				

				/************************* Create the child process *****************************/

				if( !CreateProcess( NULL, buffer, NULL, NULL, fInheritHandles, 0, NULL, NULL, &si, &pi )) {
						fprintf( stderr, "'%s' is not recognized as an internal or external command, operable program or batch file\n", cmdInfo.cmd );
						jobHandlerAndCleanup(&cmdInfo);
						continue;
				}
				
				if (cmdInfo.isFG){
					WaitForSingleObject( pi.hProcess, INFINITE );
					CloseHandle( pi.hProcess );
					CloseHandle( pi.hThread );
				}
				else{
					backgrndJob = malloc(sizeof(struct backgrndList_t));
					backgrndJob->pi = pi;
					backgrndJob->cmdLine=strdup(cmdLine);
					addBackgrndJob(backgrndJob);
				} 

				if (cmdInfo.myStdin) {
					CloseHandle(si.hStdInput);
				}
				if (cmdInfo.myStdout) {
					CloseHandle(si.hStdOutput);
				}
				
				
				
			#endif
		}
	
		/* when do we trash the input and output redir handles ??? */


		/* check for terminated backgrnd jobs and clean up memory from last use */
		jobHandlerAndCleanup(&cmdInfo);
	
		/* execute the exit command here
		 * check for any unterminated background processes */
		if (cmdInfo.p == EXIT) {
			if (backgrndList != NULL){
				struct backgrndList_t *j;
				printf("The following background processes still exist: \n");
				for (j=backgrndList;j!=NULL;j=j->next){
					printf("[%d] %s\n", j->jobNum, j->cmdLine);
				}
				printf("You need to kill or exit all background processes \n");
				printf("Exit failed !\n");
				continue;
			}
			else{
			  exit(1);
			}
		}
		
  }/* end of while(1) */
}/* end of main */
