/*
 * console.c  -  user interface I/O routines
 *
 * Copyright (C) 1998-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  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
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: console.c,v 1.13 2007/01/06 18:31:12 gkminix Exp $
 */

#define NEED_DIR 1
#include <common.h>
#include <nblib.h>
#include "console.h"



/*
 * Default screen lines and columns
 */
#define DEFAULT_LINES		25
#define DEFAULT_COLUMNS		80



/*
 * Variables local to this module
 */
static int scnlines;			/* number of screen lines */
static int scncolumns;			/* number of screen columns */
static int curline;			/* current screen line */




/*
 **************************************************************************
 *
 *		General output routines
 *
 **************************************************************************
 */


/*
 * Print a new line, and increase the number of displayed lines
 */
static char *printnl __F((prompt), const char *prompt)
{
  char *cp = NULL;

  putchar('\n');
  curline++;
  if (curline > (scnlines - 5)) {
	printf("\nPress <ENTER> to continue ");
	cp = getstring(prompt, 0);
	putchar('\n');
	curline = 0;
  }
  return(cp);
}



/*
 * Print help topic
 */
static void printhelp __F((topic), int topic)
{
  char *buf;
  int status;

  /* Check if topic is available */
  status = helptopic(topic);
  if (status == -1)
	nbexit(-1);
  else if (status == 0) {
	printf("\nTopic number %d not available in help file\n\n", topic);
	return;
  }

  /* Get us a new help buffer */
  buf = (char *)nbmalloc((size_t)scncolumns + 1);

  /* Read all lines for current topic */
  curline = 0;
  (void)printnl(NULL);
  while ((status = helpget(buf, scncolumns)) > 0) {
	if (*buf)
		printf("%s", buf);
	(void)printnl(NULL);
  }
  putchar('\n');
  if (status == -1)
	nbexit(-1);

  /* Delete help buffer again */
  free(buf);
}




/*
 **************************************************************************
 *
 *		Directory printing routines
 *
 **************************************************************************
 */

/*
 * Directory entry name list
 */
struct direntry {
	struct direntry *next;
	char            *fname;
};



/*
 * Compare a description table entry for qsort routine
 */
static int descmp __F((entry1, entry2),
				const voidstar entry1 AND
				const voidstar entry2)
{
  struct direntry *dp1 = *((struct direntry **)entry1);
  struct direntry *dp2 = *((struct direntry **)entry2);

  assert(dp1 != NULL && dp2 != NULL);
  if (dp1->fname == NULL && dp2->fname != NULL)
	return(-1);
  else if (dp1->fname != NULL && dp2->fname == NULL)
	return(1);
  else if (dp1->fname == NULL && dp2->fname == NULL)
	return(0);

  return(strcmp(dp1->fname, dp2->fname));
}



/*
 * Compare a directory entry name against one pattern.
 */
static int singledircmp __F((name, pat), const char *name AND const char *pat)
{
  while (*pat && *name) {
	if (*pat == '*') {
		pat++;
		if (!*pat)
			return(TRUE);
		while (*name && !singledircmp(name, pat))
			name++;
		return(*name);
	} else if (*pat == '?' || *pat == *name) {
		pat++;
		name++;
	} else
		break;
  }
  return(!*pat && !*name);
}



/*
 * Compare a directory entry name against a list of patterns.
 */
static int dircmp __F((name, pattern), const char *name AND const char *pattern)
{
  char *cp1, *cp2;
  char *pat = NULL;

  if (pattern == NULL)
	return(TRUE);

  copystr(&pat, pattern);
  cp1 = pat;
  while (cp1 != NULL) {
	if ((cp2 = strchr(cp1, ':')) != NULL)
		*(cp2++) = '\0';
	if (*cp1 && singledircmp(name, cp1)) {
		free(pat);
		return(TRUE);
	}
	cp1 = cp2;
  }
  free(pat);
  return(FALSE);
}



/*
 * Get a list of directory entries with name patterns.
 */
static struct direntry **getdirlist __F((dirname, pattern),
						const char *dirname AND
						const char *pattern)
{
  DIR *dir;
  struct dirent *ent;
  struct direntry *dp;
  struct direntry **dirlist = NULL;
  struct direntry *firstdir = NULL;
  unsigned int count, i;

  /* Open the directory */
  if ((dir = opendir(dirname)) == NULL)
	return(NULL);

  /* Step through all directory entries */
  count = 0;
  while ((ent = readdir(dir)) != NULL) {
	if (dircmp(ent->d_name, pattern)) {
		dp = (struct direntry *)nbmalloc(sizeof(struct direntry));
		copystr(&(dp->fname), ent->d_name);
		dp->next = firstdir;
		firstdir = dp;
		count++;
	}
  }
  closedir(dir);

  /* Generate sorted array of directory entries */
  if (count > 0) {
	dirlist = (struct direntry **)nbmalloc((count + 1) *
						sizeof(struct direntry *));
	i = 0;
	dp = firstdir;
	while (dp != NULL && i < count) {
		dirlist[i] = dp;
		dp = dp->next;
		dirlist[i]->next = NULL;
		i++;
	}
	assert(i == count);
	dirlist[i] = NULL;
	qsort(dirlist, count, sizeof(struct direntry *), &descmp);
  }
  return(dirlist);
}



/*
 * Delete directory list
 */
static void deldirlist __F((dirlist), struct direntry **dirlist)
{
  struct direntry **dpp;

  if (dirlist == NULL)
	return;

  dpp = dirlist;
  while (*dpp != NULL) {
	free((*dpp)->fname);
	free(*dpp);
	dpp++;
  }
  free(dirlist);
}



/*
 * Print list of directory entries matching a pattern
 */
static void printdir __F((dirname, pattern),
					const char *dirname AND
					const char *pattern)
{
  struct direntry **dirlist;
  char *dirbuf = NULL;
  char *cp1, *cp2, *cp3;
  unsigned int i, j, k;

  /* Generate local copy of directory name list */
  copystr(&dirbuf, dirname);
  cp1 = dirbuf;
  while (cp1 != NULL) {
	/* Find end of subdirectory name */
	if ((cp2 = strchr(cp1, ':')) != NULL)
		*(cp2++) = '\0';

	/* Get list of files in directory */
	if ((dirlist = getdirlist(cp1, pattern)) == NULL) {
		cp1 = cp2;
		continue;
	}

	/* Print listing header */
	curline = 0;
	printf("Contents of %s:", cp1);
	(void)printnl(NULL);

	/* Print all files in directory */
	i = 0;
	while (dirlist[i] != NULL) {
		printf("  ");
		for (j = 2; (j + 20) < (unsigned int)scncolumns && dirlist[i] != NULL; i++) {
			cp3 = dirlist[i]->fname;
			for (k = 0; k < 20 && *cp3; j++, k++, cp3++)
				putchar(*cp3);
			for ( ; j < ((unsigned int)scncolumns - 1) && k < 24; k++, j++)
				putchar(' ');
		}
		(void)printnl(NULL);
	}

	/* Prepare for next directory */
	deldirlist(dirlist);
	if ((cp1 = cp2) != NULL) {
		(void)printnl(NULL);
		(void)printnl(NULL);
	}
  }
  free(dirbuf);
}




/*
 **************************************************************************
 *
 *		Routines to print a sorted list of items
 *
 **************************************************************************
 */

/*
 * Maximum number of items in a list
 */
#define MAX_ITEMS	32767



/*
 * Define column sizes
 */
#define COLUMN_NUMBER	8		/* Size of number field */
#define COLUMN_STATUS	15		/* Size of status field */



/*
 * Delete the item array
 */
static void delitemarray __F((itemarray), struct listitem **itemarray)
{
  unsigned int i = 0;

  while(itemarray[i] != NULL)
	free(itemarray[i++]);
  free(itemarray);
}



/*
 * Compare a item entry for qsort routine
 */
static int itemcmp __F((entry1, entry2),
				const voidstar entry1 AND
				const voidstar entry2)
{
  struct listitem *ip1 = *((struct listitem **)entry1);
  struct listitem *ip2 = *((struct listitem **)entry2);

  assert(ip1 != NULL && ip2 != NULL &&
         ip1->descstr != NULL && ip2->descstr != NULL);
  return(strcmp(ip1->descstr, ip2->descstr));
}



/*
 * Generate an array of item records and sort it according to
 * the description name
 */
static struct listitem **sortitemlist __F((itemlist, dosort),
				struct listitem *itemlist AND
				int dosort)
{
  struct listitem *ip;
  struct listitem **itemarray;
  unsigned int i, itemnum;

  /* Determine number of items in list */
  itemnum = 0;
  for (ip = itemlist; ip != NULL; ip = ip->next)
	itemnum++;
  if (itemnum == 0)
	return(NULL);
  else if (itemnum > MAX_ITEMS)
	itemnum = MAX_ITEMS;

  /* Generate array out of linked list */
  itemarray = (struct listitem **)nbcalloc((itemnum + 1),
						sizeof(struct listitem *));
  i = 0;
  ip = itemlist;
  while (ip != NULL && i < itemnum) {
	itemarray[i] = ip;
	ip = ip->next;
	itemarray[i]->next = NULL;
	i++;
  }
  itemarray[i] = NULL;

  /* If the first item pointer is NULL we keep this item as first item */
  i = 0;
  if (itemarray[0]->desc == NULL)
	i++;

  /* Sort the array */
  if (dosort)
	qsort(&(itemarray[i]), itemnum - i, sizeof(struct listitem *),
								&itemcmp);
  return(itemarray);
}



/*
 * Print sorted array of items
 */
static unsigned int printitemarray __F((itemarray, sel),
				struct listitem **itemarray AND
				int *sel)
{
  unsigned int i, k, l, n, min;
  long j;
  char *cp, numbuf[8];

  /* Determine minimum item number */
  min = 1;
  if (itemarray[0]->desc == NULL)
	min = 0;

  /* Determine maximum length of status field */
  l = 0;
  i = 0;
  while (i < MAX_ITEMS && itemarray[i] != NULL) {
	if (itemarray[i]->status != NULL) {
		k = strlen(itemarray[i]->status);
		if (k > l)
			l = k;
	}
	i++;
  }
  assert(i > 0);
  if (l > 0) {
	if (l > COLUMN_STATUS)
		l = COLUMN_STATUS;
	l += COLUMN_NUMBER + 1;
  }

  /* Print all items */
  i = 0;
  while (TRUE) {
	/* Print item number */
	sprintf(numbuf, "%u", i + min);
	putchar('(');
	k = 1;
	cp = numbuf;
	while (k < COLUMN_NUMBER) {
		if (*cp) {
			putchar(*(cp++));
			k++;
			if (!*cp || k == (COLUMN_NUMBER - 1)) {
				putchar(')');
				k++;
			}
		} else {
			putchar(' ');
			k++;
		}
	}
	putchar(' ');
	k++;

	/* Print item status field */
	if (l > 0) {
		cp = itemarray[i]->status;
		while (k < l) {
			if (cp != NULL && *cp)
				putchar(*(cp++));
			else
				putchar(' ');
			k++;
		}
	}
	putchar(' ');
	putchar(' ');
	k += 2;

	/* Print item description */
	n = k;
	cp = itemarray[i]->descstr;
	while (*cp && k < ((unsigned int)scncolumns - 2)) {
		if (*cp == '\n') {
			putchar(*(cp++));
			for (k = 0; k < n; k++)
				putchar(' ');
			while (*cp == ' ' || *cp == '\t')
				cp++;
		} else {
			putchar(*(cp++));
			k++;
		}
	}

	/* Print end-of-line and check for continuation */
	i++;
	if (i < MAX_ITEMS && itemarray[i] != NULL) {
		cp = printnl("or enter selection number");
		if (cp != NULL && sscanf(cp, "%ld", &j) == 1 &&
		    j >= (long)min && j < (long)i) {
			*sel = (int)j;
			break;
		}
	} else {
		putchar('\n');
		break;
	}
  }
  putchar('\n');
  return(i);
}




/*
 **************************************************************************
 *
 *		Public user interface I/O routines
 *
 **************************************************************************
 */


/*
 * Get a string response from the user
 */
char *getstring __F((prompt, topic), const char *prompt AND int topic)
{
  static char buf[512];
  char *cp;

  while (TRUE) {
	if (prompt != NULL)
		printf("%s: ", prompt);
	fflush(stdout);
	fgets(buf, sizeof(buf) - 1, stdin);
	for (cp = buf; *cp && *cp != '\n'; cp++) ;
	*cp = '\0';
	if (topic > 0 && !strcmp(buf, "?"))
		printhelp(topic);
	else
		break;
  }
  return(buf);
}



/*
 * Get a numeric response from the user
 */
long getnum __F((prompt, min, max, hex, topic),
				const char *prompt AND
				long min AND
				long max AND
				int hex AND
				int topic)
{
  char *cp;
  long j;
  unsigned int i, indent = 0;

  if (prompt != NULL)
	indent = strspn(prompt, " ");

  while (TRUE) {
	if (prompt != NULL)
		printf("%s (%s): ", prompt, hex ? "hex" : "decimal");
	cp = getstring(NULL, 0);
	if (topic > 0 && !strcmp(cp, "?"))
		printhelp(topic);
	else if (*cp) {
		if ((hex ? sscanf(cp, "%lx", &j) : sscanf(cp, "%ld", &j)) != 1)
			j = min - 1;
		if (j < min || j > max) {
			for (i = 0; i < indent; i++)
				putchar(' ');
			if (hex)
				printf("  Allowed values are [%lX..%lX]\n",
								min, max);
			else
				printf("  Allowed values are [%ld..%ld]\n",
								min, max);
		} else
			break;
	}
  }
  return(j);
}



/*
 * Get a list selection from the user
 */
int getsel __F((prompt, minsel, maxsel, defsel, topic, plines),
				const char *prompt AND
				int minsel AND
				int maxsel AND
				int defsel AND
				int topic AND
				int plines)
{
  char *cp;
  int sel = minsel - 1;
  int i;

  while (TRUE) {
	i = 0;
	if (prompt != NULL) {
		if (defsel >= minsel && defsel <= maxsel)
			printf("%s [%d]: ", prompt, defsel);
		else
			printf("%s: ", prompt);
	}
	cp = getstring(NULL, 0);
	if (topic > 0 && !strcmp(cp, "?")) {
		printhelp(topic);
		if (plines == 0)
			continue;
		if ((curline + plines) > (scnlines - 1)) {
			printf("\nPress <ENTER> to continue ");
			(void)getstring(NULL, 0);
			putchar('\n');
		}
		return(-1);
	}
	if (cp != NULL && *cp != '\0')
		i = sscanf(cp, "%d", &sel);
	else if (defsel >= minsel && defsel <= maxsel) {
		sel = defsel;
		i = 1;
	}
	if (i == 1 && sel >= minsel && sel <= maxsel)
		break;
  }
  return(sel);
}



/*
 * Get a yes/no response from the user
 */
int getyn __F((prompt, def, topic),
				const char *prompt AND
				int def AND
				int topic)
{
  char *cp;

  while (TRUE) {
	if (prompt != NULL)
		printf("%s (y/n) [%s] ? ", prompt, def ? "yes" : "no" );
	cp = getstring(NULL, 0);
	if (topic > 0 && !strcmp(cp, "?"))
		printhelp(topic);
	else if (!*cp)
		return(def);
	else if (toupper(*cp) == 'Y')
		return(TRUE);
	else if (toupper(*cp) == 'N')
		return(FALSE);
  }
}



/*
 * Ask user about a file name
 */
char *getfilename __F((prompt, searchdir, pattern, topic),
				const char *prompt AND
				const char *searchdir AND
				const char *pattern AND
				int topic)
{
  char *filename, *cp, *dir;

  filename = NULL;
  while (filename == NULL) {
	if (prompt != NULL) {
		printf(prompt);
		dir = getstring(" ('*' for directory listing)", 0);
	} else {
		dir = getstring(NULL, 0);
	}
	if (topic > 0 && !strcmp(dir, "?")) {
		printhelp(topic);
		continue;
	}
	dir += strspn(dir, " \t");
	cp = dir + strcspn(dir, " \t:");
	*cp = '\0';
	if (dir[0] == '~' && dir[1] == '/') {
		filename = (char *)nbmalloc(strlen(dir) - 1 +
						strlen(userhomedir) + 1);
		sprintf(filename, "%s%s", userhomedir, &(dir[1]));
	} else if (dir[0] == '.' && dir[1] == '/') {
		filename = (char *)nbmalloc(strlen(dir) - 1 +
						strlen(curworkdir) + 1);
		sprintf(filename, "%s%s", curworkdir, &(dir[1]));
	} else
		copystr(&filename, dir);
	if ((cp = strchr(filename, '*')) != NULL) {
		if ((cp > filename && cp[-1] != '/') || cp[1] != '\0')
			printf("Invalid directory specification\n");
		else if (filename == cp)
			printdir(searchdir, pattern);
		else {
			cp[-1] = '\0';
			if (!*filename)
				printdir("/", pattern);
			else
				printdir(filename, pattern);
		}
		free(filename);
		filename = NULL;
	} else if (*filename) {
		checkaccess(&filename, searchdir, ACCESS_FILE_READ);
		if (filename == NULL)
			printf("Unable to find and/or access file %s\n", dir);
	} else {
		free(filename);
		filename = NULL;
	}
  }
  return(filename);
}



/*
 * Print a description list and ask the user about a selection
 */
voidstar getlistsel __F((header, prompt, itemlist, defitem, dosort, topic),
				const char *header AND
				const char *prompt AND
				struct listitem *itemlist AND
				struct listitem *defitem AND
				int dosort AND
				int topic)
{
  voidstar retptr;
  struct listitem **itemarray;
  int sel, defsel, itemnum;

  /* First generate a sorted array from item list */
  itemarray = sortitemlist(itemlist, dosort);
  if (itemarray == NULL)
	return(NULL);

  /* Determine default item number */
  defsel = -1;
  if (defitem != NULL) {
	for (itemnum = 0; itemarray[itemnum] != NULL; itemnum++)
		if (itemarray[itemnum] == defitem) {
			defsel = itemnum;
			break;
		}
  }

  /* Print item list and wait for a user response */
  retptr = NULL;
  sel = -1;
  do {
	curline = 1;
	printf("%s\n", header);
	itemnum = (int)printitemarray(itemarray, &sel);
	if (sel < 0) {
		sel = getsel(prompt, -1, itemnum, defsel, topic, itemnum + 2);
		if (itemarray[0]->desc != NULL)
			sel--;
	}
  } while (sel < 0 || sel >= itemnum);

  /* Determine return value and delete item array */
  retptr = itemarray[sel]->desc;
  delitemarray(itemarray);
  return(retptr);
}




/*
 **************************************************************************
 *
 *		Screen maintenance routines
 *
 **************************************************************************
 */


/*
 * Initialization values to get from environment
 */
static struct {
	char	*envname;
	int	*valptr;
	int	 defaultval;
	int	 minval;
	int	 maxval;
} envlist[] = {
	{ "LINES",	&scnlines,	DEFAULT_LINES,		24,	100 },
	{ "COLUMNS",	&scncolumns,	DEFAULT_COLUMNS, 	80,	255 },
	{ NULL,		NULL,		0 }
};



/*
 * Console initialization
 */
void initconsole __F((helpname), const char *helpname)
{
  int i;
#ifdef HAVE_GETENV
  char *cp, *endp;
  long val;
#endif

  /* Check that we really have a console attached */
  if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) {
	prnerr("no console attached for interactive mode");
	nbexit(EXIT_CONSOLE);
  }

  /* Determine operating parameters from environment */
  for (i = 0; envlist[i].envname != NULL; i++) {
#ifdef HAVE_GETENV
	val = -1;
	if ((cp = getenv(envlist[i].envname)) != NULL) {
		val = strtol(cp, &endp, 0);
		if (*endp || val < envlist[i].minval)
			val = -1;
		else if (val > envlist[i].maxval)
			val = envlist[i].maxval;
	}
	*(envlist[i].valptr) = (val < 0 ? envlist[i].defaultval : val);
#else
	*(envlist[i].valptr) = envlist[i].defaultval;
#endif
  }

  /* Initialize help system */
  if (!helpinit(helpname))
	nbexit(-1);
  if (helpname != NULL)
	printf("With any question, you can enter '?' to get help\n\n");
}



/*
 * Close console
 */
void closeconsole __F_NOARGS
{
  if (!helpinit(NULL))
	nbexit(-1);
}

