/* menuitem.c - advanced menu items for selecting data values
**
** SWADMIN PROJECT
** Network Development
**
** Copyright (c) 1994, 1996 - Carnegie Mellon University - All Rights Reserved
**
** Erikas Aras Napjus (erikas@cmu.edu)
*/

/**********************************************************************
 *
 *           Copyright 1996 by Carnegie Mellon University
 * 
 *                       All Rights Reserved
 * 
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of CMU not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 * 
 * CMU DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
 * CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 * 
 * Author: Erikas Napjus <erikas@cmu.edu>
 * 
 **********************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <assert.h>
#include <ctype.h>

#ifdef HAVE_TERMIOS_H
# include <termios.h>
#endif /* HAVE_TERMIOS_H */

#include <curses.h>      /* Curses support */

#ifdef HAVE_STRINGS_H
# include <strings.h>
#else /* HAVE_STRINGS_H */
# include <string.h>
#endif /* HAVE_STRINGS_H */

#ifdef HAVE_MALLOC_H
# include <malloc.h>
#endif /* HAVE_MALLOC_H */

#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif /* HAVE_STDLIB_H */

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif /* HAVE_UNISTD_H */

#include "swadmin.h"
#include "screen.h"

static char rcsid[] = 
"$Id: menuitem.c,v 1.2 1997/02/17 16:20:50 ryan Exp $";

extern int errorcode;

struct Item
{
  char *text;
  void *reference;
  int count;
  int position;
  struct Item *next, *prev;
} *headitem, *tailitem;

static struct Item *hold, *old;
static int hold_count;

static char *label;

/* init_item - initialize menuing system
**
** WARNING: this must be done before each incarnation of the menu
**          or else bad things will happen
*/
void init_item(void)
{
  for (hold = headitem; hold != NULL; ) {
    old = hold;
    hold = hold->next;
    free (old);
  }
  headitem = tailitem = NULL;
  label = NULL;
  hold_count = 0;
/* delete all of the temporary structures to save on memory */
}

void menu_label(char *text)
{
  label = text;
}

/* add_item - add a new item to the upcoming menu
**
** (text is the text to display for the option)
** (reference can be any type and is returned as is; be careful not to pass
**  pointers into this routine because they will be modified before the
**  menu system engages and are not copied by default)
** (malloc'd code is removed after the menu has been terminated)
**
** NOTE: items are stored in a doubly-linked list to allow easy access to
**       the scrolling functions if necessary; it also speeds addition of
**       new items in the proper order
*/
void add_item(char *text, void *reference)
{
  struct Item *hold;

  hold = (struct Item *) malloc(sizeof(struct Item));
  assert(hold != NULL);
  
  hold->count = hold_count++;
  hold->text = text;
  hold->reference = reference;
  hold->position = -1;

  if ((headitem == NULL) || (tailitem == NULL)) {
      hold->next = NULL;
      hold->prev = NULL;
      headitem = hold;
      tailitem = hold;
  } else {
      hold->next = NULL;
      tailitem->next = hold;
      hold->prev = tailitem;
      tailitem = hold;
  }

  return;
}

/* menu_item - display the menu text from add_item and request a selection
**
** (value returned is reference from add_item; if it's NULL (0), then 
**  you have a break condition and should examine errorcode, a global
**  variable, to decide what to do next)
** (menutext is the type of selection required, e.g. "select an option";
**  titletext is the hubname and is no longer used)
*/
void *menu_item(char *menutext, char *titletext)
{
  WINDOW *body, *pointer;
  int incr, star=0;
  int position, low, high;
  int reverse = FALSE;
  char holder[80];
  void *reference;
  char ch;
  int last_time, last_counter;
  int page_up;

/* create new windows */
  body = newwin(18, 75, 5, 5);
  pointer = newwin(18, 5, 5, 0);

/* refresh everything if possible */
  werase(body); clearok(body, TRUE); touchwin(body); wrefresh(body);
  werase(pointer); clearok(pointer, TRUE); touchwin(pointer); 
  wrefresh(pointer);

  leaveok(pointer, FALSE);

  screen_area("    Menu    ");
  screen_status(STAT_READY);

  if (titletext[0] != 0)
    screen_hub(titletext);

  hold = headitem;

/* make sure we display the requested item with an or a properly */
  switch (toupper(menutext[0]))
    {
    case 'A':
    case 'E':
    case 'I':
    case 'O':
    case 'U':
      sprintf(holder, "Select an %s:", menutext);
      screen_menu(0, 0, holder);
      break;
    default:
      sprintf(holder, "Select a %s:", menutext);
      screen_menu(0, 0, holder);
      break;
    }

  if (label != NULL)
    screen_menu_high(0, 0, label);

  last_time = last_counter = popd();

/* this is the default cause, e.g. start with the first page of data at
** the top of the screen */
 restart:

  for (old = headitem; (old != NULL); old = old->next)
    old->position = -1;

  werase(body);
  wrefresh(body);

/* if we're moving *up* the screen, then display things in reverse using
** the back pointers */
  if (reverse)
    {
      for (incr = 16; (incr >= 0) && (hold != NULL); incr--)
	{
	  mvwprintw(body, incr, 0, "%s\n", hold->text);
	  hold->position = incr;
	  hold = hold->prev;
	}
      if (last_time >= 0)
	{
	  position = last_time;
	  last_time = -1;
	}
      else if (page_up)
	position = 0;
      else
	position = 16;
      low = 0;
      high = 16;
    }
  else
/* if we're moving forward down the screen, use the forward pointers */
    {
      for (incr = 0; (incr < 17) && (hold != NULL); incr++)
	{
	  mvwprintw(body, incr, 0, "%s\n", hold->text);
	  hold->position = incr;
	  hold = hold->next;
	}
      if (last_time >= 0)
	{
	  position = last_time;
	  last_time = -1;
	}
      else
	position = 0;
      low = 0;
      high = incr;
    }

/* if there is a place to autoselect, make sure it appears on the page */
  if (last_counter >= 0)
    {
      hold = headitem;
      while ((hold != NULL) && (hold->count != last_counter))
	hold = hold->next;
      if ((hold != NULL) && (hold->position == -1))
	goto pagedown;
      else if (hold != NULL)
	{
	  position = hold->position;
	  last_counter = -1;
	}
      else
	last_counter = -1;
    }

  wrefresh(body);

/* print the pointer in the correct location */
 set:
  werase(pointer);
  mvwprintw(pointer, position, 0, "-->");
  wrefresh(pointer);
/* figure out what the user wants us to do */
 ready:
  ch = getch();

/* look for magic escape code */
  if (ch == 27)
    {
      star++;
      goto ready;
    }
/* look for second sequence of escape code */
  if ((star == 1) && (ch == 91))
    {
      star++;
      goto ready;
    }
/* found a final escape code */
  if (star == 2)
    {
      star = 0;
      switch (ch)
	{
	case 66:
	down:
	  if (position < (high-1))
	    position++; 
	  else
	    {
	      hold = headitem;
	      while ((hold != NULL) && (hold->position != 16))
		hold = hold->next;
	      reverse = FALSE;
	      if (hold == NULL)
		printf("\07");
	      else
		goto restart;
	    }
	  break;
	case 54:
	  getch();
	pagedown:
	  hold = headitem;
	  while ((hold != NULL) && (hold->position != 16))
	    hold = hold->next;
	  reverse = FALSE;
	  if (hold == NULL)
	    printf("\07");
	  else
	    goto restart;
	  break;
	case 65:
	up:
	  if (position > (low))
	    position--;
	  else
	    {
	      hold = headitem;
	      while ((hold != NULL) && (hold->position != 0))
		{
		  hold = hold->next;
		}
	      page_up = FALSE;
	      reverse = TRUE;
	      if (hold->prev == NULL)
		printf("\07");
	      else
		goto restart;
	    }
	  break;
	case 53:
	  getch();
	pageup:
	  hold = headitem;
	  while ((hold != NULL) && (hold->position != 0))
	    {
	      hold = hold->next;
	    }
	  reverse = TRUE;
	  page_up = TRUE;
	  if (hold->prev == NULL)
	    printf("\07");
	  else
	    goto restart;
	  break;
	case 68:
	  goto back;
	case 67: 
	  goto exit;
	}
    }
/* otherwise, look at normal codes */
  else switch (toupper(ch))
    {
    case 6:
    case '\n':
    case ' ':
    exit:
      hold = headitem;
      while ((hold != NULL) && (hold->position != position))
	hold = hold->next;
      assert(hold->reference != NULL);
      reference = hold->reference;
      pushd(hold->count);
      goto end;
    case 'U':
      goto pageup;
    case 'D':
      goto pagedown;
    case 'L':
    case 'H':
    case '?':
      screen_legend();
      touchwin(pointer); wrefresh(pointer);
      touchwin(body); wrefresh(body);
      break;
    case 'E':
    case 'Q':
      reference = NULL;
      errorcode = ERR_QUIT;
      goto end;
    case 2:
    case 7:
    case 'B':
    back:
      errorcode = ERR_BACK;
      reference = NULL;
      goto end;
    case 'M':
    case 'T':
      reference = NULL;
      errorcode = ERR_TOP;
      clear_stack();
      goto end;
    case 16:
      goto up;
    case 9:
    case 14:
      goto down;
    case 12:
      reference = NULL;
      errorcode = ERR_TOP;
      clear_stack();
      goto end;
    }
  goto set;

 end:
/* clear the screen, remove the windows, and return the reference */
  werase(body);
  werase(pointer);

  wrefresh(body);
  wrefresh(pointer);
      
  delwin(body);
  delwin(pointer);
  return reference;
}
