/*
 * Copyright 1999 Silicon Graphics, Inc. All rights reserved.
 */
#include <lcrash.h>
#include <setjmp.h>
#include <strings.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>

cmd_rec_t *cmd_tree = (cmd_rec_t *)0;
extern jmp_buf klib_jbuf;
extern char ql_have_terminal;

/*
 * register_cmds()
 */
int
register_cmds(_command_t cmds[])
{
	int i = 0, ret;
	cmd_rec_t *cmd_rec;

	while(cmds[i].cmd) {
		cmd_rec = (cmd_rec_t *)
			kl_alloc_block(sizeof(*cmd_rec), K_PERM);
		cmd_rec->cmd_name = cmds[i].cmd;
		if (cmds[i].real_cmd) {
			cmd_rec->real_cmd = (cmd_rec_t *)
				kl_find_btnode((btnode_t *)cmd_tree, 
				cmds[i].real_cmd, 0);
			if (!cmd_rec->real_cmd) {
				fprintf(KL_ERRORFP, "%s not found for "
					"alias %s!\n", cmds[i].real_cmd, 
					cmds[i].cmd);
				i++;
				continue;
			} 
		} else {
			cmd_rec->cmdfunc = cmds[i].cmdfunc;
			cmd_rec->cmdparse = cmds[i].cmdparse;
			cmd_rec->cmdhelp = cmds[i].cmdhelp;
			cmd_rec->cmdusage = cmds[i].cmdusage;
			cmd_rec->cmdcomplete = cmds[i].cmdcomplete;
		}
		ret = kl_insert_btnode((btnode_t **)&cmd_tree,
				(btnode_t *)cmd_rec, 0);
		if (ret == -1) {
			fprintf(KL_ERRORFP, "%s is a duplicate cmd!\n",
				cmd_rec->cmd_name);
			kl_free_block(cmd_rec);
		}	
		i++;
	}
	return(0);
}

/*
 * unregister_cmds()
 */
void
unregister_cmd(char *name)
{
	cmd_rec_t *cmd_rec=find_cmd_rec(name);

	if(cmd_rec) {

		kl_delete_btnode((btnode_t **)&cmd_tree, (btnode_t *)cmd_rec, 0, 0);
		kl_free_block(cmd_rec);
	}
}

/*
 * find_cmd_rec()
 */
cmd_rec_t *
find_cmd_rec(char *name) 
{
	cmd_rec_t *cmd_rec;

	cmd_rec = (cmd_rec_t *)kl_find_btnode((btnode_t *)cmd_tree, name, 0);
	return(cmd_rec);
}

/*
 * first_cmd_rec()
 */
cmd_rec_t *
first_cmd_rec()
{
	cmd_rec_t *crp;

	crp = (cmd_rec_t *)kl_first_btnode((btnode_t *)cmd_tree);
	return(crp);
}

/*
 * last_cmd_rec()
 */
cmd_rec_t *
last_cmd_rec()
{
	cmd_rec_t *crp = (cmd_rec_t *)NULL, *ncrp;

	ncrp = first_cmd_rec();
	while (ncrp) {
		crp = ncrp;
		ncrp = next_cmd_rec(crp);
	}
	return(crp);
}

/*
 * next_cmd_rec()
 */
cmd_rec_t *
next_cmd_rec(cmd_rec_t *crp)
{
	cmd_rec_t *ncrp;

	ncrp = (cmd_rec_t *)kl_next_btnode((btnode_t *)crp);
	return(ncrp);
}

/*
 * prev_cmd_rec()
 */
cmd_rec_t *
prev_cmd_rec(cmd_rec_t *crp)
{
	crp = (cmd_rec_t *)kl_prev_btnode((btnode_t *)crp);
	return(crp);
}

/*
 * clean_cmd()
 */
static void
clean_cmd(command_t *cmd)
{
	option_t *op, *opnext;

	if (!cmd) {
		return;
	}
	if (cmd->ofp && (cmd->ofp != stdout)) {
		fclose(cmd->ofp);
	}
	op = cmd->options;
	while (op) {
		opnext = op->op_next;
		kl_free_block(op);
		op = opnext;
	}
	if (cmd->pipe_cmd) {
		kl_free_block(cmd->pipe_cmd);
	}
	bzero(cmd, sizeof(*cmd));
}

/*
 * line_to_words()
 */
void
line_to_words(command_t *cmd)
{
	int i;
	char *cp, *ecp;

	i = 0;
	/* Get the command name. Make sure we strip off any 
	 * leading blank space
	 */
	while (*cmd->command == ' ') {
		cmd->command++;
	}
	cp = cmd->command;
	while (*cp != ' ') {
		if (!(*cp)) {
			cmd->args[0] = 0;
			cmd->nargs = 0;
			return;
		}	
		cp++;
	}
	*cp++ = 0;
	cmd->cmdline = cp;

	/* Parse the remainder of the cmdline and separate into arguments. 
	 * If an argument begins with a double quote ('"'), include all 
	 * text through the next double quote (or end of line) in the 
	 * argument.
	 */
	while (*cp == ' ') {
		cp++;
	}
	if (!(*cp)) {
		cmd->args[0] = 0;
		cmd->nargs = 0;
		return;
	}

	/* Now get the arguments (if there are any)
	 */
	while (*cp) {
		if (i == MAX_ARGS) {
			fprintf(KL_ERRORFP, "Too many command line "
				"arguments!\n");
			cmd->nargs = i;
			return;
		}
		if (*cp == '\"') {
			ecp = cp + 1;
			while (*ecp != '\"') {
				if (!(*ecp)) {
					break;
				}
				ecp++;
			}
			ecp++;
		} else {
			ecp = cp;

			/* Step over the non-blank characters
			 */
			while (*ecp != ' ') {
				if (!(*ecp)) {
					break;
				}
				ecp++;
			}
		}
		cmd->args[i] = cp;
		cp = ecp;
		if (*ecp) { 
			*ecp = 0; 
			cp++; 
		}
		i++;

		/* If we have reached the end of the input line then
		 * return.
		 */
		if (!(*cp)) {
			if (i <= MAX_ARGS) {
				cmd->args[i] = 0;
			}
			cmd->nargs = i;
			return;
		}

		while (*cp == ' ') {
			cp++;
		}
	}
	cmd->args[i] = 0;
	cmd->nargs = i;
}

/*
 * get_cmd()
 */
static void
get_cmd(command_t *cmd)
{
	int len;
	char *empty="";
	char *rl_getline(void);
	
	static char cmd_buf[256];

	clean_cmd(cmd);
	cmd->ofp = stdout;
	cmd->efp = stderr;
	
	if (!ql_have_terminal) {
		fprintf(stdout, "\n>> ");
		fflush(stdout);
		
		if (fgets(cmd_buf, sizeof(cmd_buf), stdin) == NULL) {
			/* stdin closed */
			exit(0);
		}
		else { /* get rid off the newline character */
			len = strlen(cmd_buf);
			cmd_buf[len - 1] = '\0';
			cmd->command = cmd_buf;
		}
	}
	else {
		/* if we hit EOT we take that as a empty command line ]
		 */
		if(!(cmd->command = rl_getline())) {
			cmd->command = empty;
		}
	}
	line_to_words(cmd);
}

/*
 * parse_options()
 */
static int
parse_options(command_t *cmd, char *optstr, int flags)
{
	char *cp;
	int extra, i = 0, j;
	option_t *op = 0;

	while (cmd->args[i]) {
		extra = 0;
		if (cmd->args[i][0] == '-') {
			if (strlen(cmd->args[i]) == 1) {
				if (flags & C_NO_OPCHECK) {
					i++;
					continue;
				}
				fprintf(KL_ERRORFP, "Option letter missing\n");	
				return(1);
			} else if ((cp = strchr(optstr, cmd->args[i][1]))) {
				if (op) {
					op->op_next = (option_t *)
						kl_alloc_block(sizeof(*op), 
						K_TEMP);
					op = op->op_next;
				} else {
					op = (option_t *)
						kl_alloc_block(sizeof(*op),
						K_TEMP);
					cmd->options = op;
				}	
				op->op_char = *cp;
				if (*(cp+1) == ':') {
					if (i == (cmd->nargs - 1)) {
						fprintf(KL_ERRORFP, 
							"Argument missing for "
							"command line option: "
							"\'%c\'\n", *cp);
						return(1);
					}
					if (cmd->args[i+1]) {
						if (cmd->args[i+1][0] != '-') {
							op->op_arg = 
								cmd->args[i+1];	
							extra++;
						} else {
							fprintf(KL_ERRORFP, 
							"Argument missing for "
							"command line option: "
							"\'%c\'.\n", *cp);
							return(1);
						}
					}
				}
			} else {
				if (flags & C_NO_OPCHECK) {
					i++;
					continue;
				}
				fprintf(KL_ERRORFP, "Illegal comamnd line "
					"option: \'%c\'\n", cmd->args[i][1]);
				return(1);
			}
			for (j = i; j < cmd->nargs - extra; j++) {
				cmd->args[j] = cmd->args[j + 1 + extra];
			}
			cmd->nargs -= (1 + extra);
		} else if (cmd->args[i][0] == '|') {
			if ((cmd->nargs == 1) && !cmd->args[i][1]) {
				fprintf(KL_ERRORFP, "No arguments to "
					"pipe cmd\n");
				return(1);
				
			}
			cmd->pipe_cmd = 
				(char *)kl_alloc_block(MAX_CMDLINE, K_TEMP);
			if (klib_error) {
				fprintf(KL_ERRORFP, "Could not allocate pipe "
					"cmdline\n");
				return(1);
			}
			if (strlen(cmd->args[i]) == 1) {
				strcpy(cmd->pipe_cmd, cmd->args[i + 1]);
				strcat(cmd->pipe_cmd, " ");
				for (j = i + 2; j < cmd->nargs; j++) {
					strcat(cmd->pipe_cmd, cmd->args[j]);
					cmd->args[j] = 0;
					if (j < cmd->nargs - 1) {
						strcat(cmd->pipe_cmd, " ");
					}
				}
			} else {
				strcpy(cmd->pipe_cmd, &cmd->args[i][1]);
				strcat(cmd->pipe_cmd, " ");
				for (j = i + 1; j < cmd->nargs; j++) {
					strcat(cmd->pipe_cmd, cmd->args[j]);
					cmd->args[j] = 0;
					if (j < cmd->nargs - 1) {
						strcat(cmd->pipe_cmd, " ");
					}
				}
			}
			cmd->args[i] = 0;
			cmd->nargs -= (cmd->nargs - i);
			if ((cmd->ofp = popen(cmd->pipe_cmd, "w")) == NULL) {
				fprintf(KL_ERRORFP, "Could not open pipe\n");
				return (1);
			}
			setbuf(cmd->ofp, NULL);
			return(0);
		} else {
			i++;
		}
	}
	return(0);
}

/*
 * do_cmd()
 */
void
do_cmd(command_t *cmd)
{
	cmd_rec_t *cmd_rec;
	cmdfunc_t cmdfunc;
	cmdparse_t cmdparse;
	cmdhelp_t cmdhelp;
	cmdusage_t cmdusage;

	if (cmd->command[0]) {
		cmd_rec = find_cmd_rec(cmd->command);
		if (cmd_rec) {
			if (cmd_rec->real_cmd) {
				cmdfunc = cmd_rec->real_cmd->cmdfunc;
				cmdparse = cmd_rec->real_cmd->cmdparse;
				cmdusage = cmd_rec->real_cmd->cmdusage;
				cmdhelp = cmd_rec->real_cmd->cmdhelp;
			} else {
				cmdfunc = cmd_rec->cmdfunc;
				cmdparse = cmd_rec->cmdparse;
				cmdusage = cmd_rec->cmdusage;
				cmdhelp = cmd_rec->cmdhelp;
			}	
			
			if ((cmdparse)(cmd)) {
				(cmdusage)(cmd);
			} else {
				(cmdfunc)(cmd);
			}
		} else {
			fprintf(KL_ERRORFP, "unknown command\n");
		}
	} 
}

/*
 * process_cmds()
 */
int
process_cmds()
{
	command_t *cmd;
	
	if ((cmd = (command_t *)kl_alloc_block(sizeof(*cmd), K_PERM)) == NULL) {
		return(1);
	}
	while(1) {
		if (setjmp(klib_jbuf)) {
			clean_cmd(cmd);
			free_temp_blocks();
		}
		/* Reset global error status */
		kl_reset_error();
		get_cmd(cmd);
		do_cmd(cmd);
		clean_cmd(cmd);
		free_temp_blocks();
		iter_threshold = def_iter_threshold;
	}
}

/*
 * set_cmd_flags()
 */
int
set_cmd_flags(command_t *cmd, int flags, char *extraops)
{
	int i = 0, have_args = 0;
	char optstr[25];
	option_t *op;
	FILE *ofp;

	/* If more than one flag is set, we go with the most restrictive
	 */
	if (flags & C_FALSE) {
		have_args = C_FALSE;
	} else if (flags & C_TRUE) {
		have_args = C_TRUE;
	} 

	/* Check for standard flags
	 */
	if (flags & C_ALL) {
		optstr[i++] = 'a';
	}
	if (flags & C_FULL) {
		optstr[i++] = 'f';
	}
	if (flags & C_LIST) {
		optstr[i++] = 'l';
	}
	if (flags & C_NEXT) {
		optstr[i++] = 'n';
	}
	if (flags & C_WRITE) {
		optstr[i++] = 'w';
		optstr[i++] = ':';
	}
	optstr[i] = 0;

	/* Add any user defined options
	 */
	if (extraops) {
		strcat(optstr, extraops);
	}

	if (parse_options(cmd, optstr, flags)) {
		return(1);
	}

	if ((have_args == C_TRUE) && (cmd->nargs  == 0)) {
		fprintf(KL_ERRORFP, "Command requires arguments\n"); 
		return(1);	
	}
	if ((have_args == C_FALSE) && cmd->nargs) {
		fprintf(KL_ERRORFP, "Command does not take any arguments\n"); 
		return(1);	
	}

	/* Now set flags for any of the standard options
	 */
	op = cmd->options;
	while (op) {
		switch (op->op_char) {
			case 'a':
				cmd->flags |= C_ALL;
				break;

			case 'f':
				cmd->flags |= C_FULL;
				break;

			case 'l':
				cmd->flags |= C_LIST;
				break;

			case 'n':
				cmd->flags |= C_NEXT;
				break;

			case 'w':
				/* Check for pipe cmd output (it wont work 
				 * with -w option)
				 */
				if (cmd->pipe_cmd) {
					fprintf(KL_ERRORFP, "Cannot use -w "
					"command line option when output has "
					"bnen redirected to a pipe\n");
					return(1);
				}
				ofp = fopen(op->op_arg, "a");
				if (ofp == (FILE*)0) {
					fprintf(KL_ERRORFP, "Cannot open file "
						":%s\n", op->op_arg);
				}
				cmd->ofp = ofp;
		}
		op = op->op_next;
	}
	return(0);
}

#define INDENT_STR "    "

/*
 * helpformat() -- String format a line to within N - 3 characters, where
 *                 N is based on the winsize.  Return an allocated string.
 *                 Note that this string must be freed up when completed.
 */
char *
helpformat(char *helpstr)
{
	int indentsize = 0, index = 0, tmp = 0, col = 0;
	char *t, buf[1024];
	struct winsize w;

	/* if NULL string, return 
	 */
	if (!helpstr) {
		return ((char *)0);
	}

	/* get the window size 
	 */
	if (ioctl(fileno(stdout), TIOCGWINSZ, &w) < 0) {
		w.ws_col = 80;
	}

	indentsize = strlen(INDENT_STR);

	/* set up buffer plus a little extra for carriage returns, if needed 
	 */
	t = (char *)kl_alloc_block(strlen(helpstr) + 256, K_TEMP);
	bzero(t, strlen(helpstr) + 256);

	/* Skip all initial whitespace -- do the indentions by hand. 
	 */
	while (helpstr && isspace(*helpstr)) {
		helpstr++;
	}
	strcat(t, INDENT_STR);
	index = indentsize;
	col = indentsize;

	/* Keep getting words and putting them into the buffer, or put them on
	 * the next line if they don't fit.
	 */
	while (*helpstr != 0) {
		tmp = 0;
		bzero(&buf, 1024);
		while (*helpstr && !isspace(*helpstr)) {
			buf[tmp++] = *helpstr;
			if (*helpstr == '\n') {
				strcat(t, buf);
				strcat(t, INDENT_STR);
				col = indentsize;
				index += indentsize;
				tmp = 0;
			}
			helpstr++;
		}

		/* if it fits, put it on, otherwise, put in a carriage return 
		 */
		if (col + tmp > w.ws_col - 3) {
			t[index++] = '\n';
			strcat(t, INDENT_STR);
			col = indentsize;
			index += indentsize;
		}

		/* put it on, add it up 
		 */
		strcat(t, buf);
		index += tmp;
		col += tmp;

		/* put all extra whitespace in (as much as they had in) 
		 */
		while (helpstr && isspace(*helpstr)) {
			t[index++] = *helpstr;
			if (*helpstr == '\n') {
				strcat(t, INDENT_STR);
				index += indentsize;
				col = 4;
			}
			else {
				col++;
			}
			helpstr++;
		}
	}

	/* put on a final carriage return and NULL for good measure 
	 */
	t[index++] = '\n';
	t[index] = 0;
	return (t);
}

/*
 * complete_cmds() -- Devide the string from the head of command line to the 
 *                    position of TAB into words.
 *                    Call the completion function for command names, if TAB 
 *                    is pressed on the first word. 
 *                    This function is registered to librl by 
 *                    rl_register_complete_func().
 *
 *                    Call the completion function for command arguments, if 
 *                    TAB is pressed on the word after the second it.
 *                    Note that it is not implemented.
 */
char *
complete_cmds(char *inputline, int tabpos)
{
	static command_t cmd;
	static char cline[DEF_LENGTH];
	int help_list(command_t *);

	/* copy string from the head of command line to the position of TAB to 
	 * buffer. (tabpos + 1) does not exceed DEF_LENGTH, so error check is 
	 * not needed.
	 */
	clean_cmd(&cmd);
	cmd.ofp = stdout;
	strncpy(cline, inputline, tabpos + 1);
	cline[tabpos] = '\0';
	cmd.command = cline;

	/* command line is devided into a command name and arguments */
	line_to_words(&cmd); 

	/* TAB is pressed on the head of argument */
	if (*cmd.command && inputline[tabpos - 1] == ' ') {
		cmd.nargs++;
	}
	
	if (cmd.nargs == 0) {
		/* TAB is pressed on the command name */
		if (!(*(cmd.command))) {
			/* if TAB is pressed at the head of a command name, 
			 * display the list of command names */	
			fprintf(stdout, "\n");
			help_list(&cmd); /* display the list of command names */
			return(DRAW_NEW_ENTIRE_LINE);
		} else {
			/* if TAB is pressed in the middle of a command name, 
			call completion function for a command name */
			return(complete_subcmd_name(cmd.command));
		}
	} else {
		/* TAB is pressed on the command argument */
		/* call completion function for command arguments */
		cmd_rec_t *crec;
		cmdcomplete_t cfunc;

		/* get internal data for cmd.command */
		if ((crec = find_cmd_rec(cmd.command)) == NULL) {
			/* bad command name */
			return(PRINT_BEEP);
		}
		cfunc = crec->real_cmd ? crec->real_cmd->cmdcomplete : crec->cmdcomplete;
		if (cfunc) {
			/* call completion function for command arguments */
			return(cfunc(&cmd));
		} else {
			return(PRINT_BEEP);
		}
	}
}

/*
 * complete_subcmd_name() -- This function completes command names.
 *                           When there is no candidate, return PRINT_BEEP. 
 *                           When there is a candidate, return the string.
 *                           When there are two or more candidates, return the 
 *                           identical part of string of them. 
 *                           When there isn't the identical part of string, 
 *                           display the list of candidates and return  
 *                           DRAW_NEW_ENTIRE_LINE. 		
 */
#define SV_CNM(i) save_crp[i]->cmd_name
char *
complete_subcmd_name(char *string)
{
	int slen, i, j, index;
	static int cmdnum = 0; 
	static cmd_rec_t **save_crp = (cmd_rec_t **)0;
	static char cptstr[DEF_LENGTH];
	cmd_rec_t *ncrp;
	int candidates_cnt = 0;

	/* get number of commands */
	if (!cmdnum) {
		if ((ncrp = first_cmd_rec())) {
			do {
				cmdnum++;
			} while ((ncrp = next_cmd_rec(ncrp)));
		}
	}
	/* allocate pointer array */
	if (!save_crp) {
		save_crp = kl_alloc_block(cmdnum * sizeof(cmd_rec_t *), K_PERM);
		if (klib_error) {
			fprintf(KL_ERRORFP, "Could not allocate memory for completion\n");
			return(PRINT_BEEP);
		}
	}

	slen = strlen(string);
	cptstr[0] = '\0';

	/* find candidates */
	ncrp = first_cmd_rec();
	for (i = 0; i < cmdnum; i++) {
		if (!strncmp(ncrp->cmd_name, string, slen)) {
			save_crp[candidates_cnt] = ncrp;
			/* get a string to complete */
			if (candidates_cnt == 0) {
				strcpy(cptstr, SV_CNM(candidates_cnt)+slen);	
			} else if (cptstr[0] != '\0') {
				/* if there are two or more candidates, get the identical part 
				of string of them */
				for (j = 0; cptstr[j] != '\0' && 
					*(SV_CNM(candidates_cnt)+slen+j) != '\0' &&
					cptstr[j] == *(SV_CNM(candidates_cnt)+slen+j); j++); 
				cptstr[j] = '\0';
			}
			candidates_cnt++;
		}
		ncrp = next_cmd_rec(ncrp);
	}

	if (candidates_cnt == 0) {
		/* there is no candidate */
		return(PRINT_BEEP);
	} else if (candidates_cnt == 1) {
		/* there is a candidate */
		strcat(cptstr, " ");
		return(cptstr);
	} else { 
		/* there are two or more candidates */
		if (cptstr[0] == '\0') { /* there is no the identical part of string */
			goto print_list;
		} else { /* there is the identical part of string */
			return(cptstr);
		}
	}
print_list:
	fprintf(stdout, "\n");
	index = candidates_cnt / 4 + (candidates_cnt % 4 ? 1 : 0);
	for (i = 0; i < index; i++) {
		fprintf(stdout, "%-17s", SV_CNM(i));
		if ((j = index + i) < candidates_cnt) {
			fprintf(stdout, "%-17s", SV_CNM(j));
		}
		if ((j = index * 2 + i) < candidates_cnt) {
			fprintf(stdout, "%-17s", SV_CNM(j));
		}
		if ((j = index * 3 + i) < candidates_cnt) {
			fprintf(stdout, "%-17s", SV_CNM(j));
		}
		fprintf(stdout, "\n");
	}
	fflush(stdout);
	return(DRAW_NEW_ENTIRE_LINE);
}

/*
 * complete_standard_options() -- This function completes the standard options 
 *                                argument. 
 *                                If there is a standard option, it returns 
 *                                with the value which the completion function 
 *                                returned.
 *                                If there is no standard option, it returns 
 *                                NOT_COMPLETED.  
 */
char *
complete_standard_options(command_t *cmd)
{
	if (cmd->nargs > 1) {
		if (!strcmp(cmd->args[cmd->nargs - 2], "-w")) {
			/* if previous word is "-w", complete file name */
			return(complete_file_name(cmd->args[cmd->nargs -1], 100));
		} else {
			return(NOT_COMPLETED);
		}
	} else {
		return(NOT_COMPLETED);
	}
}


/*
 * complete_symbol_name() -- This function completes 'keystr' as symbol name.
 *                           When there is no candidate, return PRINT_BEEP. 
 *                           When there is a candidate, return the string.
 *                           When there are two or more candidates, return 
 *                           the identical part of string of them. 
 *                           When there isn't the identical part of string, 
 *                           display the list of candidates and return  
 *                           DRAW_NEW_ENTIRE_LINE. 		
 *                           If number of the candidates is more 
 *                           'print_max_candt', ask whether display or not. 
 */
char *
complete_symbol_name(char *keystr, int print_max_candt)
{
	syment_t	*sq_cur, *sq_head;
	static char retstr[KL_SYMBOL_NAME_LEN];
	int	candtcnt = 0;
	int i;
	int	candt_maxlen;
	int	print_column;
	char print_str[8];
	struct winsize w;

	if (!keystr) {
		keystr = "";
	}
	/* get que of the candidates for symbol name */
	sq_head = kl_get_similar_name(keystr, retstr, &candtcnt, &candt_maxlen);
	if (candtcnt == 0) {
		/* if there is no candidate, return PRINT_BEEP */
		return(PRINT_BEEP);
	} else if (candtcnt == 1) {
		/* if there is a candidate, return string to complete */
		strcat(retstr, " ");
		return(retstr);
	} else { /* candtcnt is 2 or more */
		if (retstr[0] == '\0') {
			/* if there is no the identical part of string, print the list of 
			   candidates */
			if (print_max_candt && candtcnt >= print_max_candt) {
				/* if there are number of "print_max_candt" or more candidates, 
				   ask whether diaplay or not */
				for (;;) {
					int c;
					fprintf(stdout, 
						"\nDisplay all %d possibilities? (y or n)", candtcnt);
					c = getc(stdin);
					if (c == 'y' || c == 'Y') {
						break;
					} else if (c == 'n' || c == 'N') {
						fprintf(stdout, "\n");
						return(DRAW_NEW_ENTIRE_LINE);
					} else {
						continue;
					}
				}
			}
			/* print list of candidates */
			/* get the window size */
			if (ioctl(fileno(stdout), TIOCGWINSZ, &w) < 0) {
				w.ws_col = 80;
			}
			/* get number of the columns suited for printing candidates */ 
			if (!(print_column = w.ws_col / (candt_maxlen + 1))) {
				print_column = 1;
			}
			sprintf(print_str, "%%-%ds", 
				(w.ws_col < candt_maxlen + 1) ? w.ws_col : candt_maxlen + 1); 
			sq_cur = sq_head;
			for (i = 0; i < candtcnt || sq_cur; i++) {
				if (i % print_column == 0) {
					fprintf(stdout, "\n");
				}
				fprintf(stdout, print_str, sq_cur->s_name);
				sq_cur = sq_cur->s_forward;
			}
			fprintf(stdout, "\n");
			fflush(stdout);
			return(DRAW_NEW_ENTIRE_LINE);
		} else {
			/* if there is the identical part of string, return string to
			   complete */
			return(retstr);
		}
	}
}

/*
 * complete_file_name() --  This function completes 'string' as file name.
 *                          When there is no candidate, return PRINT_BEEP. 
 *                          When there is a candidate, return the string.
 *                          When there are two or more candidates, return the 
 *                          identical part of string of them. 
 *                          When there isn't the identical part of string, 
 *                          display the list of candidates and return  
 *                          DRAW_NEW_ENTIRE_LINE. 		
 *                          If number of the candidates is more 
 *                          'print_max_candt', ask whether display or not. 
 */
char *
complete_file_name(char *string, int print_max_candt)
{
	char *last_slash_pos;
	static char dirname[DEF_LENGTH];
	static char keystr[NAME_MAX + 1], retstr[NAME_MAX + 1];
	int	candt_maxlen;
	int	 dirlen, keylen;
	DIR *dp;
	struct dirent *dent;
	int candtcnt = 0;
	int	i;
	struct stat sbuf;
	struct candt_que_s {
		struct candt_que_s *next;
		char str[1];
	} *q_head, *q_tail, *q_cur;
	int	print_column;
	char print_str[8];
	struct winsize w;
	char *ret;

	/* string is '\0' */
	if (!string || string[0] == '\0') {
		strcpy(dirname, "./");
		strcpy(keystr, "");
	} else {
		/* get position of last slash */
		last_slash_pos = strrchr(string, '/');
		if (last_slash_pos == NULL) {
			/* search current directory */
			strcpy(dirname, "./");
			strcpy(keystr, string);
		} else {
			/* get search directory */
			dirlen = last_slash_pos - string + 1;
			strncpy(dirname, string, dirlen);
			dirname[dirlen] = '\0';
			/* get key string to complete */
			strcpy(keystr, string+dirlen); 
		}
	}
	keylen = strlen(keystr);
	/* open search directory */
	if ((dp = opendir(dirname)) == NULL) {
		ret = PRINT_BEEP;
		goto out;
	}

	/* initialize q_head */
	q_head = 0;	

	/* get the queue of candidates for file name */ 
	while ((dent = readdir(dp)) != NULL) {
		if (!keylen || !strncmp(dent->d_name, keystr, keylen)) {
			/* allocate memory for candidates queue */
			q_cur = kl_alloc_block(sizeof(struct candt_que_s) + 
				strlen(dent->d_name), K_PERM);
			if (klib_error) {
				fprintf(KL_ERRORFP, 
					"Could not allocate memory for file name completion\n");
				/* free memory for queue of candidates and return with value of 
				   'ret'*/
				ret = PRINT_BEEP;
				goto out;
			}
			strcpy(q_cur->str, dent->d_name);
			if (candtcnt == 0) {
				q_head = q_tail = q_cur;
				/* save return string to 'retstr' */
				strcpy(retstr, q_cur->str+keylen);
				/* get max length of candidates */
				candt_maxlen = strlen(q_cur->str);
			} else {
				q_tail->next = q_cur;
				q_tail = q_cur;
				if (retstr[0] != '\0') {
					/* get the identical part of string of candidates and save 
					   to 'retstr' */
					for (i = 0; retstr[i] != '\0' && 
						retstr[i] == *(q_cur->str+keylen+i); i++);
					retstr[i] = '\0';
				}
				/* get max length of candidates */
				if (candt_maxlen < strlen(q_cur->str)) {
					candt_maxlen = strlen(q_cur->str);
				}
			}
			q_tail->next = 0;
			candtcnt++;
		}
	}
	closedir(dp);

	if (candtcnt == 0) {
		ret = PRINT_BEEP;
		goto out;
	} else if (candtcnt == 1) {
		/* check if file is directory */
		strcat(dirname, q_head->str);
		stat(dirname, &sbuf);
		if (S_ISDIR(sbuf.st_mode)) {
			strcat(retstr, "/");
		} else {
			strcat(retstr, " ");
		}
		/* return string to complete */
		ret = retstr;
		/* free memory for queue of candidates and return with value of 'ret'*/
		goto out;
	} else { /* candtcnt >= 2 */
		if (retstr[0] == '\0') {
			/* if there is no the identical part of string, print the list of 
			   candidates */
			if (print_max_candt && candtcnt >= print_max_candt) {
				/* if there are number of "print_max_candt" or more candidates,
				   ask whether display or not */
				for (;;) {
					int c;
					fprintf(stdout, "\nDisplay all %d possibilities? (y or n)",
						candtcnt);
					c = getc(stdin);
					if (c == 'y' || c == 'Y') {
						break;
					} else if (c == 'n' || c == 'N') {
						fprintf(stdout, "\n");
						/* free memory for queue of candidates and return with 
						   value of 'ret' */
						ret = DRAW_NEW_ENTIRE_LINE; 
						goto out;
					} else {
						continue;
					}
				}
			}
			/* print list of candidates */
			/* get the window size */
			if (ioctl(fileno(stdout), TIOCGWINSZ, &w) < 0) {
				w.ws_col = 80;
			}
			/* get number of the columns suited for printing candidates */ 
			if (!(print_column = w.ws_col / (candt_maxlen + 1)))
				print_column = 1;
			sprintf(print_str, "%%-%ds", 
				(w.ws_col < candt_maxlen + 1) ? w.ws_col : candt_maxlen + 1); 
			q_cur = q_head;
			for (i = 0; i < candtcnt || q_cur; i++) {
				if (i % print_column == 0) {
					fprintf(stdout, "\n");
				}
				fprintf(stdout, print_str, q_cur->str);
				q_cur = q_cur->next;
			}
			fprintf(stdout, "\n");
			fflush(stdout);
			/* free memory for queue of candidates and return with value of 
			   'ret'*/
			ret = DRAW_NEW_ENTIRE_LINE;
			goto out;
		} else {
			/* if there is the identical part of string, return string to
			   complete */
			/* free memory for queue of candidates and return with value of 
			   'ret'*/
			ret = retstr;
			goto out;
		}
	}
out:
	while (q_head) {
		q_cur = q_head;
		q_head = q_head->next;
		kl_free_block(q_cur);
	}
	return(ret);
}
