///////////////////////////////////////////////////////////////////////////////
// File   :  ksh.c
// Date   :  October 2017
// Author :  Alain Greiner
///////////////////////////////////////////////////////////////////////////////
// This single thread application implement a simple shell for ALMOS-MKH.
///////////////////////////////////////////////////////////////////////////////

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

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

////////////////////////////////////////////////////////////////////////////////
//	   Structures
////////////////////////////////////////////////////////////////////////////////

// one entry in the registered commands array
typedef struct log_entry_s 
{
	char          buf[CMD_MAX_SIZE];
	unsigned int  count;
}
log_entry_t;

// one entry in the supported command types array
typedef struct ksh_cmd_s 
{
	char * name;
	char * desc;
	void   (*fn)( int , char ** );
}
ksh_cmd_t;


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

ksh_cmd_t       cmd[];                    // array of supported commands

log_entry_t     log_entries[LOG_DEPTH];   // array of registered commands

unsigned int    ptw;                      // write pointer in log_entries[]
unsigned int    ptr;                      // read pointer in log_entries[]

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

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

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

	path = argv[1];

    printf("error: not implemented yet\n");

/*
	// 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);
*/

}   // end cmd_cat()

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

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

	path = argv[1];

    printf("error: not implemented yet\n");
/*
	path = argv[1];

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

}   // end cmd_cd()

/////////////////////////////////////////
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;
	}

    printf("error: not implemented yet\n");

/*
	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);
*/

}   // end cmd_cp()

//////////////////////////////////////////////
static void cmd_help( int argc , char **argv )
{
	unsigned int i;

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

	printf("available commands:\n");
	for (i = 0 ; cmd[i].name ; i++) 
    {
		printf("\t%s\t : %s\n", cmd[i].name , cmd[i].desc);
	}
}   // end cmd_help()

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

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

	pid = atoi(argv[1]);

	if( kill( pid , 9 ) )   // TODO replace 9 by SIGKILL
    {
		printf("error: unable to kill process %x\n", pid );
	}
}   // end cmd_kill()

//////////////////////////////////////////////
static void cmd_load( int argc , char **argv )
{
	unsigned int         pid;
    unsigned int         error;
	char               * pathname;

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

	pathname = argv[1];

    // fork system call
	pid = fork();

    if (pid == 0)  // it is the child process 
    {
        printf("CHILD\n");
        exit(0);

        // exec system call
        // error = exec( pathname , NULL , NULL );

        // if( error )
        // {
        //    printf("error: new process unable to exec <%s>\n", pathname );
        //    exit();
        // }
	} 
    else if ( pid < 0 )  // it is a failure reported to parent
    {
        printf("error: unable to fork\n");
    }
    else                 // it is a success reported to parent
    {
        printf("PARENT\n");
        exit(0);
    }

}   // end cmd_load

/////////////////////////////////////////////
static void cmd_log( int argc , char **argv )
{
	unsigned int i;

	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 )
{
	char  * path;

//  struct dirent * file;
//  DIR *dir;

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

    printf("error: not implemented yet\n");
/*
	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 * pathname;

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

    pathname = argv[1];

    printf("error: not implemented yet\n");
/*
	if ( mkdir( path, 0x700) == -1 )
    {
		printf("error: cannot create directory %s\n", path);
	}
*/
}

////////////////////////////////////////////
static void cmd_mv( int argc , char **argv )
{

	if (argc < 3)
	{
		printf("  usage : %s src_pathname dst_pathname\n", argv[0]);
		return;
	}

    printf("error: not implemented yet\n");
    
/*
	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 );
	}
*/

}

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

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

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

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

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

	pathname = argv[1];

    printf("error: not implemented yet\n");
/*
	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_sched( int argc , char **argv )
{
    unsigned int cxy;
    unsigned int lid;

	if (argc != 3)
    {
		printf("usage: sched cxy lid\n");
		return;
	}

	cxy = atoi(argv[1]);
	lid = atoi(argv[2]);

    if( get_sched( cxy , lid ) )
    {
        printf("error: illegal arguments\n");
    }
}

//////////////////////////////////////////////////////////////////
// Array of commands
//////////////////////////////////////////////////////////////////

ksh_cmd_t cmd[] =
{
	{ "cat",    "display file content",                cmd_cat   },
	{ "cd",     "change current directory",            cmd_cd    },
	{ "cp",     "replicate a file in file system",     cmd_cp    },
	{ "load",   "load an user application",            cmd_load  },
	{ "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 },
    { "sched",  "display scheduler state",             cmd_sched },
	{ NULL,     NULL,								   NULL      }
};

////////////////////////////////////////////////////////////////////////////////////
// This function analyses one command (with arguments), execute it, and return.
////////////////////////////////////////////////////////////////////////////////////
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 
		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[CMD_MAX_SIZE];		  // buffer for one command
	unsigned int count = 0;				  // pointer in buf
	unsigned int i;						  // index for loops

	enum fsm_states 
    {
		NORMAL,
		ESCAPE,
		BRAKET,
	};

    // log buffer initialisation
	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 three 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;

// @@@
parse("load /bin/user/sort.elf");
// @@@


    
	while (1)
	{
		c = (char)getchar();

        if( c == 0 ) continue;

		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;
			}
		}
	}
}

