/* ut-demo2.c - complex example of using MicroThreads */
/* Jon Mayo - PUBLIC DOMAIN - January 29, 2007 */

#define NDEBUG

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "ut.h"

#define NR(x) (sizeof(x)/sizeof*(x))	/* number of elements in an array */

struct client_state {
	struct ut_state uts;
	unsigned cmdid;
	char tinybuf[16]; /* very small buffer! */
	unsigned len;
	int keep_going;	/* flag if command input should continue */
};

/* returns -1 if there is not a complete word in the buffer */
static int has_word(const char *buf, unsigned len) {
	unsigned i;
	for(i=0;i<len;i++, buf++) {
		if(isspace(*buf)) {
			return i;
		}
	}
	return -1;
}

/* returns -1 if there is not a line in the buffer */
static int has_line(const char *buf, unsigned len) {
	unsigned i;
	for(i=0;i<len;i++, buf++) {
		if(*buf=='\n') {
			return i;
		}
	}
	return -1;
}

/* takes an array of strings and finds a matching entry */
static int word_to_int(const char *word, unsigned wordlen, const char * const *wordlist, unsigned nr_wordlist) {
	unsigned i;
	for(i=0;i<nr_wordlist;i++) {
		if(strncmp(word,wordlist[i],wordlen)==0 
		   && strlen(wordlist[i])==wordlen) { 
			return i;
		}
	}
	return -1;
}

/* runs a command */
static void command_run(struct client_state *cl, int cmdid, const char *args) {
	switch(cmdid) {
		case 0:
			printf("enable: not yet implemented\n");
			break;
		case 1:
			printf("disable: not yet implemented\n");
			break;
		case 2:
			printf("show: not yet implemented\n");
			break;
		case 3:
			printf("service: not yet implemented\n");
			break;
		case 4:
			printf("access-list: not yet implemented\n");
			break;
		case 5:
			printf("ping: not yet implemented\n");
			break;
		case 6:
			printf("diag: not yet implemented\n");
			break;
		case 7:
			printf("configure: not yet implemented\n");
			break;
		case 8:
			printf("reset: not yet implemented\n");
			break;
		case 9:
			printf(	"Help\n"
				"====\n"
				"quit\n"
				"reset\n"
				"configure\n"
				"diag\n"
				"ping\n"
				"access-list\n"
				"service\n"
				"show\n"
				"disable\n"
				"enable\n");
			break;
		case 10:
		case 11:
			cl->keep_going=0;
			break;
		default:
			printf("Unknown command %d\n", cmdid);
	}
}

/* buf - provide length terminated string data.
 * len - pointer to length of string, modified to indicate consumption
 */
static int parser_th(struct client_state *cl, char *buf, unsigned *len) {
	static const char * const command_str[] = {
		"enable",
		"disable",
		"show",
		"service",
		"access-list",
		"ping",
		"diag",
		"configure",
		"reset",
		"help",
		"quit",
		"exit"
	};
	int tmplen;

#ifndef NDEBUG
	printf("%s():enter. len=%d\n", __func__, *len);
#endif
	UT_BEGIN(&cl->uts);

	/* command word */
	UT_WAIT_UNTIL(&cl->uts, (tmplen=has_word(buf, *len)) != -1);

	printf("COMMAND: '%.*s'\n", tmplen, buf);

	/* save the command as a number so we can empty the buffer */
	cl->cmdid=word_to_int(buf, tmplen, command_str, NR(command_str));

	/* eat trailing whitespaces if there are any left in this buffer, does
	 * not eat any of the whitespaces that might be send in the next buffer
	 */
	while(tmplen<*len && buf[tmplen]==' ' && buf[tmplen]=='\t') tmplen++;

	/* shift consumed buffer */
	*len-=tmplen;
	memmove(buf, buf+tmplen, *len);

	/* arguments of command */
	/* TODO: figure out how to consume leading whitespaces */
	/* TODO: handle each argument as a word to minimize buffer space, it
	 * would make this function massively more complex but allow for very
	 * long commands to be entered and make for easier command completion
	 * in the future. */ 
	 UT_WAIT_UNTIL(&cl->uts, (tmplen=has_line(buf, *len)) != -1);

	printf("ARGS: '%.*s'\n", tmplen, buf);

	buf[tmplen++]=0; /* null terminate buffer (write over \n) */
	command_run(cl, cl->cmdid, buf);

	/* shift consumed buffer */
	*len-=tmplen;
	memmove(buf, buf+tmplen, *len);

#ifndef NDEBUG
	printf("%s():complete. len=%d\n", __func__, *len);
#endif
	
	UT_END(&cl->uts);
}

static void client_init(struct client_state *cl) {
	assert(cl != NULL);
	UT_INITIALIZE(&cl->uts);
	cl->cmdid=-1;
	cl->len=0;
	cl->keep_going=1;
	memset(cl->tinybuf, 0, sizeof cl->tinybuf);
}

static int get_command(struct client_state *cl) {
	assert(cl != NULL);
	/* keep calling the thread's reentry until it returns non-zero */
	while(cl->keep_going && (printf("> "),fgets(cl->tinybuf+cl->len, sizeof cl->tinybuf-cl->len, stdin))) {
		/* fill the buffer with data */
		cl->len+=strlen(cl->tinybuf+cl->len);

		if(parser_th(cl, cl->tinybuf, &cl->len)==UT_STATUS_EXITED)  {
			/* completed command */
			printf("\n");
		}

		if(cl->len >= sizeof cl->tinybuf-1) { /* nothing was consumed */
			fprintf(stderr, "error:Buffer Overflow?\n");
			return 0;
		}
		
#ifndef NDEBUG
		printf("%s():Looping...\n", __func__);
#endif
	}
	return 1;
}

int main(int argc, char **argv) {
	struct client_state ex;
	
	client_init(&ex);
	printf("Welcome. type 'help' for help.\n");
	if(!get_command(&ex)) {
		printf("\nThere was a fatal error.\n");
		return EXIT_FAILURE;
	}
	return 0;
}