/**
Small Test Shell
Version 0.0.1
(c) 2005 josh farr

Features
- execute single commands, passing any arguments (the execvp system call searches the PATH env var transparently)
- execute multiple commands in sequence using the command separator ';'
- pipe i/o of arbitrary command chains using the '|' separator (using a recursive exec method)
- built-in 'quit' / 'exit' command to terminate shell

Todo
- i/o redirection (processing of '>' and '<' operators already supported)
- job management ('fg' and 'bg' built-in commands, auto-background with '&' operator)
- error handling for 'command not found'
- command history

Notes
- there may still be problems with argv passing. sometimes passed args aren't recognized by
the child process. possibly an allocation problem.
- the prompt doesn't always get redrawn after a child process finishes. it might be useful 
to rely on terminfo/termcap/curses system methods/libraries to interact with the screen more 
intelligently.

Installation & Usage
g++ shell.cxx -o shell
./shell
**/
#include <unistd.h>
#include <sys/wait.h>

#include <string>
#include <iostream>
#include <vector>

class command
{
	public:
		command() : _connector(NONE), _status(STATUS_NONE), _piper(0), _pid(-1)
		{
		}
		void name(const std::string & str)
		{
			_name = str;
		}
		std::string name(void)
		{
			return _name;
		}
		void arg(const std::string & str)
		{
			_argv.push_back(str);
		}
		std::string args(void)
		{
			std::string str;
			for (unsigned int i = 0; i < _argv.size(); i++)
			{
				if (i > 0)
					str += " ";
				str += _argv[i];
			}
			return str;
		}
		std::vector<std::string> & arg_list(void)
		{
			return _argv;
		}
		void clear(void)
		{
			_name = "";
			_argv.clear();
			_connector = NONE;
			_status = STATUS_NONE;
			_piper = 0;
			_pid = -1;
		}
		typedef enum Connector { NONE, PIPE, REDIRECT_INPUT, REDIRECT_OUTPUT, BACKGROUND } Connector;
		void connector(Connector conn)
		{
			_connector = conn;
		}
		Connector connector(void)
		{
			return _connector;
		}
		int * pipe(void)
		{
			return _pipe;
		}
		void pipe(int * p)
		{
			memcpy(_pipe, p, sizeof(int) * 2);
		}
		typedef enum Status { STATUS_NONE, STATUS_EXECED } Status;
		Status status(void)
		{
			return _status;
		}
		void status(Status s)
		{
			_status = s;
		}
		int pid(void)
		{
			return _pid;
		}
		void pid(int p)
		{
			_pid = p;
		}
		void piper(command * p)
		{
			_piper = p;
		}
		command * piper(void)
		{
			return _piper;
		}
	private:
		std::string _name;
		std::vector<std::string> _argv;
		Connector _connector;
		int _pipe[2];
		Status _status;
		int _pid;
		command * _piper;
};

class chain
{
	public:
		void process(const std::string cmds)
		{
			// break cmds into a list of command's
			std::string piece;
			bool in_arg = false;
			command cmd;
			for (unsigned int i = 0; i < cmds.size(); i++)
			{
				// skip extra whitespace
				if (cmds[i] == ' ' && piece.size() == 0)
					continue;
				if (cmds[i] == ' ')
				{
					// got a command name
					if (!in_arg)
					{
						cmd.name(piece);
						in_arg = true;
					}
					else // got a command argument
					{
						cmd.arg(piece);
					}
					piece = "";
				}
				else if (cmds[i] == '|')
				{
					cmd.connector(command::PIPE);
					_commands.push_back(cmd);
					cmd.clear();
					in_arg = false;
				}
				else if (cmds[i] == '>')
				{
					cmd.connector(command::REDIRECT_OUTPUT);
					_commands.push_back(cmd);
					cmd.clear();
					in_arg = false;
				}
				else if (cmds[i] == '<')
				{
					cmd.connector(command::REDIRECT_INPUT);
					_commands.push_back(cmd);
					cmd.clear();
					in_arg = false;
				}
				else if (cmds[i] == '&')
				{
					cmd.connector(command::BACKGROUND);
					_commands.push_back(cmd);
					cmd.clear();
					in_arg = false;
				}
				else if (cmds[i] == ';')
				{
					_commands.push_back(cmd);
					cmd.clear();
					in_arg = false;
				}
				else
				{
					piece += cmds[i];
				}
			}
			if (piece.size() > 0)
			{
				// got a command name
				if (!in_arg)
				{
					cmd.name(piece);
					in_arg = true;
				}
				else // got a command argument
				{
					cmd.arg(piece);
				}
				piece = "";
				_commands.push_back(cmd);
				cmd.clear();
				in_arg = false;
			}
		}
		void exec(void)
		{
			std::vector<command>::iterator it = _commands.begin();

			for (; it != _commands.end(); it++)
			{
				if (it->status() == command::STATUS_NONE)
					exec(it);
			}
		}
		void exec(std::vector<command>::iterator & cmd)
		{
			// redirect the output of this command to the input of the next command
			if (cmd->connector() == command::PIPE)
			{
				// create a pipe
				int * fd = cmd->pipe();
				if (pipe(fd) == -1)
				{
					std::cerr << "pipe failed" << std::endl;
				}
				// execute the 2nd command first
				// this is the recursive bit
				std::vector<command>::iterator cmd2 = cmd + 1;
				assert(cmd2 != _commands.end());
				cmd2->piper(&(*cmd));
				exec(cmd2);
				// now execute original command
				int pid;
				pid = exec(*cmd);
				assert(pid != 0);
				cmd++;

				// close the file descriptors
				close(fd[0]);
				close(fd[1]);
				// wait for the process to finish
				int status;
				while (wait(&status) != pid);
			}
			else
			{
				// now execute command
				int pid;
				pid = exec(*cmd);
				assert(pid != 0);
			}
		}
		int exec(command & cmd)
		{
			///std::cout << "cmd = " << cmd.name() << " - " << cmd.args() << std::endl;
			// handle built-in commands
			if (cmd.name() == "quit" || cmd.name() == "exit")
			{
				exit(EXIT_SUCCESS);
			}

			// turn the argument list into a c-style array that exec can handle
			std::vector<std::string> & args = cmd.arg_list();
			char **argv = new char *[args.size() + 2];
			// the first argument is the name of the command
			argv[0] = new char [cmd.name().size() + 1];
			strncpy(argv[0], cmd.name().c_str(), cmd.name().size());
			// followed by the actual arguments
			for (unsigned int j = 1; j <= args.size(); j++)
			{
				char * arg = new char [args[j - 1].size() + 1];
				strncpy(arg, args[j - 1].c_str(), args[j - 1].size());
				argv[j] = arg;
			}
			// and terminated with a null entry
			argv[args.size() + 1] = 0;

			// fork a new process for the command to execute in
			pid_t pid;
			pid = fork();
			cmd.pid(pid);
			cmd.status(command::STATUS_EXECED);
			if (pid == 0)
			{
				/**
					if we're piping to or from this command then we need a close/dup2/close call series here.
					if we're piping the output of this command to another command then it's:
					close(fd[0]); dup2(fd[1], 1); close(fd[1]);
					if we're piping the output of another command into this command then it's:
					close(fd[1]); dup2(fd[0], 0); close(fd[0]);
					but in this case the fd set comes from the previous command
				**/
				command * prev;
				prev = cmd.piper();
				// if there's a piper, it's the command before this one
				if (prev != 0)
				{
					// we redirect the output of the piper to the input of this command
					int * fd = prev->pipe();
					close(fd[1]); 
					dup2(fd[0], 0); 
					close(fd[0]);
				}
				if (cmd.connector() == command::PIPE)
				{
					// we redirect the output of this command to the next command
					int * fd = cmd.pipe();
					close(fd[0]); 
					dup2(fd[1], 1); 
					close(fd[1]);
				}
				// execute the command
				int eval;
				eval = execvp(cmd.name().c_str(), argv);
				return 0;
			}

			// cleanup our c-style array
			for (unsigned int j = 0; j < args.size(); j++)
			{
				delete argv[j];
			}
			delete [] argv;

			return pid;
		}
	private:
		std::vector<command> _commands;
};

int main(int argc, char *argv[])
{
	const std::string prompt = "small-sh # ";

	while (1)
	{
		// display the prompt
		std::cout << prompt;

		std::string cmd = "";
		chain cli;
		char in = '\0';
		// don't eat newlines
		std::cin.ignore(0, '\n');
		// process input a character at a time
		while ((in = std::cin.get()) != EOF)
		{
			// if CR reached then process the line and start the main loop over
			if (in == '\n')
			{
				cli.process(cmd);
				cli.exec();
				break;
			}
			cmd += in;
		}
	}
	return 0;
}


