/*
 *	Copyright (C) 1987  SRI International
 *	Copyright (C) 1989, 1990, 1992, 1993  TGV, Incorporated
 *
 *	TOPS-20 style BBOARD program.
 *
 *	Now includes VMS-style qualifiers thanks to Jeff Shulman at SDR
 *		/LAST			Read last message NOW
 *		/NOPAGE			Ignore page length
 *		/AFTER=date		Force last read date
 *
 *
 *	In Unix these are the args:
 *		-last
 *		-nopage
 *		-after
 *
 */
#include "mm_header.h"
#include <signal.h>
#include <ctype.h>
#include <errno.h>
#include <dvidef.h>
#define GETUID()	((getgid() << 16) + getuid())
#include "gtjfn.h"
#include "jfns.h"
#include "date.h"
#include "multinet_root:[multinet.include.sys]types.h"
#include "multinet_root:[multinet.include.sys]socket.h"
#include <stdio.h>

int Text_File_FD;				/* File descriptor for text */
struct Msg *Msgs;				/* Message headers	    */
struct Header_File_Header Header;		/* Header file header	    */
int Message_File_Last_Modified;			/* Last time BBoard was read*/
int Text_Offset;				/* Msg offset in text file  */
int Current_Message;				/* Current Message #	    */
char Tops_File[256];				/* Tops-20 style name	    */
char Text_File[256];				/* Name of .TXT file	    */
char Header_File[256];				/* Name of Header file	    */
char Bboard_Name[128];				/* Name of BBOARD	    */
int Do_Pagination;				/* Paginate output	    */
int Force_Last;					/* Force to last message    */
int Force_Starting_Time;			/* Force starting time	    */
globalref int bboard_cld;

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

	/*
	 *	Do the system-dependent argument parsing
	 */
	Parse_Args(argc,argv);
	/*
	 *	Open the header cache file
	 */
	fd = open(Header_File,0,0,"shr=get","shr=put","shr=upd");
	if (fd < 0) exit(1);
	/*
	 *	Read in the header file header
	 */
	if (read(fd,&Header,sizeof(Header)) != sizeof(Header)) {
		printf("?Couldn't read in message header file\n");
		close(fd);
		exit(1);
	}
	/*
	 *	Read in the rest of the header file
	 */
	Msgs = (struct Msg *)mm_malloc(Header.Number_Of_Messages * sizeof(struct Msg));
	if (Msgs == 0) {
		printf("?Insufficient memory to hold all message headers\n");
		close(fd);
		exit(1);
	}
	if (read(fd,Msgs,Header.Number_Of_Messages * sizeof(struct Msg)) !=
			(Header.Number_Of_Messages * sizeof(struct Msg))) {
		printf("?Inconsistent header file\n");
		close(fd);
		exit(1);
	}
	/*
	 *	Close the header file
	 */
	close(fd);
	/*
	 *	Open the data file, get our Last Read date
	 *	and set up things for processing the BBOARD
	 */
	Get_Bboard_Read_Date1();
	/*
	 *	Open the text file
	 */
	Text_File_FD = open(Text_File,0,0,"shr=get","shr=put","shr=upd");
	if (Text_File_FD < 0)
		exit(1);
	/*
	 *	Get an Interrupt catcher set up
	 */
	{
		extern Exit_BBoard();
		signal(SIGINT,Exit_BBoard);
	}
	/*
	 *	Set Cbreak mode so we can snarf each character
	 */
	Cbreak_On();
	/*
	 *	Get page length
	 */
	if ((Current_Message < Header.Number_Of_Messages) &&
	    Do_Pagination) {
		int Page_Size = 0;
		int Status;
		struct {int Size; char *Ptr;} TT_Name;
		struct {
			unsigned short int Size;
			unsigned short int Code;
			char *Buffer;
			unsigned short int *Resultant_Length;
			} GetDvi_Item_List[2];

		/*
		 *	Get our TTY terminal type from VMS
		 */
		GetDvi_Item_List[0].Size = sizeof(Page_Size);
		GetDvi_Item_List[0].Code = DVI$_TT_PAGE;
		GetDvi_Item_List[0].Buffer = (char *)&Page_Size;
		GetDvi_Item_List[0].Resultant_Length = 0;
		GetDvi_Item_List[1].Size = 0;
		GetDvi_Item_List[1].Code = 0;
		TT_Name.Size = 2;
		TT_Name.Ptr = "TT";
		Status = sys$getdviw(0,0,&TT_Name,GetDvi_Item_List,0,0,0,0);
		if (Status & 1) {
			/*
			 *	Initialize the page size
			 */
			extern noshare int tops_tty_page_size;

			tops_tty_page_size = Page_Size;
		}
	}
	/*
	 *	Do Bboard processing
	 */
	while(Current_Message < Header.Number_Of_Messages)
		Next_Bboard_Message();
	/*
	 *	Done
	 */
	Exit_BBoard();
}


/*
 *	Do the next Bboard message
 */
Next_Bboard_Message()
{
	static char *Text = 0;
	static int Text_Size = 0;
	register char *cp,*cp1,*cps,*cpe;
	register int i;
	char Header_Line[512], Command[4];

	/*
	 *	Seek to message text
	 */
	if (lseek(Text_File_FD,Text_Offset,0) != Text_Offset) {
		printf("?Text file seek failed\n");
		exit(1);
	}
	/*
	 *	Allocate space for text
	 */
	if (Text_Size < Msgs[Current_Message].Real_Size) {
		if (Text == 0) {
			Text = (char *)
				   mm_malloc(Msgs[Current_Message].Real_Size);
		} else {
			Text = (char *)
				   mm_realloc(Text,Msgs[Current_Message].Real_Size);
		}
		if (Text == 0) {
			printf("?Out of memory!\n");
			exit(1);
		}
		Text_Size = Msgs[Current_Message].Real_Size;
	}
	/*
	 *	Read in the message
	 */
	if (read(Text_File_FD,Text,Msgs[Current_Message].Real_Size) !=
				Msgs[Current_Message].Real_Size) {
		printf("?Read error on message text file\n");
		exit(1);
	}
	/*
	 *	Get the date field and put it in the prompt
	 */
	cp1 = Header_Line;
	cp = Text + Msgs[Current_Message].Header_Size;
	i = Msgs[Current_Message].Body_Offset -
			Msgs[Current_Message].Header_Size;
	while(i > 5) {
		if (((cp[0] == 'D') || (cp[0] == 'd')) &&
		    ((cp[1] == 'A') || (cp[1] == 'a')) &&
		    ((cp[2] == 'T') || (cp[2] == 't')) &&
		    ((cp[3] == 'E') || (cp[3] == 'e')) &&
		    (cp[4] == ':')) {
			i -= 5;
			cp += 5;
			while((i > 0) && ((*cp == ' ') || (*cp == '\t'))) {
				cp++;
				i--;
			}
			while((i > 0) && (*cp != '\n')) {
				*cp1++ = *cp++;
				i--;
			}
			*cp1++ = ' '; *cp1++ = ' '; *cp1++ = ' '; *cp1++ = ' ';
			break;
		}
		while(--i >= 0)
			if (*cp++ == '\n') break;
	}
	/*
	 *	Put the "From" field in the prompt
	 */
	if ((i = Msgs[Current_Message].From_Size) != 0) {
		cp = Text + Msgs[Current_Message].From_Offset;
		while(--i >= 0) *cp1++ = *cp++;
	}
	*cp1++ = '\n';
	/*
	 *	Put the subject field in the prompt
	 */
	if ((i = Msgs[Current_Message].Subj_Size) != 0) {
		*cp1++ = ' '; *cp1++ = ' '; *cp1++ = ' '; *cp1++ = ' ';
		*cp1++ = ' '; *cp1++ = ' '; *cp1++ = ' '; *cp1++ = ' ';
		cp = Text + Msgs[Current_Message].Subj_Offset;
		while(--i >= 0) *cp1++ = *cp++;
		*cp1++ = '\n';
	}
	/*
	 *	Insert the REST of the prompt
	 */
	sprintf(cp1,"(%d chars; more?) ",Msgs[Current_Message].Size);

	cp = cp1;
	while(*cp1) cp1++;
	cpe = cp1;
	cps = Header_Line;
	/*
	 *	Write the prompt
	 */
	{extern noshare int tops_tty_cur_line; tops_tty_cur_line = 0;}
	{extern noshare int tops_tty_control_o; tops_tty_control_o = 0;}
	while (1) {
		psout_jsys(cps,cpe - cps);
		/*
		 *	Get command (Quit on error)
		 */
		if (ttyread(0,Command,1) <= 0) Command[0] = 'q';
		if (Command[0] != '\n') write(1,"\n",1);
		/*
		 *	Dispatch on command
		 */
		switch(Command[0]) {
			/*
			 *	Help
			 */
			case '?':
				printf("\n\
Legal commands are:\n\
\n\
<SPACE>, <CR>, or Y	Show the rest of this bulletin\n\
<DEL> or N		Skip this bulletin, go to the next one\n\
B			Back up one message\n\
E or Q			Quit reading this bulletin board\n\
H			Show the header again\n\
M			Call MM on this bulletin board\n\
P			Push to a new Exec\n\
\n");

/*
 * Add the following back later, when it's supported.
 */

				printf("Reading %s\n\n",Tops_File);
				cps = Header_Line;
				break;
			/*
			 *	Print message
			 */
			case ' ':
			case '\n':
			case '\r':
			case 'y':
			case 'Y':
				psout_jsys(
				    Text+Msgs[Current_Message].Body_Offset,
				    Msgs[Current_Message].Real_Size -
					Msgs[Current_Message].Body_Offset);
				if (Message_File_Last_Modified <
				    Msgs[Current_Message].Date)
					Message_File_Last_Modified =
					    Msgs[Current_Message].Date;
				Text_Offset += Msgs[Current_Message].Real_Size;
				Current_Message++;
				return;
			/*
			 *	Skip message
			 */
			case 'n':
			case 'N':
			case 127:
				if (Message_File_Last_Modified <
				    Msgs[Current_Message].Date)
					Message_File_Last_Modified =
					    Msgs[Current_Message].Date;
				Text_Offset += Msgs[Current_Message].Real_Size;
				Current_Message++;
				return;
			/*
			 *	Previous message
			 */
			case 'b':
			case 'B':
				if (Current_Message > 0) {
					Current_Message--;
					Text_Offset -=
					    Msgs[Current_Message].Real_Size;
				}
				return;
			/*
			 *	Quit
			 */
			case 'q':
			case 'Q':
			case 'e':
			case 'E':
			case 26 :
				Exit_BBoard();
			/*
			 *	Show header again
			 */
			case 'h':
			case 'H':
				cps = Header_Line;
				break;
			/*
			 *	Call MM
			 */
			case 'm':
			case 'M': {
				struct {int Size; char *Ptr;} Descr;
				Cbreak_Off();
				Set_Bboard_Read_Date1();
				sprintf(Header_Line,"MM BBOARD %s", Text_File);
				Descr.Ptr = Header_Line;
				Descr.Size = strlen(Header_Line);
				LIB$SPAWN(&Descr,0,0,0,0,0,0,0,0,0,0,0);
				Get_Bboard_Read_Date1();
				Cbreak_On();
				return;
			}
			/*
			 *	Push
			 */
			case 'p':
			case 'P':
				Cbreak_Off();
				Set_Bboard_Read_Date1();
				suspend_program();
				Cbreak_On();
				cps = Header_Line;
				break;
			/*
			 *	Undefined command
			 */
			default:
				/*
				 *	Redo the prompt and go again
				 */
				cps = cp;
		}
	}
}


/*
 *	Exit from Bboard
 */
Exit_BBoard()
{

	/*
	 *	Turn off CBREAK mode
	 */
	Cbreak_Off();
	/*
	 *	Update the LAST modified date
	 */
	Set_Bboard_Read_Date1();
	/*
	 *	Done
	 */
	exit(1);
}


/*
 *	Get the BBoard last read date
 */
Get_Bboard_Read_Date1()
{

	/*
	 *	Get the Bboard last read date
	 */
	Get_Bboard_Read_Date(Text_File, &Message_File_Last_Modified);
	/*
	 *	If forcing STARTING time, fake the last read date
	 */
	if (Force_Starting_Time) {
		Message_File_Last_Modified = Force_Starting_Time;
		Force_Starting_Time = 0;
	}
	/*
	 *	If forcing LAST message, fake the last read date
	 */
	if (Force_Last) {
		Message_File_Last_Modified =
			Msgs[Header.Number_Of_Messages - 1].Date -1;
		Force_Last = 0;
	}
	/*
	 *	If last message in BBoard is before our last read date, we
	 *	are done.
	 */
	if (Msgs[Header.Number_Of_Messages - 1].Date <=
					Message_File_Last_Modified)
		exit(1);
	/*
	 *	Calculate the offset for the next unseen message
	 */
	Text_Offset = 0;
	Current_Message = 0;
	while(Current_Message < Header.Number_Of_Messages) {
		if (Msgs[Current_Message].Date >
				Message_File_Last_Modified) break;
		Text_Offset += Msgs[Current_Message].Real_Size;
		Current_Message++;
	}
}


/*
 *	Set the BBoard last read date
 */
Set_Bboard_Read_Date1()
{
	Set_Bboard_Read_Date(Text_File, Message_File_Last_Modified);
}


/*
 *	Turn "CBREAK" mode on
 */
Cbreak_On()
{
}

/*
 *	Turn "CBREAK" mode off
 */
Cbreak_Off()
{
}


/*
 *	Read a single character from TT:
 */
#include <iodef.h>
int ttyread(fd,buf,size)
char *buf;
{
	static unsigned short int TT_Channel;
	int Status;
	unsigned short int IOSB[4];

	/*
	 *	Open "TT" if necessary
	 */
	if (TT_Channel == 0) {
		struct {int Size; char *Ptr;} Descr;
		extern noshare int vmserrno;

		Descr.Size = 2;
		Descr.Ptr = "TT";
		Status = sys$assign(&Descr,&TT_Channel,0,0);
		if (!(Status & 1)) {
			vmserrno = Status;
			socket_perror("$ASSIGN (TT:)");
			exit(1);
		}
	}
	/*
	 *	Read 1 character
	 */
	Status = sys$qiow(0,TT_Channel,IO$_READVBLK,IOSB,0,0,
			buf,1,0,0,0,0);
	if (Status & 1) Status = IOSB[0];
	if (!(Status & 1)) {
		extern int vmserrno;

		vmserrno = Status;
		socket_perror("$QIOW");
		exit(1);
	}
	if (IOSB[1] == 0) {
		/*
		 *	Only the terminator was seen!
		 */
		*buf = IOSB[2];
	}
	return(1);
}


/*
 *	Parse command arguments
 */
Parse_Args(argc,argv)
char *argv[];
{
	char BBoard_File_Defaults[256];

    	cli_parse_command("BBOARD", &bboard_cld);
	/*
	 *	Parse the "/LAST" Qualifier
	 */
	if (cli_present("LAST")) Force_Last = 1;
	/*
	 *	Parse the "/PAGE" Qualifier
	 */
	if (cli_present("PAGE")) Do_Pagination = 1;
	/*
	 *	Parse the "/AFTER" Qualifier
	 */
	if (cli_present("AFTER")) {
		char tmp[64];
		int Date[2];

		tmp[0] = 0;
		cli_get_value("AFTER",tmp,sizeof(tmp));
		if (tmp[0] == '\"') {
			strcpy(tmp,tmp+1);
			tmp[strlen(tmp)-1] = 0;
		}
		if (idtim_jsys(tmp,0,Date) < 0) {
		  if (idtim_jsys(tmp,IDT_NO_TIME,Date) < 0) {
		    if (idtim_jsys(tmp,IDT_NO_DATE,Date) < 0) {
			printf("?Bad Time/Date specification\n");
			exit(1);
		    }
		  }
		}
		Force_Starting_Time = Date[0];
	}
	/*
	 *	Parse the "BBOARD" parameter
	 */
	if (cli_present("BBOARD"))
		cli_get_value("BBOARD",Bboard_Name,sizeof(Bboard_Name));
	/*
	 *	Get the default bboard file spec.
	 */
	BBoard_File_Defaults[0] = 0;
	if (cli_present("BBOARD_FILE_DEFAULTS"))
		cli_get_value("BBOARD_FILE_DEFAULTS",
			      BBoard_File_Defaults,
			      sizeof(BBoard_File_Defaults));
	/*
	 *	Setup the Bboard files
	 */
	Setup_BBoard_Files(BBoard_File_Defaults,Bboard_Name);
}


/*
 *	Setup the Bboard files
 */
Setup_BBoard_Files(Defaults,Bboard)
char *Defaults;
char *Bboard;
{
	register char *cp,*cp1;
	int jfn;
	struct gtjfn_block gtjfn;
	char Local[256];
	char Filename[128];
	char Extension[128];

	/*
	 *	Setup the defaults for parsing the BBOARD spec.
	 */
	bzero(&gtjfn,sizeof(gtjfn));
	gtjfn.default_device = "BBOARD:";
	gtjfn.default_filename = "MAIL";
	gtjfn.default_extension = "TXT";
	/*
	 *	Setup the BBOARD file defaults
	 */
	cp = Defaults;
	cp1 = Local;
	if (*cp) {
		/*
		 *	Device spec:
		 */
		while(*cp)
			if ((*cp1++ = *cp++) == ':') break;
		if (*cp) {
			gtjfn.default_device = Local;
			*cp1++ = 0;
		} else {
			cp = Defaults;
		}
	}
	if ((*cp == '<') || (*cp == '[')) {
		gtjfn.default_directory = cp1;
		while(*cp) {
			if ((*cp1++ = *cp++) == '>') break;
			if (cp[-1] == ']') break;
		}
		*cp1++ = 0;
	}
	if (*cp != '.') {
		gtjfn.default_filename = cp1;
		while(*cp)
			if ((*cp1++ = *cp++) == '.') {cp--; cp1--; break;}
		*cp1++ = 0;
		if (*gtjfn.default_filename == 0)
			gtjfn.default_filename = "MAIL";
	}
	if (*cp == '.') {
		cp++;
		gtjfn.default_extension = cp1;
		while((*cp1++ = *cp++) != 0) ;
		*cp1 = 0;
		if (*gtjfn.default_extension == 0)
			gtjfn.default_extension = "TXT";
	}
#ifdef	DEBUG
	printf("Defaults = %s,%s,%s,%s\n",
		gtjfn.default_device ? gtjfn.default_device : "[NONE]",
		gtjfn.default_directory ? gtjfn.default_directory : "[NONE]",
		gtjfn.default_filename ? gtjfn.default_filename : "[NONE]",
		gtjfn.default_extension ? gtjfn.default_extension : "[NONE]");
#endif
	/*
	 *	Use GTJFN to parse the bboard name
	 */
	gtjfn.flags = GTJFN_OLD_FILE;
	if (gtjfn_jsys(&gtjfn,Bboard,&jfn) < 0) {
		printf("?BBoard \"%s\" not found.\n",Bboard);
		exit(1);
	}
	/*
	 *	Get the TOPS-20 filename
	 */
	jfns_jsys(Tops_File,jfn);
#ifdef	DEBUG
	printf("Bboard file is \"%s\"\n",Tops_File);
#endif
	/*
	 *	Break the filename up into components
	 */
	jfns_jsys1(Local,jfn,
			JFNS_FILENAME(JFNS_DONT_OUTPUT) |
			JFNS_EXTENSION(JFNS_DONT_OUTPUT) |
			JFNS_VERSION(JFNS_DONT_OUTPUT));
#ifdef	DEBUG
	printf("Local is \"%s\"\n",Local);
#endif
	jfns_jsys1(Filename,jfn,
			JFNS_DEVICE(JFNS_DONT_OUTPUT) |
			JFNS_DIRECTORY(JFNS_DONT_OUTPUT) |
			JFNS_EXTENSION(JFNS_DONT_OUTPUT) |
			JFNS_VERSION(JFNS_DONT_OUTPUT));
#ifdef	DEBUG
	printf("Filename is \"%s\"\n",Filename);
#endif
	jfns_jsys1(Extension,jfn,
			JFNS_DEVICE(JFNS_DONT_OUTPUT) |
			JFNS_DIRECTORY(JFNS_DONT_OUTPUT) |
			JFNS_FILENAME(JFNS_DONT_OUTPUT) |
			JFNS_VERSION(JFNS_DONT_OUTPUT));
#ifdef	DEBUG
	printf("Extension is \"%s\"\n",Extension);
#endif
	/*
	 *	Get the text file local representation and close the JFN
	 */
	local_jfns_jsys(Text_File,jfn);
	rljfn_jsys(jfn);
#ifdef	DEBUG
	printf("Local TEXT file is \"%s\"\n",Text_File);
#endif
	/*
	 *	Get the Header file
	 */
	strcpy(Header_File,Text_File);
	cp = Header_File + strlen(Header_File);
	cp[6] = 0;
	while((cp > Header_File) &&
	      isdigit(cp[-1])) {
		/*
		 *	Strip off any version number
		 */
		cp--;
		cp[6] = *cp;
		if (cp[-1] == '.') {
			cp--;
			cp[6] = 0;
			break;
		}
	}
	while(cp > Header_File) {
		cp--;
		cp[6] = *cp;
		if ((*cp == ']') || (*cp == '>')) {cp++; break;}
	}
	cp1 = "$HDRS$";
	while(*cp1) *cp++ = *cp1++;
#ifdef	DEBUG
	printf("Local Header file is \"%s\"\n",Header_File);
#endif
}
