/*
** Phaedo function library.  Stripping out all Motif and Xt specific calls.
*/

#include        "layout.h"
#include	"inet.h"
#include	<krb.h>
#include	<errno.h>
#include	<netdb.h>
#include	<sys/time.h>
#include	<hesiod.h>
#include	"db.h"

int		sockfd = -1;		/* where the server lives */
static char	*MakePair();
extern char	*whoami;

#define	safestrlen(foo)	((foo) ? strlen(foo) : 0)

extern Class		selections[];	/* course and section names */

/*
** Given a form instance, we build up a char** of field-value pairs
** describing the data in the instance, and send it over the network to
** the server.
**
** return 1 if everything's okay, some other value otherwise.
*/

int
PhSaveForm(pinstance)
ForminstancePtr	pinstance;
{

	FieldPtr	*field = pinstance->class->topfield;
	int		i, n;
	char		**pairs;
	char		*fieldvalue;
	int		paircount = 0;
	char		sendline[MAXLINE];
/*
** Build up the char** of field-value pairs;
*/
	for (	i = pinstance->class->fieldcount, field = pinstance->class->topfield;
		i; 
		i--, field++) {

		fieldvalue = MakePair(*field, pinstance);

		if (!fieldvalue)
			continue;

		paircount++;
		if (paircount == 1) {
			pairs = (char **)
				malloc(paircount * sizeof(char *));
		}
		else {
			pairs = (char **)
				realloc(pairs, paircount * sizeof(char *));
		}
		pairs[paircount-1] = fieldvalue;
	}

/*
** Send header for values:  Tell server we're sending it data for a form of
** type pinstance->class->formname, named whoami (THIS MUST CHANGE
** FOR NON-HASS-D APPLICATIONS) and with "paircount" new values.
*/

	sprintf (sendline, "put %s %s (%d)\n", 
			pinstance->class->formname, whoami, paircount);
	if (writen (sockfd, sendline, safestrlen(sendline)) != safestrlen(sendline)) {
		err_dump("PhSaveForm:  writen error on socket");
		return (0);
	}

/*
** See what the server's reply is to sending the header
*/
	n = CheckAck("PhSaveForm");
	if (n != 1)
		return (n);

/*
** Now loop through the field-value pairs
*/
	for (i = 0; i < paircount; i++) {
		if (writen (sockfd, pairs[i], safestrlen(pairs[i])) != safestrlen(pairs[i])) {
			err_dump("PhSaveForm:  writen error on socket");
			return (0);
		}
		n = CheckAck("PhSaveForm");
		if (n != 1)
			return (n);
	}

/*
** Clean up after ourselves
*/
	for (i = 0; i < paircount; i++)
		free(pairs[i]);

	free(pairs);
	return (1);
}

/*
** Return "1" if everything's okay, some other value otherwise
*/

CheckAck(fnname)
char	*fnname;
{
	char		recvline[MAXLINE];
	int		n, i;

	n = readline (sockfd, recvline, MAXLINE);
	if (n < 0) {
		err_dump(fnname);
		return (0);
	}
	if (n == 0) {
		err_dump(fnname);
		return (0);
	}
	
	i = atoi (recvline);
	if (i != 1) {
		ErrorMessage (recvline);
		return (i);
	}
	else
		return (1);
}

/*
** Return a string with the fieldname-fieldvalue pair for PhSaveForm
*/

static char*
MakePair(myfield, pinstance)
FieldPtr	myfield;
ForminstancePtr pinstance;
{
	InputFieldPtr		myinput;
	char			*sendline;
	TextInputPtr		mytext;
	NumericInputPtr		mynumeric;
	BooleanInputPtr		myboolean;
	SelectionInputPtr	myselection;

	if (myfield->field_type == INPUT) {
		myinput = myfield->detail->inputptr;
		switch (myinput->input_type) {
		case TEXTINPUT:
			mytext = myinput->inputsource->text;
			sendline = (char *) malloc(	safestrlen (myfield->name) + 
						safestrlen (mytext->data) + 3);
			sprintf (	sendline, "%s %s\n", 
					myfield->name, mytext->data);
			break;
		case NUMERICINPUT:
			mynumeric = myinput->inputsource->numeric;
			sendline = (char *) malloc(	safestrlen (myfield->name) + 
						16 + 3);
			sprintf (	sendline, "%s %d\n", 
					myfield->name, mynumeric->data);
			break;

		case BOOLEANINPUT:
			myboolean = myinput->inputsource->boolean;
			sendline = (char *) malloc(	safestrlen (myfield->name) + 
						5 + 3);
			sprintf (	sendline, "%s %s\n", 
					myfield->name, 
					myboolean->data ? "True" : "False");
			break;

/*
** Need to handle multiple selections.  But this is good enough for hass-d.
*/
		case SELECTIONINPUT:
			myselection = myinput->inputsource->selection;
			sendline = (char *) malloc(	safestrlen (myfield->name) + 
						16 + 3);
			sprintf (	sendline, "%s %d\n", 
					myfield->name, myselection->data[0]);
			break;
		}
	}
	else {
		sendline = NULL;
	}
	return (sendline);
}


/*
** Given a form class and a form name, delete the form instance.
** Return NULL if there was any error.
*/

int
PhDeleteForm(class, name)
char	*class;
char	*name;
{
	ForminstancePtr	retval;
	char		sendline[MAXLINE+1], recvline[MAXLINE+1];
	int		i;

	sprintf (sendline, "delete %s %s\n", class, name);
	i = SendLine(sendline, recvline);
	if (i != 1) {
			ErrorMessage (recvline);
			return (NULL);
	}
	return (1);
}


/*
** Given a form class and a form name, check to see if this form exists.
** If so, read the data into a form instance and return it.  Otherwise,
** create a new form instance of this type.  Return NULL if there
** was any error.
*/


ForminstancePtr
PhGetForm(class, name)
char	*class;
char	*name;
{
	ForminstancePtr	retval;
	char		sendline[MAXLINE+1], recvline[MAXLINE+1];
	int		n, i, paircount;

/*
** Start by getting myself an empty form instance
*/
	retval = ReadFormDefinition(HASSDFORM, NULL);

	if (!retval) {
		ErrorMessage ("Can't open form description file");
		return (NULL);
	}

/*
** Ask the server if a form by this name exists.  If not, have server make
** a new one for me.
*/

	sprintf (sendline, "get %s %s\n", class, name);
	i = SendLine(sendline, recvline);

	switch (i) {
	case 531:
	case 510:
	case 521:
		ErrorMessage (recvline);
		return (NULL);
	case 512:	/* no such form; make one!  */
		sprintf (sendline, "newnamed %s %s\n", class, name);
		i = SendLine(sendline, recvline);
		if (i != 1) {
			ErrorMessage (recvline);
			return (NULL);
		}
		sprintf (sendline, "get %s %s\n", class, name);
		i = SendLine(sendline, recvline);
		if (i != 1) {
			ErrorMessage (recvline);
			return (NULL);
		}
	}

/*
** Now read the incoming field-value pairs and fill in my form instance.
** recvline will contain the count of pairs.
*/

	sscanf (recvline, "%*s (%d)", &paircount);
	for (i = 0; i < paircount; i++) {
		 n = readline (sockfd, recvline, MAXLINE);
		if (n < 0) {
			err_dump("PhGetForm: readline error");
			return (0);
		}
		if (n == 0) {
			err_dump("PhGetForm: server has gone down");
			return (0);
		}
/*
** We don't want the newline.
*/
                if (recvline[n - 1] == '\n')
                        recvline[n - 1] = '\0';

                ReplaceValue(retval, recvline);
        }

	return (retval);
}

/*
** Given a form instance and a string containing a field-value pair,
** place this value in the appropriate field of the instance.
*/

#define ISWHITESPACE(foo) ((foo) == ' ' || (foo) == '\t')


ReplaceValue(pinstance, namevalue)
ForminstancePtr	pinstance;
char		*namevalue;
{
	char		field[MAXLINE], *value;
	int		n;
	InputFieldPtr	myinput;
	FieldPtr	myfield;
	TextInputPtr            mytextinput;
	NumericInputPtr         mynumericinput;
	BooleanInputPtr         mybooleaninput;
	FormclassPtr		class;

/*
** Pull out the whitespace-delimited field name from the input line.
*/
        for (n = 0; n < MAXLINE && namevalue[n]; n++) {
                if (ISWHITESPACE(namevalue[n]))
                        break;
                else
                        field[n] = namevalue[n];
        }
        field[n] = '\0';

/*
** We've found the field name.  Now skip past whitespace to find the value.
*/
        for (value = &namevalue[strlen(field)]; *value; value++) {
                if (! ISWHITESPACE(*value))
                        break;
        }

/*
** Make sure this field exists, and if so, figure out what type it is.
** Convert the value appropriately and plug it into the instance.
*/
	class = pinstance->class;
	myfield = FindFieldByName(field, class);
	if (myfield == NULL) {
		Debug ("Fed a nonexistant field, %s\n", field);
		return;
	}
	if (myfield->field_type == INPUT) {
		myinput = myfield->detail->inputptr;
		switch (myinput->input_type) {
		case TEXTINPUT:
			mytextinput = myfield->detail->inputptr->inputsource->text;
			strcpy (mytextinput->data, value);
/*
			Debug ("New value of text field %s is %s\n", field, mytextinput->data);
*/
			break;

		case NUMERICINPUT:
			mynumericinput = myinput->inputsource->numeric;
			mynumericinput->data = atoi (value);
/*
			Debug ("New value of numeric field %s is %d\n", field, mynumericinput->data);
*/
			break;

		case BOOLEANINPUT:
			mybooleaninput = myinput->inputsource->boolean;
			if (strcasecmp (value, "true"))
                        	mybooleaninput->data = 0;
                	else
                        	mybooleaninput->data = 1;
/*
			Debug ("New value of boolean field %s is %s\n", field, mybooleaninput->data ? "True" : "False");
*/
			break;
		}
	}
	else {
		Debug ("Fed a non-input field, %s\n", field);
	}
}

/*
** Send a line to the server; wait for one-line response.  Return
** the integer code from the server's response and fill in the
** full received string.
*/

SendLine(sendline, recvline)
char	*sendline;
char	*recvline;
{
	int	n, i;
	if (writen (sockfd, sendline, safestrlen(sendline)) != safestrlen(sendline)) {
		err_dump("writen error on socket");
		return (0);
	}

	n = readline (sockfd, recvline, MAXLINE);
	if (n < 0) {
		err_dump("SendLine: readline error");
		return (0);
	}
	if (n == 0) {
		err_dump("SendLine: server has gone down");
		return (0);
	}
	
	i = atoi (recvline);
	return (i);
}


/*
** return 1 if everything's okay, some other value otherwise.
*/

int
PhOpenServer()
{
	struct sockaddr_in	serv_addr;
	struct hostent		*host;

	bzero ( (char *) &serv_addr, sizeof (serv_addr));
	host = gethostbyname(SERV_HOST_NAME);

	serv_addr.sin_family = AF_INET;

	/*
	serv_addr.sin_addr.s_addr = inet_addr (SERV_HOST_ADDR);
	*/
	if (host) {
		 bcopy(host->h_addr, &serv_addr.sin_addr, host->h_length);
	}
	else {
		err_dump ("Can't locate server");
		return (0);
	}

	serv_addr.sin_port = htons (SERV_TCP_PORT);

	if ( (sockfd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
		err_dump ("can't open stream socket");
		return (0);
	}

	if (connect (	sockfd, 
			(struct sockaddr *) &serv_addr,
			sizeof(serv_addr)) < 0) {
		err_dump ("can't connect to server");
		return (0);
	}

	return(1);
}

int
PhCloseServer()
{
	if (sockfd != -1) {
		close (sockfd);
		sockfd = -1;
		return (1);
	}
	else {
		return (0);
	}
}

/*
** Get Kerberos ticket; binhex it and send it over the wire.
** return 1 if everything's okay, some other value otherwise.
*/

int
PhSendAuthenticator()
{
	char	sendline[MAXLINE], recvline[MAXLINE+1];
	int	bytecount = 26;
	int	n = 0;
	unsigned char	*auth;

	bytecount = BuildAuthenticator(&auth);
	if (bytecount == 0) {
		err_dump("PhSendAuthenticator: Cannot build authenticator");
		return (0);
	}

	sprintf (sendline, "Authenticate (%d)\n", bytecount);
	if (writen (sockfd, sendline, safestrlen(sendline)) != safestrlen(sendline)) {
		err_dump("PhSendAuthenticator:  writen error on socket");
		return (0);
	}

	n = SendBinaryData(auth, bytecount, sockfd);

	if (n) {
		err_dump("Error sending authenticator\n");
		return (0);
	}

	n = readline (sockfd, recvline, MAXLINE);
	if (n < 0) {
		err_dump("PhSendAuthenticator: readline error");
		return (0);
	}
	if (n == 0) {
		err_dump("str_cli: server has gone down");
		return (0);
	}
/*
	fputs(recvline, stdout);
*/
	return (1);
}


/*
** convert a stream of "bytecount" arbitrary ints into a stream of
** "bytecount" * 2 + 1 (binhexed chars plus newline) and send them
** out the socket.
*/

static int
SendBinaryData(data, bytecount, sockfd)
unsigned char	*data;
int		bytecount;
int		sockfd;
{
	int	n, charstosend;
	char	*sendline;

	sendline = (char *) calloc((bytecount * 2) + 2, sizeof(char));

	for (n = 0; n < bytecount; n++) {
		sprintf ((char *) (sendline + (2 * n)), "%02x", *(data + n));
	}
	sprintf ((char *) (sendline + 2 * n), "\n");

	charstosend = bytecount * 2 + 1;

	if (writen (sockfd, sendline, charstosend) != charstosend) {
		free(sendline);
		return(1);
	}

	free(sendline);
	return(0);
}

/*
** Get a Kerberos ticket, fill in a pointer to it, and return its length.
*/

static int
BuildAuthenticator(retval)
unsigned char **retval;
{
  KTEXT_ST authent;
  static unsigned char packet[2048];
  struct hostent *hp;
  static char hostname[] = SERV_HOST_NAME;
  char *krb_get_phost();
  char lrealm[REALM_SZ];
  char linst[INST_SZ];
  char sinst[INST_SZ];
  char *cp;
  int status;

  bzero(packet, sizeof(packet));

  krb_get_lrealm(lrealm, 1);	/* Get our Kerberos realm */

  cp = krb_get_phost(hostname);
  if (cp == NULL) {
    err_dump("Null return from krb_get_phost");
    return (0);
  }

  strcpy(sinst, cp);

  hp = gethostbyname(hostname);

  if (hp == NULL) {
    err_dump("Null return from gethostbyname");
    return (0);
  }

  strcpy(linst, "rcmd");
  status = krb_mk_req(&authent, linst, sinst, lrealm, 111);
  if (status != KSUCCESS) {
    ErrorMessage("Error getting kerberos authentication.");
    return (0);
  }
  (void) bcopy((char *)&authent, (char *) packet, sizeof(authent));
  *retval = packet;
  return (sizeof(authent));
}

/*
static void
bombout(mess)
int mess;
{

  fprintf(stderr, "A problem occurred while attempting authentication. (%d)\n",
	  mess);
  fprintf(stderr, "Please try again later. If this problem persists,\n");
  fprintf(stderr, "please report it to a consultant.\n");
  exit (1);
}
*/

int
readn (fd, ptr, nbytes)
int	fd;
char	*ptr;
int	nbytes;
{
	int nleft, nread;

	nleft = nbytes;
	while (nleft > 0) {
		nread = read (fd, ptr, nleft);
		if (nread < 0)
			return (nread);		/* error, return < 0 */

		else if (nread == 0)
			break;			/* EOF */

		nleft -= nread;
		ptr += nread;
	}
	return (nbytes - nleft);
}

int
writen(fd, ptr, nbytes)
int	fd;
char	*ptr;
int	nbytes;
{
	int nleft, nwritten;

	nleft = nbytes;
	while (nleft > 0) {
		nwritten = write (fd, ptr, nleft);
		if (nwritten <= 0)
			return (nwritten);		/* error */

		nleft -= nwritten;
		ptr += nwritten;
	}
	return (nbytes - nleft);
}

/*
**	Read a line from a descriptor.  Read the line one byte at a time,
**	looking for the newline.  We store the newline in the buffer,
**	then follow it with a null (the same as fgets(3)).
**	We return the number of characters up to, but not including
**	the null (the same as strlen(3)).
*/

int
readline(fd, ptr, maxlen)
int	fd;
char	*ptr;
int	maxlen;
{
	int	n, rc;
	char	c;

	for (n = 1; n < maxlen; n++) {
		if ( (rc = read (fd, &c, 1)) == 1) {
			*ptr++ = c;
			if (c == '\n')
				break;
		} else if (rc == 0) {
			if (n == 1)
				return(0);	/* EOF, no data read */
			else
				break;		/* EOF, some data read */
		} else
			return (-1);		/* error */
	}

	*ptr = 0;
	return (n);
}

err_dump(string)
char	*string;
{
	ErrorMessage (string);
	ErrorMessage (strerror(errno));
/*
	fprintf (	stderr, 
			"%s, error '%s'\n", 
			 string, strerror(errno));
 */
}

Debug (format, a, b, c, d, e, f, g)
char    *format;
void    *a, *b, *c, *d, *e, *f, *g;
{
	fprintf (stderr, format, a, b, c, d, e, f, g);
	fflush (stderr);
}

/*
** This hack can be eliminated when we're using a real form-displaying
** client which maintains the values in the form instance itself.
**
** For now, though, we manually transfer the values from the internal
** data into the form instance.
*/


PlugInData(forminstance)
ForminstancePtr forminstance;
{
	int		i;
	FieldPtr	myfield;
	TextInputPtr            mytextinput;
	NumericInputPtr         mynumericinput;
	BooleanInputPtr         mybooleaninput;  /* Not used yet */
	FormclassPtr		class;

	static char *coursenames[] = {"course1","course2","course3","course4","course5","course6"};
	static char *sectionnames[] = {"section1","section2","section3","section4","section5","section6"};

	class = forminstance->class;

	for (i = 0; i < MAX_CHOICES; i++) {
		myfield = FindFieldByName(coursenames[i], class);
		mytextinput = myfield->detail->inputptr->inputsource->text;
		strcpy (mytextinput->data, selections[i].course);

		myfield = FindFieldByName(sectionnames[i], class);
		mynumericinput = myfield->detail->inputptr->inputsource->numeric;
		mynumericinput->data = selections[i].section;
	}

	myfield = FindFieldByName("login", class);
	mytextinput = myfield->detail->inputptr->inputsource->text;
	strcpy (mytextinput->data, whoami);

	forminstance->owner = (char *) malloctrace(strlen(whoami) + 1);
	strcpy (forminstance->owner, whoami);
}

/*
** This hack can be eliminated when we're using a real form-displaying
** client which maintains the values in the form instance itself.
**
** Now, we transfer the field values one-by-one ffrom the instance
** into this client's data structures.  It's the same as PlugInData
** but going in the opposite direction.
*/


ReadOutData(forminstance)
ForminstancePtr forminstance;
{
	int			i;
	FieldPtr		myfield;
	TextInputPtr            mytextinput;
	NumericInputPtr         mynumericinput;
	BooleanInputPtr         mybooleaninput;		/* not used yet */
	FormclassPtr		class;

	static char *coursenames[] = {"course1","course2","course3","course4","course5","course6"};
	static char *sectionnames[] = {"section1","section2","section3","section4","section5","section6"};

	class = forminstance->class;

	for (i = 0; i < MAX_CHOICES; i++) {
		myfield = FindFieldByName(coursenames[i], class);
		mytextinput = myfield->detail->inputptr->inputsource->text;
		strcpy ( selections[i].course, mytextinput->data);

		myfield = FindFieldByName(sectionnames[i], class);
		mynumericinput = myfield->detail->inputptr->inputsource->numeric;
		selections[i].section = mynumericinput->data;
	}
}

/*
** Return the contents of a file as a char*.
*/

char	*
ReadFile(filename)
{

	char	*retval;
	int	buffsize = 512;
	int	charsread = 0, n;
	FILE	*infile;

	infile = fopen(filename, "r");
	if (infile == NULL) {
		fprintf(stderr, "Cannot open file %s\n", filename);
		return (NULL);
	}

	retval = (char *) malloc(buffsize);

	while (1) {
		n = fread (	retval + charsread,
				sizeof(char),
				buffsize,
				infile);
		charsread += n;

		if (n  < buffsize) {
			retval[charsread] = '\0';
			fclose(infile);
			return (retval);
		}

		else if (n == buffsize) {
			retval = (char *) realloc(retval, charsread + buffsize);
		}

		else {
			fprintf (stderr, "What am I doing here?\n");
		}
	}
}

/*
** return 1 if everything's okay, some other value otherwise.
*/

PhAssumeIdentity(who)
char	*who;
{
	int	i;
	char	sendline[MAXLINE];
	char	recvline[MAXLINE];

	sprintf (sendline, "iam %s\n", who);
	i = SendLine(sendline, recvline);
	if (i != 1) {
		ErrorMessage (recvline);
		return (0);
	}
	return (1);
}
