#include <stdio.h>
#include <strings.h>
#include <curses.h>
#include <ctype.h>
#include	"db.h"
#include	"layout.h"

char	*datafile;
Class           selections[MAX_CHOICES];/* course and section names */
ForminstancePtr myforminstance;
ClassPtr        *classlist;
int             classcount = 0;
int		changesmade = 0;
int		highlight = -1;	/* which line is highlighted  */
int		cursesok = 0;
int	        origcurrentchoice = 0;   

extern char             *getenv();
extern int              PhSendAuthenticator();
extern int              PhOpenServer();
extern int              PhSaveChoices();
extern int              PhDeleteForm();
extern ForminstancePtr  PhGetForm();
extern char		*ReadFile();
void			ErrorMessage();

#ifdef VAXTOKEN
#define safetoupper(foo)  (islower(foo) ? toupper(foo) : foo)
#endif


#include <sys/types.h>
#if (defined(__ultrix) || defined(ultrix))
#include <stddef.h>
#include <sys/syscall.h>
#include <sys/sysmips.h>
#elif defined(__osf__)
#include <sys/sysinfo.h>
#include <sys/proc.h>
#endif

void
coredump_on_unaligned_access(
    void)
{
#if defined(ultrix) || defined(__ultrix)
    syscall(SYS_sysmips, MIPS_FIXADE, 0, NULL, NULL, NULL);
#elif defined(__osf__)
    int nvpair[2];

    nvpair[0] = SSIN_UACPROC;
    nvpair[1] = UAC_SIGBUS;

    setsysinfo(SSI_NVPAIRS, (char *) nvpair, 1, NULL, 0);
#endif
}

char helptext[] =
	"                  Help for PE Lottery Enrollment               \n \n\
Please select, in order of preference, up to six courses from the list.\n\
\n\
	Make your selections with the following keys:\n\
\n\
	downarrow, ^N, j:      Move highlight down\n\
	uparrow, ^P, k:        Move highlight up\n\
	^D, ^F:                Move one page down\n\
	^U, ^B:                Move one page up\n\
	Space, Return:         Select the currently highlighted class\n\
	E, e, Backspace:       Erase most recently selected class\n\
	S, s:                  Save these choices and exit the program\n\
	C, c, Q, q:            Cancel these choices and exit the program\n\
\n\
Press any key to return to the main screen...";

static void erasecallback();
static void cancelcallback();
static void savecallback();
static void helpcallback();
static void quitcallback();
void PrintTextList();

#define	NUM_BUTTONS	4
#define	LINE_WIDTH	74

#define	UP	1
#define	DOWN	2
#define	RIGHT	3
#define	LEFT	4
#define	RETURN	5
#define	TAB	6
#define	BACKTAB	7
#define	QUIT	8
#define	SPACE	9
#define	BACKSPACE	10
#define	REFRESH	11
#define	PAGEDOWN	12
#define	PAGEUP	13

int	LISTROW = 6;		/* where the list of classes appears   */
int	LISTCOL = 0;
int	LISTDISPLEN = 8;	/* how tall a window of classes        */
char	**stringlist;		/* data for list of classes */

int	CHOICEROW = 16;		/* where the list of selections appears */
int	CHOICECOL = 5;

int	currentchoice = 0;	/* which choice are we filling in now? */
char	choices[MAX_CHOICES][MAX_LINE];	/* storage for choices made */
char	*toptext;

typedef struct {		/* okay, so I've done too many widgets */
	char label[10];
	int row, col;
	void (* callback)();
} Button;

Button	allbuttons[NUM_BUTTONS] = {
	{ "Erase", 23, 10, erasecallback},
	{ "Cancel", 23, 25, cancelcallback},
	{ "Help", 23, 40, helpcallback},
	{ "Save", 23, 55, savecallback} };

char	*whoami = NULL;

main(argc, argv)
int	argc;
char	*argv[];
{

	int	i;
	coredump_on_unaligned_access();

	if (argc == 2) {
		if (argv[1][0] == '-') {
			printf ("This program allows you to select your choices for the physical education lottery.\n", argv[0]);
			printf ("usage:  %s\n", argv[0]);
			exit(0);
		}
		whoami = argv[1];
	}


	toptext = ReadFile(WORKINGTEXT);
	if (!toptext)
		exit(-1);
/*
** Introduce myself to the server
*/
	if (!PhOpenServer()) {
		fprintf(stderr, "Cannot reach database server.\n");
		exit(-1);
	}

	if (!PhSendAuthenticator()) {
		fprintf(stderr, "Kerberos authentication failed.\n");
		exit(-1);
	}
	if (!whoami) {
		whoami = getenv ("USER");
	}
	else {
		(void) PhAssumeIdentity(whoami);
	}

	if (!whoami) {
		fprintf(stderr, "Unable to read your username.\n");
		exit(-1);
	}
/*
** Start by building the form instance data structure
*/
	myforminstance = PhGetForm("pelott", whoami);
	if (!myforminstance) {
		fprintf(stderr, "Cannot read your record from the database\n");
		exit(-1);
	}

	BuildUI();

/*
** Transfer values from form instance data into this client's data structures.
** Then copy it into the local storage in the list.
*/
	ReadOutData(myforminstance);

	for (i = 0; i < MAX_CHOICES; i++) {
		if ((selections[i].course)[0] != NULL) {
			SetSelection(i);
			currentchoice = i + 1;
		}
	}
	origcurrentchoice = currentchoice;
	DrawFeedbackBoxes();
	refresh();

	MainLoop();
}

BuildUI()
{
	char	buf[MAX_LINE];
	(void) initscr();
        if ((LINES < 24) || (COLS < 60)) {
            printf("Display window too small.\n\n");
            (void) sprintf(buf, "Current window parameters are (%d \
lines, %d columns)\n", LINES, COLS);
            printf(buf);
            printf("Please resize your window\n");
            printf("to at least 24 lines and 60 columns.\n");
            exit(0);
        }

	cursesok = 1;

        raw();
        noecho();

	PutUpIntro();

	DrawText();
	DrawActionButtons();
	MakeScrolledList();
	bzero(choices, MAX_CHOICES * MAX_LINE);
	DrawBox();
}

DrawText()
{
	int i, j;


	show_text (0,0,toptext);
	show_text (CHOICEROW - 1, CHOICECOL,"Classes currently selected:");
}

DrawActionButtons()
{
	int	i;
	Button	*mb;

	for (i = 0; i < NUM_BUTTONS; i++) {
		mb = &allbuttons[i];
		draw_button (mb->row, mb->col, mb->label);
	}
}


/*
** Make a set of labels that will be updated with the user's selections
*/

DrawFeedbackBoxes()
{
	static char *indices[MAX_CHOICES] = {"1st", "2nd", "3rd", "4th", "5th", "6th"};
	char	namebuf[MAX_LINE + 4];
	int	i;

	for (i = 0; i < MAX_CHOICES; i++) {
		sprintf (namebuf, "%s: %s",indices[i], choices[i]);
    		move(CHOICEROW + i, CHOICECOL);
    		printw("%-*.*s",LINE_WIDTH, LINE_WIDTH, namebuf);
	}
}

MainLoop()
{
	int	c, i;
	int	token;
	int	eaten;

	while (1) {
		token = NULL;
		c = getchar() & 0x7f;   /* mask parity bit */
		eaten = 0;

		token = ParseChar(c);

		if (token == QUIT) {
			cancelcallback();
			eaten = 1;
		}

		else if (token == BACKSPACE) {
			erasecallback();
			eaten = 1;
		}

		else if (token == REFRESH) {
			DoRefresh();
			eaten = 1;
		}

		else
			eaten = ClassListInput(token);

/*
** Keyboard accelerators
*/
		if (!eaten) {
			for (i = 0; i < NUM_BUTTONS; i++) {
#ifdef VAXTOKEN
				if (safetoupper(c) == safetoupper(((allbuttons[i]).label)[0])) {
#else
				if (toupper(c) == toupper(((allbuttons[i]).label)[0])) {
#endif
					((allbuttons[i]).callback)();
					eaten = 1;
				}
			}
		}
		if (!eaten)
			printf("%c", 7);
	}
}

/*
** List of classes has input.  What does it do with this key?
** Return 1 if it can do something, 0 otherwise
*/

ClassListInput(token)
int	token;
{
	static int	leftcol = 0;	/* upper left of clipping box */
	static int	toprow = 0;	/* upper left of clipping box */
	int		retval = 0;
        int             j, doup   = 0;

	switch (token) {
	case RETURN:
	case SPACE:
		changesmade = 1;
        	if (	currentchoice != MAX_CHOICES && 
			*stringlist[highlight] != '\0' &&
			strcmp(classlist[highlight]->course, "NONSEL")) {


        for (j = 0; j < currentchoice; j ++)  
        {
        if ( (!strcmp (selections[j].course, ((classlist[highlight])->course))) && ((classlist[highlight])->section == selections[j].section ) )     
        {
        doup = 1;
        }  
  }
                   if (!doup)
                      {
                	strcpy (choices[currentchoice], stringlist[highlight]);
                	strcpy (selections[currentchoice].course,
                                (classlist[highlight])->course);

                	selections[currentchoice].section =
                                (classlist[highlight])->section;

                	currentchoice++;
			DrawFeedbackBoxes();
			retval = 1;
                       }
        	}
		break;

	case UP:
		highlight = (highlight <= 0 ? 0 : highlight - 1);
/*
** Need to scroll?  We move down a whole page at once to minimize redrawing.
*/
		if (highlight < toprow)  {
			toprow -= LISTDISPLEN;
			if (toprow < 0)
				toprow = 0;
		}
		retval = 1;
		break;
	case DOWN:
		highlight = (highlight >= classcount - 1 ? classcount - 1 : highlight + 1);
/*
** Need to scroll?  We move down a whole page at once to minimize redrawing.
*/
		if (highlight >= toprow + LISTDISPLEN) {
			toprow += LISTDISPLEN;
			if (toprow > classcount - LISTDISPLEN)
				toprow = classcount - LISTDISPLEN ;
		}
		retval = 1;
		break;

	case PAGEDOWN:
		toprow += LISTDISPLEN;
		if (toprow > classcount - LISTDISPLEN)
			toprow = classcount - LISTDISPLEN ;
		highlight = toprow + LISTDISPLEN - 1;
		highlight = (highlight >= classcount - 1 ? classcount - 1 : highlight);
		retval = 1;
		break;

	case PAGEUP:
		toprow -= LISTDISPLEN;
		if (toprow < 0)
			toprow = 0;
		highlight = toprow;
		retval = 1;
		break;

	case RIGHT:
		leftcol = leftcol++;
		retval = 1;
		break;
	case LEFT:
		leftcol = (leftcol <= 0 ? 0 : leftcol - 1);
		retval = 1;
		break;
	}
	PrintTextList(stringlist, LISTDISPLEN, LINE_WIDTH, 
			classcount, toprow, leftcol,
			LISTROW, LISTCOL);
	refresh();
	return (retval);
}

ParseChar(c)
int c;
{
	int	token;

       	switch (c) {
		case '\r':
		case '\n':
			token = RETURN;
			break;

		case '\t':
			token = TAB;
			break;

		case 'q':
		case 'Q':
		case 3:			/* ^C */
			token = QUIT;
			break;

		case ' ':
			token = SPACE;
			break;

		case 4:
		case 6:
			token = PAGEDOWN;
			break;

		case 21:
		case 2:
			token = PAGEUP;
			break;

		case 12:
			token = REFRESH;
			break;

		case 'J':
		case 'j':
		case 14:
			token = DOWN;
			break;

		case 'K':
		case 'k':
		case 16:
			token = UP;
			break;

/*
		case 'H':
		case 'h':
			token = LEFT;
			break;

		case 'L':
		case 'l':
			token = RIGHT;
			break;
*/

		case 127:
			token = BACKSPACE;
			break;

		case 27:                /* incoming arrow key */
           		c = getchar() & 0x7f;
            		if (c == 91) {
               			c = getchar() & 0x7f;

				switch (c) {
					case 65:
						token = UP;
						break;
					case 66:
						token = DOWN;
						break;
					case 67:
						token = RIGHT;
						break;
					case 68:
						token = LEFT;
						break;
				}
			}
			break;

		default:
/*
			move (0,0);
    			printw("Unknown key: %d, '%c'", c, c);
*/
			token = NULL;
			break;
	}
	return (token);
}


static void 
quitcallback()
{
	cls();
	endwin();
	exit();
}

static void 
helpcallback()
{
	cls();
	refresh();
	show_text (0,0,helptext);
	refresh();
	getchar();
	DoRefresh();
}

static void 
erasecallback()
{
	changesmade = 1;
	if (currentchoice > 0) {
		currentchoice--;

		choices[currentchoice][0] = '\0';
		*(selections[currentchoice].course) = NULL;
		selections[currentchoice].section = 0;

		DrawFeedbackBoxes();
		refresh();
        }
	else {
		printf("%c", 7);
	}
}

show_text(row, col, buff)
    int row, col;
    char *buff;
{
    move (row, col);
    addstr(buff);
    refresh();
}

draw_button(row, col, buff)
    int row, col;
    char *buff;
{
	move (row, col);
	wstandout(stdscr);
	printw("%c", buff[0]);
	wstandend(stdscr);
	addstr(buff+1);
}

erase_line(row, col)
    int row, col;
{
    char *buff;
    int i;

    buff = (char *) calloc((unsigned)COLS, 1);
    for (i = 0; i <= COLS - 2; i++)
        buff[i] = ' ';
    buff[i] = 0;                /* just to be sure ! */
    move(row, col);
    mvcur(0, 0, row, col);
    addstr(buff);
    refresh();
}

cls()
{
    clear();
    refresh();
}

static void 
cancelcallback()
{ 
	int	doit;

	if (changesmade)
		doit = Confirm("Do you really want to exit without saving your choices? (y/n)");
	else
		doit = 1;

	if (doit) {
	      if (!origcurrentchoice) {
		PhDeleteForm("pelott", whoami);
              } 
		cls();
		endwin();
		printf ("\nNo changes made\n");
		exit();
	}
	else 
		DoRefresh();
}

static void 
savecallback()
{ 
	int	doit;

	if (changesmade)
		doit = Confirm ("Do you really want to save your choices and exit? (y/n)");
	else
		doit = 1;

	if (doit) {
		if  (!currentchoice)  
		PhDeleteForm("pelott", whoami);
                else {
		PlugInData(myforminstance, selections);
		PhSaveForm(myforminstance);
	      }
		cls();
		endwin();
		printf ("\nYour choices have been saved\n");
		exit();
	}
	else
		DoRefresh();
}

display_buff(string)
char *string;
{
	move (0,0);
    	printw("-%s-",string);
}

/*
** Print a window of the text, "numdisplines" high and "numdispcols" wide,
** starting at line "topline" and column "leftcol" within the text,
** at position row, col on the screen.
**
** The text has "numlines" lines in it.
*/
void 
PrintTextList(char **text, int numdisplines, int numdispcols,
	int numlines, int topline, int leftcol,
	int row, int col)
{
	int	i;
	char	*s;
	char	buffer[20];
	static int	oldhighlight = 0;
/*
** Unhighlight old highlight...
*/
	if (oldhighlight){
   		move (oldhighlight, col + 1);
	      	printw ("   ");
	}     
	for (i = 0; i < numdisplines; i++) {
		if (i + topline >= numlines) break;

		move (row + i, col + 4);
		if (strlen (text[i + topline]) < leftcol)
			s = text[i + topline] + strlen (text[i]);
		else
			s = text[i + topline] + leftcol;
    		printw("%-*.*s",numdispcols, numdispcols, s);

	       
		if (i + topline == highlight) {



		/*
			strncpy (buffer, s, 11);
			move (row + i, col + 1);
			wstandout(stdscr);
    			printw("%s", buffer);
			wstandend(stdscr);
		*/

			move (row + i, col+1);
			wstandout(stdscr);
			printw ("==>");
			wstandend(stdscr);


			oldhighlight = row + i;
		}
	}
}

DrawBox()
{
	int i;

	move (LISTROW - 1, LISTCOL + 1);
	for (i = 0; i < LINE_WIDTH + 3; i++)
    		printw("-");

	move (LISTROW + LISTDISPLEN, LISTCOL + 1);
	for (i = 0; i < LINE_WIDTH + 3; i++)
    		printw("-");

	for (i = 0; i < LISTDISPLEN; i++) {
		move (LISTROW + i, LISTCOL);
    		printw("|");
		move (LISTROW + i, LISTCOL + LINE_WIDTH + 4);
    		printw("|");
	}
}

/*
** Display "string" in a notice box and take "y" or "n" as a response.
** Return "1" for Yes, "0" for No.
*/

Confirm(string)
        char    *string;
{
	int	i,j;
	int	row, col;
	int	c = ' ';

	for (i = 0; i < LISTDISPLEN; i++) {
		move (LISTROW + i, LISTCOL);
    		printw("|");
		for (j = 0; j < LINE_WIDTH + 3; j++) {
    			printw(" ");
		}
    		printw("|");
	}
	row = (int) (LISTROW + (LISTDISPLEN / 2));
	col = (int) (LISTCOL + (LINE_WIDTH - strlen(string)) / 2);
	move (row, col);
    	printw(string);
	refresh();
	while (c != 'n' && c != 'N' && c != 'y' && c != 'Y')
		c = getchar() & 0x7f;   /* mask parity bit */

	if (c == 'y' || c == 'Y')
		return (1);
	else
		return (0);
}

DoRefresh()
{
	cls();
	wstandend(stdscr);
	DrawText();
	DrawFeedbackBoxes();
	DrawActionButtons();
	ClassListInput(REFRESH);
	DrawBox();
	refresh();
}


/*
** Look through the list of offered classes until we find the one with
** the same short name and section number as the previously-made selection.
** Then copy it into the text for the "index"th selection.
*/

SetSelection(index)
int	index;
{
	char		*choice;
	int		i;

	for (i = 0; i < classcount; i++) {
		if ( (!strcmp (classlist[i]->course, selections[index].course) &&
		     (classlist[i]->section == selections[index].section) ) ) {

				strcpy (choices[index], stringlist[i]);
		}
	}
}

/*
** Make the scrolled list of classes offered
*/

MakeScrolledList()
{
	FILE		*infile;
	char		buffer[MAX_LINE + 1];	/* + 1 for NULL byte */
	int		charsread;
	int		newchar;
	char		*rovingpointer;
	char		shortname[COURSELEN + 1];
	int		sectionnum;
	char		*longname;
	int		n;
/*
** Open the data file and read in the list of courses.
**
** Format:  shortname  sectionnum  one-line-description
**          8 chars    3 chars     remainder of line
**
** The long descriptions go into the scrolling list, and the short
** descriptions and section numbers stay in the internal data, "classlist"
*/

	infile = fopen(OFFEREDCLASSES, "r");
	if (infile == NULL) {
		fprintf(stderr, "Cannot open file %s\n", datafile);
		exit(1);
	}

	classcount = 0;

	while (1) {
/*
** Get one line from the data file
*/
		rovingpointer = (char *) &buffer[0];
		newchar = getc(infile);
		charsread = 0;
		while (newchar != '\n' && newchar != EOF && charsread < MAX_LINE) {
			*(rovingpointer++) = (char) newchar;
			newchar = getc(infile);
			charsread++;
		}
		*rovingpointer = '\0';

		if (newchar == EOF)
			break;

		if (sscanf (buffer, "%8s%3d", shortname, &sectionnum) != 2) {
/*
			fprintf (stderr, "WARNING:  Malformed entry \"%s\" in list of classes; ignored.\n", buffer);
			fflush (stderr);
*/
			continue;
		}
/*
** Allocate space to store the data
*/
		classcount++;

		if (classcount == 1) {
			stringlist = (char **)
				malloc(classcount * sizeof(char *));
			classlist = (ClassPtr *)
				malloc(classcount * sizeof(ClassPtr));
		}
		else {
			stringlist = (char **)
				realloc(stringlist, classcount * sizeof(char *));
			classlist = (ClassPtr *) 
				realloc(classlist, classcount * sizeof(ClassPtr));
		}

/*
** Store it.  Note the potentially dangerous assumption that the long 
** course description begins in column 12 of the text.
*/

		classlist[classcount-1] = (ClassPtr) malloc(1 * sizeof(Class));
		strcpy ((classlist[classcount-1])->course, shortname);
		classlist[classcount-1]->section = sectionnum;

		stringlist[classcount-1] = (char *) malloc(strlen(buffer) + 1);
		strcpy (stringlist[classcount - 1], buffer + 11);
	}
/*
** Position the highlight on the first selectable line.
*/
	if (highlight == -1) {
		for (	highlight = 0;
			highlight < classcount;
			highlight++) {
			if (strcmp(classlist[highlight]->course, "NONSEL"))
				break;
		}		
/*
** Nothing selectable, this should never happen, but I look for it anyway.
*/
		if (highlight >= classcount)
			highlight = 0;
	}

	PrintTextList(stringlist, LISTDISPLEN, LINE_WIDTH, 
		classcount, 0, 0,
		LISTROW, LISTCOL);
	refresh();

}

/*
** Display an arbitrary screen of text.  If the first character is "Y"
** we allow the users to continue; otherwise, we exit after letting
** them read the text.
*/

PutUpIntro()
{
	char		*introduction;
	int		continueflag;

	introduction = ReadFile(INTROFILE);
	if (!introduction)
		return(-1);
	if (introduction[0] == '-') {
		free(introduction);
		return;
	}
	continueflag = ((introduction[0] == 'y' || introduction[0] == 'Y') ? True : False);

	cls();
	show_text (0,0,introduction+1);
	show_text (22,30,"Press any key...");
	refresh();
	getchar();
	cls();
	refresh();

	free(introduction);
	if (!continueflag)
		exit();
}

void
ErrorMessage(string)
char	*string;
{
	if (!string)
		return;

	if (cursesok) {
		cls();
		show_text (0,0,string);
		show_text (22,30,"Press any key...");
		refresh();
		getchar();
		DoRefresh();
	}
	else {
		printf ("%s\n", string);
	}
}
