/* libcli -- a small commandline interpreter libraray
 * Copyright (C) 2002 Øyvind Kolås
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
 
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "cli.h"
#include "cli_tokenize.h"

/*#define HIDE_NULL_HELP
*/
/* TODO:

	allow removal of commands/variables
	scripts? (with simple flow-control?)
*/

#ifdef WIN32
#define snprintf(a,b,args...) sprintf(a,args)
#endif
/*
	wordwrapping outputting function
*/
static void default_output(char *data){
	#define COLS 78
	char *tbuf=malloc(COLS+1);
	char *word=malloc(COLS+1);
	char *bp=tbuf,
	     *wp=word,
		 *dp=data;
	
	*bp=*wp='\0';
	
	while(1+1==2){
		if(isspace((unsigned char)*dp) || *dp=='\0'){
			if( (bp-tbuf) + (wp-word) +1 < COLS){
				strcpy(bp,word);
				bp+=(wp-word);
				*(bp++)=' ';
				*bp='\0';
				wp=word;
				*wp='\0';
			} else {
				printf(tbuf);printf("\n");
				bp=tbuf;
				*bp='\0';
				strcpy(bp,word);
				bp+=(wp-word);
				*(bp++)=' ';
				*bp='\0';
				wp=word;
				*wp='\0';
			}
			if(!*dp)break;
		} else {
			if(wp-word>=COLS){
				printf("%s\n",tbuf);
				printf("%s\n",word);
				wp=word;
			}
			*(wp++)=*dp;
			*wp='\0';
		}
		dp++;
	}
	printf("%s\n",tbuf);
	
	free(word);
	free(tbuf);
}

static void default_unknown_command (int argc,char **argv, void *data){
	cli_outfunf ("unknown command '%s' type '?' to see allowed commands.\n",argv[0]);
}

void (*cli_outfun) (char *) = default_output;
void (*cli_precmd) (char *) = NULL;
void (*cli_postcmd) (char *) = NULL;
void (*cli_unknown) (int,char **,void *) = default_unknown_command;
int cli_width = 40;


void cli_outfunf(char *format, ...){
	va_list arglist;
	char buf[128];
	
	va_start( arglist, format );
	vsnprintf(buf,127,format,arglist);
	va_end(arglist);

	buf[127]=0;
	cli_outfun(buf);
}


static  int item_matches (const char *itemname);

typedef struct ItemT {
	char *name;					/* what the user types */
	int (*func) (int argc,char **argv, void *data);	/* function that is the command */
	int *integer;				/* pointer to integer (set to NULL if string) */
	char *string;				/* pointer to string (set to NULL if integer) */
	char *usage;					/* helptext for this command */
	char *help;
	int flags;
	struct ItemT *next;
} ItemT;

#define is_command(a) (a->func && (a->integer==NULL) && (a->string==NULL))
#define is_variable(a) (!is_command(a))

static ItemT *items = NULL;

void
cli_add_item (char *name,
		  int *integer, char *string,
		  int (*func) (int argc,char **argv, void *data), char *usage)
{
	ItemT *titem = items;

	while(titem){
		if(!strcmp(titem->name,name)){
			cli_outfunf ("libcli: attempted to add item '%s' more than once\n", name);
			return;
		}
		titem=titem->next;
	}
	titem=items;

	if (!titem) {
		titem = items = malloc (sizeof (ItemT));
		titem->next = NULL;
	} else {
		ItemT *tmp;

		while (titem->next && ((strcmp ((titem->next)->name, name)) < 0)) {
			titem = titem->next;
		}

		tmp = titem->next;
		titem->next = malloc (sizeof (ItemT));
		titem = titem->next;
		titem->next = tmp;
	}

	titem->name = strdup (name);
	titem->func = func;
	titem->integer = integer;
	titem->string = string;
	if(usage)titem->usage = strdup (usage);
	titem->help=strdup("");

	if (strcmp (items->name, titem->name) > 0) {
		ItemT *tmp = items;
		ItemT *tmp_next = titem->next;

		items = titem;
		items->next = tmp;
		items->next->next = tmp_next;
	}
}

void cli_add_help(char *name, char *helptext){
	ItemT *titem = items;

	while (titem) {
		if (!strcmp (name, titem->name)){
				free(titem->help);
				titem->help=strdup(helptext);
				return;
		}
		titem=titem->next;
	}
	cli_outfunf("libcli: attempted to add help for '%s' which is not registered",name);
}


static int help (int argc,char **argv, void *data);
static int vars (int argc,char **argv, void *data);

static int inited = 0;

void cli_cleanup(void){
	inited=0;
	cli_outfun  = default_output;
	cli_precmd  = NULL;
	cli_postcmd  = NULL;
	cli_unknown = default_unknown_command;
	cli_width = 40;

	while(items){
		ItemT *titem=items;
		if(items->name)free(items->name);
		if(items->usage)free(items->usage);
		if(items->help)free(items->help);
		items=items->next;
		free(titem);
		titem=NULL;
	}
}

static void init_cli (void)
{
	cli_add_command ("?", help, "? - this listing");
	cli_add_command ("show_vars", vars, "show all variables");
	inited = 1;
}

int cli_calllevel=0;

int cli_docmd (char *commandline, void *data)
{
	int largc=0;
	char **largv;
	
	ItemT *titem = items;
	int ret=(int)data;
	cli_calllevel++;

	if (cli_precmd)
		cli_precmd (commandline);

	if (!inited) {
		init_cli ();
		titem = items;
		inited = 1;
	}

	largv=argv_tokenize(commandline);
	if(largv)largc=argc_of_argv(largv);

	if((!largc) || largv[0][0]=='\0' ){
		free(largv);
		return ret;
	}
	
	while (titem) {
		if (!strcmp (largv[0], titem->name)) {
			if (is_command (titem)) {

				ret=titem->func (largc,largv, data);
				
				if (cli_postcmd)
					cli_postcmd (commandline);
				cli_calllevel--;

				free(largv);
				return ret;
			} else if (is_variable (titem)) {
				if (largc==1) {
					if (titem->string) {
						cli_outfunf ("%s\t[%s]\t- %s\n", titem->name,
							  titem->string, titem->usage);
					} else if (titem->integer) {
						cli_outfunf ("%s\t[%i]\t- %s\n", titem->name,
							  *titem->integer, titem->usage);
					} else {
						cli_outfunf ("%s\tis a broken variable\n", titem->name);
					}
				} else {
					if (titem->integer)
						*titem->integer = atoi (largv[1]);
					if (titem->string)
						strcpy (titem->string, largv[1]);
					if (titem->func)
						ret=titem->func (largc,largv, data);
				}
				if (cli_postcmd)
					cli_postcmd (commandline);
				cli_calllevel--;

				free(largv);				
				return ret;
			}
		}
		titem = titem->next;
	}
	if(cli_unknown)
		cli_unknown(1,&commandline,data);
	if (cli_postcmd)
		cli_postcmd (commandline);
	cli_calllevel--;
	
	free(largv);
	return ret;
}

static char newcommand[100];

static int item_matches (const char *itemname)
{
	int matches = 0;
	ItemT *titem = items;

	while (titem) {
		if (!strncmp (itemname, titem->name, strlen (itemname)))
			matches++;
		titem = titem->next;
	}
	return matches;
}

char *cli_complete (const char *commandline)
{
	int matches = 0;
	char str_matches[4096]="";

	strncpy (newcommand, commandline, 99);
	newcommand[99] = 0;

	if (commandline[0]) {
		matches = item_matches (newcommand);

		if (matches == 1) {
			ItemT *titem = items;

			while (titem) {
				if (!strncmp (newcommand, titem->name, strlen (newcommand))) {
					int pos;

					strcpy (newcommand, titem->name);
					pos = strlen (newcommand);
					newcommand[pos++] = ' ';
					newcommand[pos] = '\0';
					break;
				}
				titem = titem->next;
			}
		} else if (matches > 1) {
			ItemT *titem = items;
			strcpy(str_matches,"matches: ");
			while (titem) {
				if (!strncmp (newcommand, titem->name, strlen (newcommand))) {
					strcat (str_matches,titem->name);
					strcat (str_matches," ");
				}
				titem = titem->next;
			}
			cli_outfun(str_matches);
			while (item_matches (newcommand) == matches) {
				ItemT *titem = items;

				while (titem) {
					int len = strlen (newcommand);

					if (!strncmp (newcommand, titem->name, len)) {

						strcpy (newcommand, titem->name);
						newcommand[len + 1] = '\0';
						if(!strcmp(newcommand,titem->name)){
							return newcommand;
						}
						break;
					}
					titem = titem->next;
				}
			}
			newcommand[strlen (newcommand) - 1] = '\0';
		} else {
			cli_outfunf ("no match");
		}
	}

	return newcommand;
}

/* internal commands */

static int help (int argc,char **argv, void *data)
{
	if (argc == 1) {		/* show all help */
		ItemT *titem = items;

		cli_outfunf ("available commands:");

		while (titem) {
		#ifdef HIDE_NULL_HELP
			if(titem->usage)
		#endif
			if (is_command (titem))
				cli_outfunf ("%14s %s", titem->name, titem->usage);
			titem = titem->next;
		};
	} else {					/* show help for specified command */
		ItemT *titem = items;

		cli_outfunf ("HELP for '%s'", argv[1] );

		while (titem) {
			if (is_command (titem)) {
				if (!strcmp (argv[1], titem->name)) {
					cli_outfunf ("usage: %s %s", titem->name, titem->usage);
					if(titem->help[0]){
						cli_outfun ("");
						cli_outfun(titem->help);
					}
					return(int)data;
				}
			}
			titem = titem->next;
		}
		cli_outfunf ("unknown command '%s'", argv[1]);
	}
	return(int)data;	
}

static int vars (int argc, char **argv, void *data)
{
	ItemT *titem = items;

	cli_outfunf ("all variables:");

	while (titem) {
		#ifdef HIDE_NULL_HELP
			if(titem->usage)
		#endif
		if (is_variable (titem)) {
			if (titem->string) {
				cli_outfunf ("%15s [%s]\t- %s", titem->name,
					  titem->string, titem->usage);
			} else if (titem->integer) {
				cli_outfunf ("%15s [%i]\t- %s", titem->name,
					  *titem->integer, titem->usage);
			} else {
				cli_outfunf ("%s\tis a broken variable", titem->name);
			}
		}
		titem = titem->next;
	}

	cli_outfunf ("----------------");
	cli_outfunf ("to change a variable: \"variablename newvalue\"");
	return(int)data;
}

char *cli_getstring(char *variable){
		ItemT *titem = items;
		while (titem) {
			if (is_variable (titem)) {
				if (!strcmp (variable, titem->name)) {
					if(titem->string)
						return(titem->string);
					if(titem->integer)
						return NULL; /* FIXME: use a static buffer perhaps */
				}
			}
			titem = titem->next;
		}
		return "";
}

#include <stdio.h>

int cli_load_file(char *filename){
	char buf[255];
	FILE *file;

	file=fopen(filename,"r");
	if(!file){
		return -1;
	}

	while(fgets(buf,255,file)){
		char *c=strchr(buf,'\n');
		char *t;
		t=buf;
		if(c)*c='\0';
		if(*buf){
			while(*t==' ' || *t=='\t')t++;
			cli_docmd(buf,NULL);
		}
	}
	fclose(file);

	return 0;
}
