/******************************************************************************
 *	METAL Message section... for METAL version 1.20a
 *
 *	File name: MES.C version 1.20a
 *
 *			Copyright (c) 1984 Tim Gary
 *			    Delphi Data Systems
 *			    All rights reserved.
 *
 *
 *   This module contains common message base routines (read, msgheader, etc)
 *
 *****************************************************************************
 *
 * 1.20a 11/11/84  Nothing found message fixed in selective read.
 * 1.20<test c> 11/09/84  [Nothing found/not addressed to ya/dead] msg in read.
 * 1.20<test a> 11/04/84  Fixed timefix routine for no clock.
 * 1.10e 11/01/84  Allow for reading of dead msgs.
 * 1.10e 10/31/84  Width stuff changed in msgheader, fixed for unkill.
 * 1.10c 10/12/84  Made a few functions here overlays to conserve space.
 * 1.10b 10/09/84  Complete reworking of read message routines (internal only).
 * 1.10b 10/04/84  More cosmetics.. Should be it for a while.
 * 1.10a  9/28/84  Fixed reply problem (msg shown after reply..).
 * 1.10a  9/26/84  Added a few cosmetic changes (y/n)? and selective read help.
 * 1.10a  9/24/84  Fixed edit user/kill this msg, bugs.. (introduced 8/31/84)
 * 1.10a  9/12/84  Eliminated Reverse read bug (replies not listed)
 * 1.10a  9/12/84  Cosmetic changes.
 * 1.10a  9/09/84  Cosmetic changes.
 * 1.10a  9/07/84  Added Kill previous message to selective read for sysop.
 * 1.10a  9/06/84  Changed Edit/delete user on read for previous message.
 * 1.10a  8/31/84  Split for overlays, now contains readmsg() and commons.
 *
 * 1.01a  8/05/84  Got things going, added delete user, and edit user during
 *		  message read (prompted mode).
 * 1.01a  7/14/84  Message editing bugs fixed (delete line.. movmem changed).
 *		   Added subject truncation notice on message entry.
 * 1.01a  6/30/84  Cleaned up more functions.
 * 1.01a  6/26/84  Cleaned up a few functions, and added MULTI_USER stuff.
 * 1.01a  6/10/84  Fixed to work with Aztec C 1.06. Started Multi-user stuff.
 *
 * 1.0b   4/20/84  (cont) Added msearch, for string search for msgs..
 * 1.0b	  4/13/84  Modified for upper/lower case names, and added check for
 *		maximum messages allowed (killed and active ones).
 *
 * 1.0	  1/20/84  New format of Message file headers.
 * p3.0	 12/14/83  pre-release version 3.0 mods for MCONFIG..
 *
 *****************************************************************************/

#include "xpm.h"
#include "hmh.h"
#include "hmconfg.h"
#include "ctype.h"


getindex(m)
 register unsigned m;
{
register int a;
 if (m==0) return ERROR;
 for (a=0; a<mindex; a++) if (msg[a].number==m) break;
 if (msg[a].number!=m) return ERROR;
return a;
}

/* update message and summary file with the new reply/parent of the msg ate
 * the index (into msg array) passed to it 
 */

updatemsg(a)
 register unsigned a;
{
register unsigned *b;

messages=open(MESSAGES,F_RW | F_UNLOCK);
summary=open(SUMMARY,F_RW | F_UNLOCK);
b=bufloc(summary);
setarec(summary,1);
do  read(summary,1);	/* position to right place in sum. */
	while (*b!=msg[a].number);
setrrec(summary,-1);		/* back up one record */
setarec(messages,msg[a].seek);	/* seek to parrent loc */

movmem(&msg[a].number,bufloc(summary),8);	/* summary hdr same*/
movmem(bufloc(summary),bufloc(messages),128);	/* as message hdr  */

write(summary,0);
close(summary);

write(messages,0);
close(messages);

}	/* update msg */


#ifdef MULTI_USER

/*********************************************************
 * Check for new messages, update counters/array if some
 * are actually found.  Used only in Multi-user systems
 *********************************************************/

mu_update()
{
register unsigned tn_msg;
register msg_record *mptr;
register int rval=0;		/* return value # of new msgs */
unsigned new_msgs();

if (!(tn_msg=new_msgs())) return rval;	/* check if counters updated */

/* tn_msg will contain # of new 'nextmsg' counter, from counters file */

printf("\n%u new messages entered by other users.\n",tn_msg-nextmsg);
summary=open(SUMMARY,F_RD | F_UNLOCK);
mptr=bufloc(summary);
toeof(summary);	setrrec(summary,nextmsg-tn_msg);	/* backup # msgs */
rval=tn_msg-nextmsg;

while ((nextmsg++)<tn_msg)	/* and here we read new stuff */
	{
	read(summary,1);
	if (mptr->status!=DEADMSG)
		{
		msg[mindex].number=mptr->number;
		msg[mindex].seek=mptr->seek;
		msg[mindex].parent=mptr->parent;
		msg[mindex].reply=mptr->reply;
		++totalmsgs;  ++msgcount;
		if (thisis(mptr->reciever))
			printf("\n#%u from %s %s is for you.\n",mptr->number,
				mptr->fsend,mptr->lsend);
		}  /* if */
	}  /* while */
close(summary);
lmsg=msg[mindex-1].number;
putchar('\n');

return rval;	/* # of new msgs */
}  /* mu_update */


/*********************************************************
 * check for differing (in nextmsg) counters file
 * returns new nextmsg
 *********************************************************/

unsigned new_msgs()
{
unsigned tnext=0;

if ((counters=open(COUNTERS,F_RD | F_UNLOCK))!=NULL)
	{
	sscanf(bufloc(counters),"%*d %*d %d",&tnext);
	close(counters);
	if (tnext==nextmsg) tnext=0;
	}
return tnext;
}

#endif /* multi user */


/***************************
 * help for read functions *
 ***************************/

read_help(mode)
 char mode;
{
static char *r_hlp[] = {
	"\nEnter the message number you wish to retrieve.",
	"\nTo read a series of messages, enter a plus (+) or minus (-)",
	"\n(for increasing or decreasing order) after the number of",
	"\nthe first message you wish to read. (eg. 10+ or 280-)\n",
	0 };	/* help text */

if (mode=='S' || mode=='R')
	send("\nEnter the message you wish to start retrieval at.\n");
  else if (mode!='P') dis_text(r_hlp);

if (mode & 128) search_help();
}


selrd_help()
{
static char *nsr_help[] = {
	"\n> (Y)es, read this message.      (N)o, don't read it.",
	"\n> (R)eply to PREVIOUS message.   (Q)uit reading messages.\n",
	0 };
static char *ssr_help[] = {
	"\nSpecial SYSOP functions:",
	"\n> (K)ill PREVIOUS message.	    (W)rite message to disk file.",
	"\n> (P)rint message to LST: device",
	"\n> (E)dit sender of PREVIOUS message",
	"\n> (D)elete sender of previous message\n",
	0 };

if (dis_text(nsr_help)!=ERROR) if (user.status==SYSOP) dis_text(ssr_help);
}


/******************************************
 *  Read messages routine.		  *
 *  Code is much clearer, and is not	  *
 *  dependent on message structure.	  *
 ******************************************/

readmsgs(mode)
 int mode;
{
int firstm,lastm,inc;		/* message indexes and increment count	*/
register int flag;		/* flag for various things ERROR=abort	*/
register unsigned startmsg;	/* starting message number		*/
int read_flag;			/* flag for something read or not	*/

#ifdef MULTI_USER
  mu_update();
#endif

if (!msgcount)
       {
       send("\nSorry, there are currently no messages posted.\n");
       return;
       }

novhelp();

if (strloc==0 && user.parm.expert==POFF) read_help(mode);

   /*
    *  Loop the Loop...
    */

  do   {
       if (ovloader(OVMISC,GETRANGE,&mode,&firstm,&lastm,&inc)==ERROR) break;
	read_flag=FALSE;	/* nothing read yet */

       if ((messages=open(MESSAGES,F_RD | F_UNLOCK))==NULL)
	       {
	       send("\nUnable to open messages file.\n");
		read_flag=TRUE;		/* fake it out so only one msg! */
	       break;
	       }

       for (flag=NULL,startmsg=msg[firstm].number;
	    ((inc!=-1) ? (firstm<=lastm) : (firstm>=lastm)) && (flag!=ERROR);
	    firstm+=((inc==0) ? 1 : inc) )
	       {
		int loc_first,loc_reply;	/* local copies */
		loc_first=firstm;
		loc_reply=msg[firstm].reply;

	       if (msg[loc_first].number && (msg[loc_first].parent<startmsg) )
		    do {
		       flag=rm(msg[loc_first].number,mode);
			if (flag) read_flag=TRUE;
		       if (inc!=1) break;      /* single and backward.. */
		       if (!msg[loc_first].number)
				if (loc_reply) loc_first=getindex(loc_reply);
				  else break;
			if (loc_first!=ERROR) loc_reply=msg[loc_first].reply;
		       } while ((flag!=ERROR) &&
				((loc_first=getindex(msg[loc_first].reply))
				 != ERROR) && (loc_first<=lastm) );
	       } /* for */

if (!read_flag)
	send("\n[Nothing found.  Message(s) either private or deleted.]");

close(messages);
} while ((flag!=ERROR) && !inc);

putchar('\n');

} /* readmsgs */


/* This routine displays the message, it's header, from current place in file
 *  expects message number, and read mode given to it.
 *	mode is 'S' for selective read.. (ie prompt), else, no prompt
 *  returns ERROR if break hit, or can't read a record
 *  returns NULL if not addressed to user and private, or not selected
 *  returns #of lines if read.....
 */

rm(msgnum,rd_mode)
 unsigned msgnum;
 int rd_mode;
{
 register int lns;
 register unsigned saveseek;
 char str[3],ttt[80];
 int c;
 unsigned ind,mode;
 char tsender[FNAMELEN+LNAMELEN+3];

static char prevsender[FNAMELEN+LNAMELEN+3];
static unsigned prevmsg=0;		/* previous message #, for reply */
static char status=DEADMSG;		/* current message status	 */

mode=rd_mode&0x7f;
if ((ind=getindex(msgnum))==ERROR || msg[ind].number==0)  return NULL;

do {		/* silly loop to avoid goto, and for single exit point	 */
 setarec(messages,msg[ind].seek);	/* goto message location in file */
 lns=msgheader(messages,0,2|(rd_mode & 0x80));	/* read header info.. */

 status=message.status;
 if (lns==ERROR || lns==NULL) break;

 sprintf(tsender,"%s %s",message.fsend,message.lsend);
 saveseek=getrec(messages)-1;	/* save current loc  */

 if (mode=='S')		/* selective read */
	{
	if (user.status==SYSOP) send("[y/n/r/p/w/e/d/k/q]");
	   else send("[Read y/n/r/q/?]");
	c=toupper(getd());
	send(" [");
	if (c=='N') {	send("no]\n");  break;	}

	if (c=='Q') {	send("quit]");	lns=ERROR;	break;	}

	if (prevmsg!=0)
	  {
	  if (c=='R')
		{
		send("reply]\n\n");
		do_ov("[Reply to PREVIOUS message (y/n)? ]",OVSEND,0,prevmsg,0);
		continue;
		}
	  if (user.status==SYSOP)
	    {
	    int flag;
	    flag=TRUE;

	    switch(c)
	      {
	      case 'K':
		send("kill]\n\n");
		sprintf(ttt,"[Kill PREVIOUS message #%u (y/n)? ]",prevmsg);
		do_ov(ttt,OVKILL,KILL,prevmsg,0);
		break;
	      case 'E':
		send("edit user]\n\n");
		do_ov(0,OVUSER,EDITUSER,0,prevsender);
		break;
	      case 'D':
		send("delete user]\n\n");
		sprintf(ttt,"[Delete PREVIOUS sender (%s) from users file (y/n)? ]",prevsender);
		do_ov(ttt,OVUSER,DELETEUSER,0,prevsender);
		break;
	      default:
		flag=FALSE;
		break;
	      } /* switch */
	    if (flag) continue;		/* go back and get header stuff */
	    } /* sysop test */
	  } /* previous message available */

	if (user.status==SYSOP) {
		if (c=='W' || c=='P')
			  {
			  if (ovloader(OVMISC,OUTMSG,c,lns,0,0)) break;
			    else continue;
			  }
		} /* sysop test */

	if (c=='?') {
		send(" ? ]\n\n");
		selrd_help();	/* selective read help */
		continue;
		}

	send("yes]\n\n");
	} /* mode==TRUE */	/* else read the thing */

read(messages,1);		/* next record */
if (showmsg(lns)==ERROR) {  lns=ERROR; break;	}
putchar('\n');

if (thisis(message.receiver))
	{	/* message was to this guy.. does he want to reply? */
	if (do_ov("\n[Reply to this msg (y/n)? ]",OVSEND,0,msgnum,0)==TRUE)
	     sprintf(ttt,"\n[Kill Message you've just replied to (y/n)? ]");
	   else sprintf(ttt,"[Delete this Message (y/n)? ]");
	do_ov(ttt,OVKILL,KILL,msgnum,0);
	putchar('\n');
	}

  break;		/* continue gets by this crock */
   } while (1);

if ((rd_mode&0x80) || status!=DEADMSG)
	{
	prevmsg=msgnum;
	strcpy(prevsender,tsender);
	}

return lns;	/* return lines.. */
}


/* do overlay from selective read... special kill msg handling */

do_ov(atext,ovname,ovfn,uparm,cparm)
 char *atext,*ovname,*cparm;
 int ovfn;
 unsigned uparm;
{
int c;
int ret_flag=YES;

/* prompt and get single letter yes=y, no=other char */
if (atext) ret_flag=yes_no(atext);	

if (ret_flag)
	{
	if (!strcmp(OVKILL,ovname)) close(messages);	/* killmsg req. */

	if (cparm) ovloader(ovname,ovfn,cparm);	/* do char overlay func */
	  else	{
		ovloader(ovname,ovfn,uparm);	/* do unn overlay func	*/
		messages=open(MESSAGES,F_RD | F_UNLOCK);
		}
	putchar('\n');
	}

return ret_flag;	/* true=we did the overlay */
}


/* This routine prints the message from file ffd, for nl lines */

showmsg(nl)
 register int nl;
{
register int lc;

globalchar='\0';	/* clear this before and after showing msg! */
for (lc=0; lc<nl; lc++)
	{
	fgets(buffer,messages);			/* get a line	*/
	*(char *)O.RESETDRIVE;
	if (send(buffer)==ERROR) return ERROR;
	*(char *)O.RESETDRIVE;
	if ((globalchar & 0x1f)==0x0f) break;	/* ^O hit to skip to next */
	}
globalchar='\0';
return 0;
}


/* yes=y, any other=no */

yes_no(text)
 char *text;
{
register int ret_flag,c;
ret_flag=NO;			/* default return value */

send(text); c=toupper(getd());
if (c!='Y') send(" [no]\n");
    else {
	 ret_flag=YES;
	 send(" [yes]\n");
	 }

return ret_flag;
}


/****************************************************************
 * format message header info 					*
 * formats.  0=Quick Summary format    1=Full summary format	*
 *           2=Read Message format     3=Kill message format	*
 *	     4=unkill..						*
 ****************************************************************/

get_header(mode,buf)
 register int mode;
 register char *buf;
{
char tstr[30];
register int a;

*tstr='\0';

if (message.parent) 
	if ((a=msg[getindex(message.parent)].number)==ERROR) message.parent=0;

if (mode==0)
	sprintf(buf,"\n%u  %s",message.number,message.topic);
   else if (mode==1)
	{
	if (message.parent) sprintf(tstr,"[R/%u]",a);
	*(message.date+5)='\0';		/* chop off year... */
	sprintf(buf,"\n%4u %s %s  From: %s %s%s  To: %s %s : (%d) %s",
		message.number,message.date,tstr,
		message.fsend,message.lsend,
		user.parm.width<80 ? "\n    " : "",
		message.receiver,
		(message.status==PRIVMSG ? "<Priv>" :
		    message.status==DEADMSG ? "<Dead>" : " "),
		message.lines,message.topic);
	if (user.parm.width>=80)
		buf[user.parm.width]='\0'; /* cut off at 80 chars */
	}
     else
	if ((mode!=3 && mode!=4) || user.parm.expert==POFF)
	  {
	  if (message.parent) sprintf(tstr,"\n[Reply to msg #%u]\n",a);
	  sprintf(buf,"\nMsg #%u posted %s %s%s by %s %s\
\nTo: %s %s  About: %s (%d lines)\n%s\n",message.number,message.date,
O.RTC ? "at " : "",O.RTC ? message.time : "",
message.fsend,message.lsend,message.receiver,
message.status==PRIVMSG ? "<Priv>" : message.status==DEADMSG ? "<Dead>" : "  ",
message.topic,message.lines,tstr);
	  }
}


/* msgheader() displays the message header information in one of three
 * formats.  0=Quick Summary format    1=Full summary format
 *           2=Read Message format     3=Kill message format
 *	     4=Unkill msg
 * Bit 0x80 of mode indicates display even if dead.
 * It expects you to supply the file descriptor, a 'starting' msg number (which
 *	is only used by the summary funtions, and should be 1 or 0 for Read),
 *      and the mode as described above.
 *
 * it returns ERROR if it could not read the header, or ^K was entered aborting
 *  its output.
 * NULL (0) is returned if the message is private, and can't be read by the
 *  current user of the system.
 * if everything is ok, it returns the number of lines in the message.
 */

msgheader(ffd,startmsg,mode)
 FILE *ffd;
 unsigned startmsg;
 int mode;
{
register unsigned a;

if (read(ffd,1)!=128) return ERROR;
movmem(bufloc(ffd),&message,128);	/* get msg structure	*/
timefix(message.time);			/* make time look better.. */

if (message.status==DEADMSG) message.parent=message.reply=0;

if (mode==4)
	{
	if (message.status!=DEADMSG)
		{
		if (startmsg!=0) printf("\n[Message %u not dead!] ",message.number);
		return NULL;
		}
	}
  else if ((message.status==DEADMSG) && !(mode & 0x80)) return NULL;

/* send(".");	*/

if ((message.status==PRIVMSG || (message.status==DEADMSG && (mode & 0x80)))
	 && !thisis(message.receiver)
	 && ( (mode<3)
		? (O.user_types[user.type].readpriv==NO)
		: (O.user_types[user.type].killflag==NO) )
	 && (ustrcmp(message.fsend,user.first)
	     || ustrcmp(message.lsend,user.last))  )
	return NULL;

/* send(".");	*/

if (mode==3 && !thisis(message.receiver) &&
	(ustrcmp(message.fsend,user.first) || ustrcmp(message.lsend,user.last))
	&& O.user_types[user.type].killflag==NO) return NULL;

/* send(".");	*/

if (message.number>=startmsg || startmsg==0) 	
	{
	if (msearch(0)==0) return NULL;	/* no match.. ret */
	if (message.parent!=0)
		{	/* find original msg number */
		if (((mode&0x7f)==2) &&
		    (message.parent>=startmsg) && startmsg!=0)
			return NULL;
		while (msg[getindex(message.parent)].parent!=0)
			message.parent=msg[getindex(message.parent)].parent;

		/* parent now has 0 if none, else orig. msg number */
		}

	*(char *)O.RESETDRIVE;	/* defined in HMH.H	*/
	get_header(mode&0x7f,buffer);
	if ((mode!=3 && mode!=4) || user.parm.expert==POFF)  /* write line */
		if (send(buffer)==ERROR) return ERROR;
	*(char *)O.RESETDRIVE;

	}
	else return NULL;
return message.lines ? message.lines : 1;	/* if 0 lines, fake it.. */
}


/* convert passed string (containing time in hh:mm:ss format) to hh:mm[am|pm]
   format	*/

timefix(s)
 register char *s;
{
register int n;
char ch[3];

 if (strlen(s)>=8)
    {
    if (n=atoi(s))	/* if a number is there, assume correct format */
	if (n>=12)
		{
		strncpy(ch,s+3,2);	*(ch+2)='\0';	/* get mins in ch */
		sprintf(s,"%2d:%2s pm",n==12 ? 12 : n-12,ch);
		}
	  else strcpy(s+5," am");
     }
}


/***************
 * search help *
 ***************/

search_help()
{
static char *s_hlp[] = {
	"\nThe following message fields may be searched:",
	"\n  s: = subject field         d: = date field",
	"\n  f: = from (name) field     t: = to (name) field",
	"\n  *: = ALL of the above fields",
	"\nThe above may be mixed (ie. ft: = from and to)",
	"\nExamples:   244+ s:for sale    AND     *:my name\n",
	0 };	/* that's enuf for now */

dis_text(s_hlp);
}


/************************************************************************
 *  Search for string in message variables if passed string ptr=0,      *
 * else, setup the string to be compared to the message variables later.*
 *  string, if passed, format of:					*
 *	<s|d|f|t|*>:<search string>	s=subject search		*
 *		f=from search		t=to search	d=date search	*
 *		*=search all above fields				*
 *  examples:								*
 *	s:for sale		ft:tim gary	d:09-02-84		*
 *  in read operations:							*
 *	rs;30+ s:wanted		r;223+ *:sysop    etc...		*
 *									*
 *  returns:	bit 0 ON for SUBJECT match	bit 1 ON for DATE match *
 *		bit 2 ON for FROM match		bit 3 ON for TO match	*
 ************************************************************************/

char msearch(sstr)
 char *sstr;
{
char *ps;
register char ret_flag;
static spattern[MAXLINE+1],from_flag,to_flag,subject_flag,date_flag;

ret_flag=0;

if (sstr)	/* if not null ptr, setup search string */
	{
	*spattern='\0';	/* make empty string at first */
	from_flag=to_flag=subject_flag=date_flag=NO;	/* all off	*/
	if (ps=index(sstr,':'))		/* '=' IS ON PURPOSE!!!!!!!!	*/
		{			/* search pattern given..	*/
		while (--ps>=sstr)	/* backwards parse from ':'	*/
			{
			switch (*ps--) {
				case 'S':
					subject_flag=YES;
					break;
				case 'D':
					date_flag=YES;
					break;
				case 'F':
					from_flag=YES;
					break;
				case 'T':
					to_flag=YES;
					break;
				case '*':
					subject_flag=date_flag=from_flag=to_flag=YES;
					/* fall through to default.. */
				default:
					ps=sstr;	/* stop loop here */
				} /* switch */
			} /* while loop */
		strcpy(spattern,index(sstr,':')+1);	/* get rest of line */
		ret_flag=TRUE;				/* flag search */
		} /* if ':' test */
	} /* main 'string passed' if */
  else {	/* no string passed, compare with message fields */
	if (!subject_flag && !date_flag && !from_flag && !to_flag)
			ret_flag=128;	/* bit 7 set if no compare */
	   else	{
		char tstr[MAXLINE+1];	/* used for sender string */
		if (subject_flag)
			ret_flag|=(usindex(spattern,message.topic) ? 1 : 0);
		if (date_flag)
			ret_flag|=(usindex(spattern,message.date) ? 2 : 0);
		if (from_flag) {
			sprintf(tstr,"%s %s",message.fsend,message.lsend);
			ret_flag|=(usindex(spattern,tstr) ? 4 : 0);
			}
		if (to_flag)
			ret_flag|=(usindex(spattern,message.receiver) ? 8 : 0);
		} /* check pattern?? if */
	}

return ret_flag;	/* return value */
}	/* msearch */



/* End of MES.C */


