#include <ascii.h>
#include "fido.h"
#include "fidomem.h"
#include <xfbuf.h>
#include "proto.h"

static char ns[SS];			/* inputted list of nodes */

#define min(a,b) ((a)<=(b)?(a):(b))

/* Message creation & editing:

NOTE: The text array here is a simple linear array, treated as a 
series of SS column arrays. Line #0 starts at text, line 1 at
text + SS, etc. This is addressed variously as char text[]; (text[n * SS])
or as a pointer to an array of char[SS] lines.

msend(replynum,comment)
	Generates a new message or reply. If replnum is not 0, then it
	creates a reply to the specified message number, linking itself
	in to the chain. 
	If comment is true, then the message is addressed To: the first
	record in CALLER.SYS and does not prompt for node numbers etc.

	KLUDGE: This does the IFNA Kludge, by prepending the "^AINTL "
	line to messages saved in the FidoNet area.

editmsg(n)
	Load and modify an existing message number.

msgedit(t,msgnbr,comment,replynum,text,tlines,ln,width)
	Menu of edit commands to modify the message header and text. T is
	a pointer to the message header; text is a pointer to the text and
	tlines is the maximum line number. ln is the current line number.
	Returns true if the message should be saved.

edithdr(t,comment,replynum,first)
	Edit the msg header items; first true allows a blank line for the
	To: and Subj: fields, which indicates the message should be aborted.

sethdr(t,comment,replynum)
	Clears out the message header, and fills in the fields according to
	the flags passed; if comment, then CALLER.SYS record 0 is read and
	used if found; the source/dest node numbers filled in, etc.

msgline(n,text)
	Displays text line N.

getlno(prompt,limit,&n,&r)
	Displays the prompt followed by "line number" and inputs a number
	1 to limit, with error checking. This allows a range to be specified
	by "2-5", ie. starting at line 2 with a range of 3. If no range is
	specified r returns as 1. Note that the range is "inclusive" -- for
	this example the range is 4 not 3. (Lines 2,3,4,5) Returns n == -1 
	if error. Note that "2-" is acceptable also; it means 2 with a range
	that includes the last line.

savemsg(t,text,f)
	Writes out the message and increments the message number. Returns
	0 if there was a serious error. This checks for creation errors
	(bad path) disk full, etc. The message number and highhest message
	are advanced. For TWIT callers, it delays 3 seconds and does not 
	save.
	This also writes out the IFNA Kludge lines INTL for all FidoNet
	messages, and FMPT and TOPT for all point addressed messages.

	Flag 'f' determines the type of save -- 1 is a normal save, 2
	adds CRs to the end of each line.

writemsg(n,t,text,hardcr)
	Creates disk file n.MSG in the current message area and writes the
	message out to disk. Does the actual error checking above and returns
	0 if any error. If 'hardcr' == 2, makes sure that every line
	has a hard CR.

add_reply(n)
	Links the current message in memory into the chain of messages
	starting with message n. Checks for self referential messages
	and dead chains.

fix_kludge(text array)
	Removes all INTL, FMPT and TOPT lines, and returns the number
	of lines deleted.

	(Commented out one:
	This function reorders the lines in the message text array so
	that all the ^A lines are at the top. Returns non-zero if any
	lines were reordered.)

gtnodes(ns)

*/

/* Edit an existing message. */

int editmsg(msgnbr)
int msgnbr;
{
int width;		/* working screen width */
char *tb;		/* raw text buffer */
unsigned tlines;	/* lines in tb */
int hidden;		/* number of hidden lines */
int ln;			/* highest line number */
int i;
FLAG save;
char buff[SS],name[SS];

	strcpy(ns,str_node(&msg_dest));		/* default msg destination */
	width= uval(CLR_WID,CLR_WIDS);
	if (width > (SS - 2)) width= SS - 2;	/* set max. screen width */

/* Note that we "hide away" the first line of text we get; in there
we put the EDITED notice so it's not available for editing. All messages
that are edited must be marked so. */

	tb= text;				/* ptr to work buffer */
	tlines= textsize / SS;			/* get space to enter message */
	if (tlines == 0) {			/* whole lines only */
		closemsg();
		mprintf(CM+190);		/* "not enough memory" */
		return(0);
	}

/* If the message has been read at least once add the "NOTE: Message 
modified..." text as the first line of the message. (msg.times is incremented 
only when the msg has been read by other than the author.) */

	hidden= (msg.times > 1) ? 1 : 0;	/* room for the "[NOTE: ...." text */
	tlines -= hidden;			/* we add just before save */
	tb += (hidden * SS);

	ln= loadtext(msgfile,width,tb,tlines); /* load the text, */
	closemsg();				/* close it now */
	mprintf(CM+191,ln);			/* "loaded N lines of text" */
	if (ln == tlines) {
		mprintf(CM+192);		/* "lines may be lost" */
		mprintf(CM+193);
	}
	ln -= fix_kludge(tb);			/* delete address kludges */

	cmdflush();				/* flush typeahead */
	gtod(msg.date);
	listhdr(&msg,msgnbr);			/* list it as it is now, */
	if (uval(CLR_HLP,CLR_HLPS) == NOVICE) {
		listhdr(&msg,msgnbr);
		for (i= 0; i <= ln; i++) {
			msgline(i,tb);
			if (abort) break;
		}
		mcrlf();
	}
	mprintf(CM+194,msgnbr);			/* "you may now edit..." */
	save= msgedit(&msg,msgnbr,0,msg.reply,tb,tlines,ln,width); /* msg edit commands, */

	if (save) {				/* ie. not A)bort */
		if (shared()) {
			mprintf(CM+8);		/* "wait..." */
			msgcount();
		}
		if (hidden) {			/* if we must add [NOTE... */
			tb -= (hidden * SS);	/* un-hide top line */
			tlines += hidden;	/* and add the text */
			lprintf(LM+32,msgnbr,msgarea.number + 1);
			sprintf(tb,CM+195);	/* "[NOTE: message modified" */
			strproc(buff,msg.from,99); /* copy/process to fixed format name */
			strproc(name,caller.name,99);
			if (! same(name,buff)) {
				strcat(tb,", "); /* say by who if different */
				strcat(tb,caller.name);
			}
			gtod(buff);		/* current time/date */
			sprintf(&tb[strlen(tb)],0,", %s]\r\n",buff);
		}
		msg.attr &= ~(MSGREAD | MSGSENT); /* not read/sent yet (again ...) */
		save= writemsg(msgnbr,&msg,tb,save == 2); /* remember sucess */
	}
	return(save);
}

/* Load message text into an array; return the number of lines read. */

static loadtext(f,width,tb,tlines)
int f;			/* open file */
int width;		/* maximum width */
char *tb;		/* text buffer, */
int tlines;		/* maximum lines */
{
char word[SS];		/* word we build char at a time */
int ln;			/* highest line number */
char c,lastc;
int i;

	if (width > sizeof(word)) width= sizeof(word);	/* be reasonable */
	width -= sizeof("99: ");			/* editor prompt width */

	for (i= 0; i < tlines; ++i) tb[i * SS]= NUL;	/* clear all lines */

	for (ln= 0; ln < tlines; ) {
		i= 0;
		word[i]= NUL;
		while (wordlen(word,0) < width) {	/* make a word first */
			lastc= c;			/* remember previous character, */
			c= NUL;				/* (in case EOF) */
			if (! read(f,&c,1)) break;	/* check EOF */
			switch (c) {
				case CR + 128: if (lastc == ' ') c= NUL;
						else c= ' '; break;
				case LF: c= NUL;
			}
			if (c == NUL) continue;		/* skip this */
			if (c == CR) break;		/* stop if CR, */

			word[i++]= c;			/* store others, */
			word[i]= NUL;			/* mark the end of the word */
			if (c == TAB) break;
			if (c == ' ') break;
		}

/* We have a word in the array; if the line isnt too long, add it onto the 
line we are on; if it would make the line too long, end this line and go to
the next. */

		if (wordlen(&tb[ln * SS],0) + i >= width) { /* if too long, */
			if (++ln >= tlines) break;	/* next line (if room) */
		}
		strcat(&tb[ln * SS],word);		/* add it in */

		if (c == CR) {
			strcat(&tb[ln * SS],"\r\n");
			if (++ln >= tlines) break;
		}

/* If we had an EOF before, terminate. */

		if (c == NUL) break;			/* check EOF */
	}
	return(ln);
}

/* Remove INTL, FMPT and TOPT lines, and return the number of lines deleted. */

static fix_kludge(tb)
char *tb;
{
char *to,*from;
int lines;

	lines= 0;
	for (to= from= tb; *from; from += SS) {		/* check each line */
		if (to != from) {			/* copy over deleted */
			from[SS - 1]= NUL;		/* lines */
			strcpy(to,from);
		}			
		if (   (fcomp("\001INTL",from,5) != 0)	/* if this line */
		    && (fcomp("\001FMPT",from,5) != 0)	/* contains none */
		    && (fcomp("\001TOPT",from,5) != 0) )
			to += SS;			/* retain it */

		else ++lines;				/* else delete it */
	}
	*to= NUL;					/* truncate last line */
	return(lines);
}

/* Enter a new message or reply. */

msend(replynum,comment)
int replynum;		/* 0 else a reply to this message */
int comment;		/* 1 == comment to CALLER.SYS record #0 */
{
int width;		/* working screen width */
struct _msg t;		/* new message header */
char *tb;		/* raw text buffer */
unsigned tlines;	/* text buff as allocated for later release */
int ln;			/* highest line so far */
FLAG save;

char *cp;
int i,j,saved;

	width= uval(CLR_WID,CLR_WIDS);
	if (width > (SS - 2)) width= SS - 2;	/* set max. screen width */

	mprintf(CM+196,*msg_highest() + 1);	/* "this will be msg #..." */
	sethdr(&t,comment,replynum);		/* initialize the message header, */
	if (!edithdr(&t,comment,replynum,1)) return(0);	/* message aborted */

	tb= text;
	tlines= min(textsize / SS,fido.textlines + 1);
	if (tlines <= 1) {			/* whole lines only */
		mprintf(CM+190);			/* "not enoug memory" */
		return(0);
	}
	for (i= 0; i < tlines; i++) 		/* clear the text, */
		tb[i * SS]= NUL;

	cmdflush();				/* flush typeahead */
	mprintf(CM+197,tlines);			/* "max lines" */
	mprintf(CM+198);				/* "blank line to end" */
	if (uval(CLR_HLP,CLR_HLPS) > EXPERT) mprintf(CM+199); /* "word wrap" */

	ln= 0;					/* highest line number entered */
	ln= edit(ln,tlines - 1,width,tb);	/* enter text, */
	save= msgedit(&t,*msg_highest()+1,comment,replynum,tb,tlines,ln,width); /* msg edit commands, */

	saved= 0;				/* no msg written yet */
	if (save) {				/* ie. not A)bort */
		if (shared()) {
			mprintf(CM+8);
			msgcount();
		}

/* If this is the FidoNet area, write this message out to all the nodes
in NODES.BBS. This generates an IFNA Kludge line in the hidden line at
the top of the message, even if it is redundant. */

		if (msgarea.number == fido.netmarea) {
			maketid(ns,"NODES.BBS");	/* taskID */
			i= open(ns,0);
			*ns= NUL;
			j= 0;				/* total cost */
			while (rline(i,ns,sizeof(ns))) {
				cpy_node(&msg_dest,&id);/* default zone:net */
				msg_dest.point= 0;	/* default no point */
				set_nn(ns,&msg_dest);	/* parse node/net */

				find_ndat(&msg_dest);	/* find it, */
				t.cost= ndat.cost;	/* correct msg cost */

				t.dest_node= msg_dest.number; /* set destination */
				t.dest_net= msg_dest.net; /* FidoNet address */

				if (! savemsg(&t,tb,save)) break; /* abort if error */
				saved= 1;		/* a msg is written */
				j += ndat.cost;		/* tally cost */
				add_reply(replynum);
			}
			close(i);
			caller.debit += j;

/* Not the FidoNet area; just write the message out. Maintain the reply
links and all that crap. */

		} else {
			if (savemsg(&t,tb,save)) {
				add_reply(replynum);
				saved= 1;		/* message saved */
			}
		}
	}
	return(saved);
}
/* Edit a message. */

static msgedit(t,msgnbr,comment,replynum,tb,tlines,ln,width)
struct _msg *t;		/* msg header to edit, */
int msgnbr;		/* message number */
int comment;		/* 1 == comment to operator */
int replynum;		/* 0 else msg this is a reply to */
char *tb;		/* message text, */
int tlines;		/* maximum number of lines, */
int ln;			/* lines so far */
int width;		/* screen width */
{
char *cp;
int i,r;
char s1[SS],s2[SS];	/* line edit buffers */

	while (1) {
		if (! isargs()) mcrlf();
		switch (prompt(fido.cmd.edit,CM+265,"edit.hlp",0)) {

			case 4: if (nverf(CM+271)) {	/* abort */
					mprintf(CM+200); /* "aborted" */
					return(0);
				}
				break;

			case 10: edithdr(t,comment,replynum,0); break;

			case 3:				/* continue */
addmore:;			if (ln < tlines) ln= edit(ln,tlines - 1,width,tb);
				else mprintf(CM+201);	/* "msg full" */
				break;

			case 5: listhdr(t,msgnbr);	/* list */
				for (i= 0; i < ln; i++) {
					msgline(i,tb);
					if (abort) break;
				}
				mcrlf();
				break;

			case 8:				/* insert */
				if (ln == 0) goto addmore;
				if (ln < tlines) {
					getlno(CM+165,ln,&i,&r); /* "insert before" */
					if (i < 0) break;
					ln= edit(i,tlines - 1,width,tb);

				} else mprintf(CM+201);	/* "msg is full" */
				break;

			case 7:				/* delete */
				if (ln > 0) {
					getlno(CM+166,ln,&i,&r);	/* "delete" */
					if (i < 0) break;
					if (kludge_line(i,tb)) break;
					while (r--) ln= del_line(i,ln,tb);

				} else mprintf(CM+202);	/* "No lines to delete!" */
				break;

			case 6:				/* edit */
				if (ln == 0) goto addmore;

				getlno(CM+171,ln,&i,&r);	/* "edit" */
				if (i < 0) break;
				if (kludge_line(i,tb)) break;
				msgline(i,tb);
				mprintf(CM+203); getstring(s1); mcrlf(); s1[SS - 1]= NUL;
				mprintf(CM+204); getstring(s2); mcrlf(); s2[SS - 1]= NUL;
				if (substr(&tb[i * SS],s1,s2,SS) == 0) 
					mprintf(CM+205,s1);	/* "cant find" */
				else msgline(i,tb);
				break;

			case 2:					/* save */
				mprintf(CM+206);		/* "saving" */
				if (isargs()) 			/* if text follows "S" */
					i= atoi(getarg());	/* get format type */
				if (i == 0) i= 1;		/* keep it non-zero */
				return(i);
				break;

			case 9:					/* read */
				mprintf(CM+207);		/* "read from buffer" */
				ln= textread(tb,ln,tlines,width);
				break;
		}
	} 
}

/* Read lines from a text file and insert them into the message body at
the specified line number; return the highest line number used. */

static textread(text,ln,maxlines,width)
char *text;	/* pointer to the text buffer */
int ln;		/* first empty line */
int maxlines;	/* max. line number */
int width;	/* max. we let the lines get */
{
char lastc,c,buff[256];
int i,f;
FLAG wrap,quote;
FLAG suppressed; /* 1 == we ate a CR */
int line,col;
char word[SS];	/* word built from input */
int wi;		/* index into word */
int count;

	if (uval(CLR_PRV,CLR_PRVS) >= EXTRA) {
		getfield(buff,string(CM+14),0,3,SS,1);	/* "file to read from CR=buffer" */
		if (! *buff) goto from_buffer;		/* yuck, a GOTO */
		f= open(buff,0);			/* open the file */
		if (f == -1) {
			stoupper(buff);
			mprintf(CM+208,buff);		/* "doesnt exist!" */
			return(ln);
		}

	} else {
from_buffer:	maketid(buff,"MSG.BUF");		/* default quote file */
		f= open(buff,0);
		if (f == -1) {
			mprintf(CM+209);			/* "buffer empty" */
			return(ln);
		}
	}
	quote= yverf(CM+272);			/* "prefix with >?" */
	wrap= nverf(CM+273);			/* "force word wrap?" */

	width -= sizeof("999: ");		/* compensate for prompt length, */
	if (quote && (width > 32)) width -= 8;	/* narrow margins */

	*word= NUL; wi= suppressed= col= c= 0;
	while (1) {
		word[wi= 0]= NUL;		/* word is empty */
		while (wi < width) {		/* word at a time */
			lastc= c;		/* remember last character */
			while (1) {
				count= 0;		/* (for abort) */
				if (abort) break;	/* allow manual abort */
				count= read(f,&c,1); /* read a character, */
				if (! count) break; /* check EOF */

				switch (c) {
					case CR: if (wrap && !suppressed) {
							suppressed= 1;
							c= ' ';
						}
						break;

					case SUB: c= NUL; count= 0; break;
					case CR + 128: if (lastc == ' ') c= NUL;
						else c= ' '; break;
					default: if (c < ' ') c= NUL; break;
				}
				if (c) break;	/* got a real one or CR */
			}
			if (count == 0) break;	/* end of file, word too */
			if (c == CR) break;	/* end of (the) wor(l)d */
			word[wi++]= c;		/* build the word */
			word[wi]= NUL;		/* for output */
 			if (c == ' ') break;
			suppressed= 0;		/* definitely not a CR */
		}

/* Now we have a word, plus the reason for word termination in 'c'. */

		if (suppressed && (c == CR)) {		/* two CRs in a row */
			col= 0;				/* go to next line */
			for (i= 2; i--;) {
				strcat(&text[ln * SS],"\r\n"); 
				msgline(ln,text);	/* display the line, */
				if (++ln >= maxlines) break;
			}
			if (ln >= maxlines) {
				mprintf(CM+201);	/* "msg full" */
				break;
			}
		}
		if (col + wi >= width) {	/* ... if it wont fit this line */
			msgline(ln,text);	/* display the line, */
			if (quote) strcat(&text[ln * SS],"\r\n"); 
			col= 0;			/* go to next line */
			if (++ln >= maxlines) {
				mprintf(CM+201);/* "msg full" */
				break;
			}
		}
		if ((col == 0) && quote) {	/* add the quote indicator */
			strcat(&text[ln * SS],"> "); 
			col= wordlen(&text[ln * SS],0);	/* recalc column */
		}
		strcat(&text[ln * SS],word); 	/* add the text, */
		col= wordlen(&text[ln * SS],0);	/* recalc column */
		wi= 0;				/* empty what we processed */

		if (c == CR) {			/* if we had a CR */
			strcat(&text[ln * SS],"\r\n"); 
			msgline(ln,text);	/* display the line, */
			col= 0;			/* go to next line */
			if (++ln >= maxlines) {
				mprintf(CM+201);/* "msg full" */
				break;
			}
		}
		if (! count) break;		/* we had hit EOF */
	}
	if (col != 0) {
		strcat(&text[ln * SS],"\r\n"); 	/* add CR/LF */
		msgline(ln,text);		/* display the line, */
		++ln;				/* next free line */
	}
	msgline(ln,text);			/* display the last line, */
	close(f);
	return(ln);
}

/* If this line is an IFNA Kludge line, and privilege level is
below 4, print a nasty message and return true. */

static kludge_line(n,tb)
int n;
char *tb;
{
	if ((tb[n] == SOH) && (uval(CLR_PRV,CLR_PRVS) < EXTRA)) {
		mprintf(CM+210);			/* "you cant edit that line" */
		return(1);
	}
	return(0);
}

/* Display a message line. Doesnt display if a Kludge line and less than 
EXTRA priv. */

static msgline(n,tb)
int n;
char *tb;
{
char c,*s;

	s= &tb[n * SS];
	if ((*s == SOH) && (uval(CLR_PRV,CLR_PRVS) < EXTRA)) return;

	mprintf(0,"%2u: ",n + 1);
	column= 0;				/* reset so tabs line up */
	while (*s && !abort) {
		c= *s++;
		if (c == CR) continue;		/* ignore LFs, CRs, etc */
		if (c == LF) continue;
		if (c == CR + 128) continue;
		mconout(c);
	}
	mcrlf();
}

/* Generate the header of a message. Returns 0 if any fields were left
blank; fields can be left blank only if the 'first' flag is set; this allows
aborting E)nter or R)eply the first time through by entering blank fields. */

static edithdr(t,comment,replynum,first)
struct _msg *t;		/* msg header we edit */
int comment;		/* 1 == is a comment to sysop (sic) */
int replynum;		/* 0 else msg this is a reply to */
int first;		/* 1 == first time, allow blank To: Subj: to abort */
{
char buff[SS],prompt[SS];
char *cp;
FLAG netstuff;		/* 1 == ask for net type stuff */
int i;
struct _node d;

/* For convenience: flag OK to ask for nodes to send to and file attach;
this is a FidoNet area, not a G)oodbye comment and the message being
edited is not marked (SENT). */

	netstuff= (!comment) && ((t-> attr & MSGSENT) == 0) && (msgarea.number == fido.netmarea);
	if (msgarea.flags & AREA_ANON) while (1) {	/* ANON allows changing */
		sprintf(prompt,CM+211,t-> from);	/* "From:" */

/* ARGH: CM211 has a CR/LF at the end. It should not. There is no way to 
edit all ten zillion language files with this problem. So I'll simply code
around it. AARGH. */

		for (cp= prompt; *cp; cp++)		/* disgusting */
			if ((*cp == CR) || (*cp == LF))	/* presumably its at */
				*cp= NUL;		/* the end of the string! */

		strcpy(buff,t-> from);			/* default From: */
		getfield(buff,prompt,0,99,sizeof(caller.name),0);
		if (*buff) strcpy(t-> from,buff);	/* only if entered text, */
		if (*t-> from) break;			/* OK, not blank, */
		else if (first) return(0);		/* no, but allow blank & return */
	}
	mprintf(CM+184,t-> from); mcrlf();		/* say who from */

	if (msgarea.number == fido.netmarea) {
		if ((uval(CLR_PRV,CLR_PRVS) >= EXTRA) && !same_point(&id,&altid)) {
			if (nverf(CM+274,str_node(&altid))) /* "use alias?" */
				cpy_node(&msg_orig,&altid);
		}
	}

	while (1) {				/* get/verify To: name, */
		if (replynum) sprintf(prompt,CM+212);
		else sprintf(prompt,CM+213);	/* "To: " */
		if (*t-> to) {			/* add default */
			strcat(prompt,"[");
			strcat(prompt,t-> to);
			strcat(prompt,"] ");
		}
		getfield(buff,prompt,0,99,sizeof(caller.name),0);
		if (*buff) strcpy(t-> to,buff);	/* only if entered text, */
		if (*t-> to) break;		/* OK, not blank, */
		else if (first) return(0);	/* no, but allow blank & return */
		else mprintf(CM+214);		/* "must be To: someone" */
	}

/* If the FidoNet area, ask for the node(s) to send to, and set the first 
entered node, so that when listed it looks OK. */

	if (netstuff) {
		while (1) {
			if (gtnodes(ns)) break;	/* got a node, */
			if (first) return(0);	/* OK to abort first time, */
			mprintf(CM+215);		/* "must have a node addr" */
		}
		maketid(ns,"nodes.bbs");
		i= open(ns,0);
		*ns= NUL;
		if (i != -1) {
			rline(i,ns,sizeof(ns));	/* pull out first node */
			close(i);		/* in the list for L)ist */
		}
		cpy_node(&msg_dest,&id);	/* default to "us" */
		msg_dest.point= 0;		/* no point */
		set_nn(ns,&msg_dest);		/* then fill it in */
		t-> dest_node= msg_dest.number;
		t-> dest_net= msg_dest.net;	/* store first one in the msg */

		t-> attr &= ~MSGKILL;		/* clear KILL bit, */
		if (yverf(CM+275))		/* "kill after sending?" */
			t-> attr |= MSGKILL;

		if (uval(CLR_PRV,CLR_PRVS) >= EXTRA) /* do file attach/request */
			do_file(t);
	}

/* Ask for Subj: if a normal message or file attach not selected. */

	if (! (t-> attr & (MSGFILE | MSGFREQ))) {
		while (1) {
			sprintf(prompt,CM+216);			/* "Subj" */
			if (*t-> subj) {
				strcat(prompt,"[");		/* add default */
				strcat(prompt,t-> subj);
				strcat(prompt,"] ");
			}
			getfield(buff,prompt,0,99,sizeof(t-> subj),0);
			if (*buff) strcpy(t-> subj,buff);
			if (*t-> subj) break;
			else if (first) return(0);
			else mprintf(CM+217);
		}
	}

/* If the area is marked "O=NoPrivate" or "O=Private", make the message so; for
"O=Anon" or low-priv caller, leave it public. G)oodbye messages are Private, 
unless the area is marked "O=Public", then we ask. For all others, ask. */

	t-> attr &= ~MSGPRIVATE;			/* assume public, */

	if (comment) {					/* if a G)oodbye message */
		if (msgarea.flags & AREA_NPVT) {	/*  and "O=Public" area */
			if (nverf(CM+276)) 		/*  default No */
				t-> attr |= MSGPRIVATE;	/*  ask "private?" */

		} else t-> attr |= MSGPRIVATE;		/* else is private */

	} else if ((msgarea.flags & AREA_NPVT)		/* if "O=Public" */
	    || (uval(CLR_PRV,CLR_PRVS) < NORMAL)) {	/* or low privilege */
		/* DO NOTHING */			/* remains public */

	} else {					/* else ask Yes/No */
		i= (msgarea.flags & AREA_PVT) ?		/* "O=Private" selects */
		yverf(CM+276) : nverf(CM+276);		/* default Yes or No */
		if (i) t-> attr |= MSGPRIVATE;
	}

	if ((t-> attr & MSGPRIVATE) && (uval(CLR_HLP,CLR_HLPS) > EXPERT)) 
		mprintf(CM+218);			/* "Sysop can read ..." */

	return(1);
}

/* Handle the file request/attach biz.  For file attach, it merely prompts
for a file to attach, defaulting to the current file(s). For request, it is
far more complex; request is merely a file-attach of the algorithmically
named file that itself contains filenames; replying means defaulting to the
contents of that file instead. */

static do_file(t)
struct _msg *t;
{
char buff[SS],prstr[SS];

	while (1) {
		t-> attr &= ~(MSGFILE & MSGFREQ);	/* clear FILE and REQUEST bits, */
		switch (prompt(fido.cmd.fileatt,CM+24,"msgfile.hlp",4)) { /* 4 == S)ubj */
			case 2: 			/* file attach (easy) */
				sprintf(prstr,CM+220);	/* "files to attach" */
				t-> attr |= MSGFILE;	/* set file attach */
				goto doit;

			case 3:
				sprintf(prstr,CM+219);	/* "files to req" */
				t-> attr |= MSGFREQ;	/* set file request */

doit:				if (*t-> subj) {
					strcat(prstr,"[");	/* add default */
					strcat(prstr,t-> subj);
					strcat(prstr,"] ");
				}
				getfield(buff,prstr,0,99,sizeof(t-> subj),0);
				if (! *buff) break;
				if (t-> attr & MSGFILE) {	/* if file attach */
					if (!chk_files(buff)) 	/* check missing files */
						break;		/* don't accept */
				}
				strcpy(t-> subj,buff);		/* add file(s) */
				return;

/* This case is for "Subj:"; it causes the caller to fall through and ask
for the subject. */
			case 4: return;
		}
	}
}

/* Check each filespec to see if it exists; list the ones that don't. If
one or more do not exist, ask if we are to continue. return yes/no. */

static chk_files(cp)
char *cp;
{
struct _xfbuf xfbuf;
char missing,name[SS];

	for (missing= 0; *cp; cp= next_arg(cp)) {
		cpyarg(name,cp);
		xfbuf.s_attrib= 0;
		if (!_find(name,0,&xfbuf)) {	/* if filespec doesn't exist */
			stoupper(name);		/* pretty */
			mprintf(CM+208,name);	/* say "<name> doesn't exist" */
			++missing;		/* flag it */
		}
	}
	if (missing) return(yverf(CM+269));	/* "Do you want to continue (Y/n)" */
	else return(1);				/* else all files exist */
}

/* Initialize the message and header. */

static sethdr(t,comment,replynum)
struct _msg *t;
int comment;		/* 1 == comment to operator */
int replynum;		/* 0 else a reply to this message number */
{
struct _clr clr;
int i,f;
char buff[SS],name[SS],*cp;
struct _node dest;

	*ns= NUL;			/* no node set yet */

	cp= (char *) t;			/* clear out the entire structure */
	for (i= 0; i < sizeof(struct _msg); ++i) *cp++= 0;
	t-> times= 1;			/* mark as read once for TWIX */
	t-> attr |= MSGLOCAL;		/* generated locally */
	gtod(t-> date);			/* creation date */
	strcpy(t-> from,caller.name);	/* who from */

/* If a comment, set it To: the first record in CALLER.SYS. */

	if (comment) {			/* who to, if comment to CALLER.SYS */
		strcpy(t-> to,"Sysop");	/* default ... */
		f= open("caller.sys",0); /* record #0 */
		if (f != -1) {
			if (read(f,&clr,sizeof(clr)) == sizeof(clr))
				strcpy(t-> to,clr.name);
			close(f);
		}
	}

/* If a reply, set the message To: the original messages' From:, unless 
it is the messages originator; then set it to the To: name. */

	if (replynum) {
		t-> reply= replynum;			/* set Reply-To, */

		strproc(buff,msg.from,99);		/* make fixed format */
		strproc(name,caller.name,99);		/* both names */
		if (strcmp(name,buff) != 0) 		/* if unique name, */
			strcpy(t-> to,msg.from);	/* send to From: name */
		else strcpy(t-> to,msg.to);		/* else To: name */

		cpyarg(buff,msg.subj); stolower(buff);	/* if already an re: */
		if (!same(buff,"re:") && !(t-> attr & MSGFILE)) strcpy(t-> subj,"re: ");
		strcpy(buff,msg.subj);			/* now shorten the subj */
		buff[sizeof(msg.subj) - 5]= NUL;	/* text to guarentee a fit */
		strcat(t-> subj,buff);			/* then add it */

		if (is_us(&msg_orig))			/* if From: our node, */
			strcpy(ns,str_node(&msg_dest));	/* send to To: node */
		else strcpy(ns,str_node(&msg_orig));	/* else to From: node */
	}

/* Set the message To: and From: us. (Both the .MSG header and the
msg_dest and msg_orig structures.) If its a reply, set the To: node. */

	cpy_node(&msg_orig,&id);		/* its From: us, */
	cpy_node(&msg_dest,&id);		/* assume To: us, */
	if (replynum) set_nn(ns,&msg_dest);	/* unless a reply */

	t-> orig_node= msg_orig.number; 	/* fill in the msg header */
	t-> orig_net= msg_orig.net;
	t-> dest_node= msg_dest.number; 	/* destination node */
	t-> dest_net= msg_dest.net;
}

/* Get a line number; prompt for one until a valid number or blank line;
return -1 if invalid number. */

static getlno(id,lim,result,range)
unsigned id;	/* prompt stringID */
int lim;	/* upper limit */
int *result;	/* returned line number */
int *range;	/* number of lines */
{
char *cp;
int n;

	while (1) {
		*result= -1;				/* set error return */
		*range= 1;				/* assume one line */
		cp= getarg(CM+221,string(id));		/* get input */
		if (!*cp) return;			/* quit if blank */

		*result= atoi(cp) - 1;			/* convert it, */
		while (isdigit(*cp)) ++cp;		/* look for a range */
		if (*cp == '-') {			/* following line # */
			cp= skip_delim(++cp);
			n= atoi(cp);			/* if a number follows */
			if (n > lim) n= lim;		/* bound to the limit */
			if (! n) n= lim;		/* else it means til end */
			*range= n - *result;		/* make into # lines */
			if (*range <= 0) *range= 1;	/* (note not - 1) */
		}
		if ((*result >= 0) && (*result <= lim) && /* sanity check */
		    (*result + *range >= 0) && (*result + *range <= lim))
			return;

		mprintf(CM+222,lim);			/* yell at them. */
	}
}

/* Write out the message as is, return 0 if an error. (Disk full). */

static savemsg(t,tb,hardcr)
struct _msg *t;
char *tb;
int hardcr;
{
int n;

	if (uval(CLR_PRV,CLR_PRVS) <= TWIT) return(1);

	n= writemsg(*msg_highest() + 1,t,tb,hardcr == 2); /* save the message, */
	if (n) {				/* if written OK, */
		++*msg_highest();		/* next highest message number, */
		++*msg_total();			/* another message */
	}
	return(n);
}

/* Write a message header and the buffer of text to disk; returns 0
if write error. If a FidoNet message, add INTL and TOPT/FMPT lines
as indicated. */

static writemsg(n,t,tb,hardcr)
int n;			/* message number, */
struct _msg *t;		/* msg header, */
char *tb;		/* msg text */
int hardcr;		/* 1 == CRs on each line */
{
int l,q,i,f;
char c,*cp,*s,name[SS],hascr;
char buff[256];
struct _node d,o;

	sprintf(buff,0,"%d.MSG",n);		/* file name, */
	makemname(name,buff);			/* pathname */
	f= creat(name,1);
	if (f == -1) {
		mprintf(CM+151,name);		/* "Cant create a file" */
		lprintf(LM+39,name); 		/* "! cant create filename" */
		return(0);
	}
	*buff= NUL;				/* no extra junk yet */
	if (msgarea.number == fido.netmarea) {

/* Perform the IFNA Kludge. This is: If this message is to another zone, address
it to origzone/destzone. */

		cpy_node(&d,&msg_dest);		/* the INTL line cannot contain */
		cpy_node(&o,&msg_orig);		/* point addresses */
		d.point= o.point= 0;		/* so we force it clear */

		sprintf(buff,0,"\001INTL %s %s\r\n",str_node(&d),str_node(&o));
		if (id.zone &&
		    !same_zone(&msg_dest,&id) && 
		    !same_zone(&msg_dest,&altid)) {
			t-> dest_node= msg_dest.zone;
			t-> dest_net= msg_orig.zone;
		}
		if (msg_orig.point) sprintf(&buff[strlen(buff)],0,"\001FMPT %d\r\n",msg_orig.point);
		if (msg_dest.point) sprintf(&buff[strlen(buff)],0,"\001TOPT %d\r\n",msg_dest.point);
	}
	write(f,t,sizeof(struct _msg));		/* write error not checked here */
	if (*buff) write(f,buff,strlen(buff));	/* write out extra junk */
	l= q= i= 0;
	while (1) {				/* output all the text */
		s= &tb[l * SS];
		if (! *s) break;		/* NUL is end of message */
		++l;				/* index/count lines */

/* This is the quoted-line counter, that was never implemented... */

		cp= skip_delim(s);		/* check for a ">" in */
		for (i= 0; i < 3; i++) {	/* the 1st 3 non-blank chars */
			if (*cp++ == '>') {	/* (either ">" or "TJ>") */
				++q;
				break;
			}
		}

	if (msgarea.flags & AREA_HARDCR) 	/* if hard CRs required */
		hardcr= 1;			/* force it on */

/* If HardCR is true (manually entered "S 2" or O=HardCR in AREAS.INI) then
we must put a hard CR on each line. If HardCR is true, convert softCRs to
hard ones. (Performance is OK because of the passive buffered file 
system.) */

		hascr= 0;			/* (havent found CR/LF yet) */

		while (c= *s++) {		/* until end of line ... */
			if (hardcr && (c == CR+128)) /* if so enabled */
				c= CR;		/* translate softCRs to hard */

			if (! write(f,&c,1)) {	/* write one at a time */
				mprintf(CM+68,name);	/* "disk full!" */
				lprintf(LM+16,name);	/* "disk full filename" */
				close(f);
				delete(name);
				return(0);
			}
			if (c == LF) ++hascr; /* flag found LFs */
		}
		if (hardcr) 			/* if we require a CR/LF */
			if (! hascr) 		/* and there is no one */
				write(f,"\r\n",2); /* add one */
	}
	write(f,"",1);				/* for stupid programs */
	close(f);
	return(1);
}

/* Mark this as a reply message; tie it into the chain. Find the end of 
the chain, add this one there, and make it also refer to the one just
entered. Do nothing if the caller privelege is TWIT, as the message
was not saved. */

static add_reply(replynum)
int replynum;
{
int i,lfound;

	if (uval(CLR_PRV,CLR_PRVS) <= TWIT) return;
	if (replynum == 0) return;		/* no sense in it */

	lfound= 0;				/* No "last found" msg, */
	i= findmsg(replynum,0);
	mprintf(CM+226,replynum);		/* "adding a reply..." */
	while (i) {
		lfound= i;			/* last good msg in chain, */
		if (msg.up == 0) break;		/* quit if at end, */
		mprintf(CM+227,i,msg.up);	/* "already has reply..." */
		if (i == msg.up) {
			mprintf(CM+228,i);	/* "points to itself!" */
			break;
		}
		closemsg();
		i= findmsg(msg.up,0);		/* else get child, */
	}

/* Check for the case where we had at least one msg in the chain (lfound) but
one of the children was missing (i == 0). Go back to the last one we had. */

	if ((i == 0) && (lfound != 0)) {
		closemsg();	/* may not be open, */
		i= findmsg(lfound,0);	/* open in any case */
	}
	if (i) {
		msg.up= *msg_highest();	/* set fwd ptr in replied-to msg */
		wrtmsg();		/* write it out, */

		if (findmsg(*msg_highest(),0)) { /* the one we just entered */
			msg.reply= i;	/* set backward ptr */
			wrtmsg();
		}
	} 
}

/* Get a list of nodes, leave in NODES.BBS. Put the first one in the
passed string, for display purposes. */

static gtnodes(ns)
char *ns;
{
char *cp,arg[SS],ln[SS];
int n,nodes,found,cost,o;
struct _node def,d;

	if (node_files()) {
		mprintf(CM+139);		/* "no nodelist" */
		lprintf(LM+3);
		*ns= NUL;
		return;
	}
	cpy_node(&d,&id);			/* default to our zone:net/node */
	d.point= 0;

	while (1) {
		nodes= 0;			/* none entered */
		if (! *ns) {
			cpy_node(&def,&d);	/* find host node */
			def.number= 0;		/* for current net */
			n= find_ndat(&def);	/* locate the host for our net, */
			if (n < 0) {		/* no host? Hmm ... */
				def.net= 0;	/* none? Hmm ... */
				n= find_ndat(&def);
			}
			if (n >= 0) {
				mprintf(CM+229);	/* "Current Net is " */
				listnode(&ndat);/* display it */
			}
		}
		*arg= NUL;
		sprintf(arg,CM+230);		/* prompt for input */
		if (*ns) {
			strcat(arg,"[");		/* add default */
			strcat(arg,ns);
			strcat(arg,"] ");
		}
		getfield(ln,arg,0,99,sizeof(ln),1);
		if (*ln) strcpy(ns,ln);		/* use new input */
		if (! *ns) break;		/* stop if no input */
		cp= ns;

/* Given the input string, parse it into net/node numbers. If we exhaust the
string, write out the list of nodes and return. If we hit anything else, 
try to execute it as a command. */

		maketid(arg,"nodes.bbs");
		o= creat(arg,2);		/* where we put nodes */
		*arg= NUL;			/* (delete the name) */
		cost= 0;			/* accumulated costs */
		nodes= 0;			/* nodes outputted so far */
		while (*cp) {
			cpyatm(arg,cp);		/* arg == command word/number */
			cp= next_arg(cp);	/* cp == argument */
			if (arg[1] == '?') help(arg,0,"nodes.hlp");
			else switch (tolower(*arg)) {
				case '!': nodes= 0; list_nodes(&d,0); break;

				case '?': nodes= 0; cp= ""; 
					help("",0,"nodes.hlp"); break;

				case 'f': 
					if (! *cp) {			/* if no typeahead, */
						list_nodes(&d,1);	/* list them, */
						getfield(arg,string(CM+26),0,99,sizeof(arg),1);

					} else {
						cpyarg(arg,cp);		/* there is one */
						cp= next_arg(cp);	/* assume its a number */
					}
					if (! isdigit(*arg)) break;
					/* number(s) in arg; fall through */

				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
				case '.':	/* point specifier */
					cknode(&nodes,&d,&cost,arg,o); 
					if (!nodes) cp= ""; break;

				case '=': from_list(&nodes,&d,&cost,arg,o); 
					if (!nodes) cp= ""; break;

				case 'z':
					if (! isdigit(*cp)) {
						mprintf(CM+8);
						if (! list_nodes(&d,3)) {
							mprintf(CM+231); /* "no zones" */
							nodes= 0;
							cp= "";
							break;
						}
						getfield(arg,string(CM+27),0,1,sizeof(arg),1);

 					} else {
						cpyarg(arg,cp);
						cp= next_arg(cp);
					}
					if (! isdigit(*arg)) break;

					d.zone= atoi(arg);
					d.net= 0; d.number= 0;
					if (same_zone(&d,&id) || same_zone(&d,&altid)) break;
					if (find_ndat(&d) == -1) {
						mprintf(CM+232,d.zone); /* "no such zone" */
						nodes= 0;
						cp= "";
					}
					break;

				case 'n':
					if (! *cp) {
						if (! list_nodes(&d,2)) {
							mprintf(CM+233); /* "no nets" */
							cp= "";
							nodes= 0;
							break;
						}
						getfield(arg,string(CM+28),0,99,sizeof(arg),1);

					} else {
						cpyarg(arg,cp);
						cp= next_arg(cp);
					}
					if (! isdigit(*arg)) break;
					d.net= atoi(arg);
					d.number= 0;
					if (same_net(&d,&id) || same_net(&d,&altid)) break;
					if (find_ndat(&d) == -1) {
						mprintf(CM+234,d.net,d.zone); /* "no such net" */
						cp= "";
						nodes= 0;
					}
					break;

				case NUL: break;

				default: nodes= 0; mprintf(CM+36,*arg);
					cp= ""; break;
			}
		}
		close(o);
		if (nodes > 0) break;			/* list is OK */
		cmdflush();				/* its all garbage */
		*ns= NUL;				/* no default next time */
	}
	return(nodes);
}

/* See if this node number exists and is legal; return 0 if not. If it
is, return with the node structure set. */

static cknode(nodes,n,cost,arg,o)
int *nodes;
struct _node *n;
int *cost;
char *arg;
int o;
{
int found;
struct _node d;
char *cp;

	cpy_node(&d,n);				/* duplicate for working */
	set_nn(arg,&d);				/* parse net/node or node */
	found= (find_ndat(&d) != -1);		/* look for it, */

	if (! found) {
		mprintf(CM+235,str_node(&d));	/* "no such node" */
		*nodes= 0;
		return(0);
	}

	*cost += ndat.cost;			/* check the cost, */
	if ((*cost > (caller.credit - caller.debit)) && (uval(CLR_PRV,CLR_PRVS) < SYSOP)) {
		mprintf(CM+236);			/* "not enoguh credit" */
		*nodes= 0;
		return(0);
	}

/* String-ize the orig. node address, because it may contain a point
field, which is not stored in the ndat record. */

	cp= str_node(&d);			/* string-ize the node number */

	write(o,cp,strlen(cp));			/* write the node address, */
	write(o,"\r\n",2);			/* plus a CR/LF */

	++(*nodes);				/* count another */
	return(*nodes);
}

/* Convert a file into the list of nodes. */

static from_list(nodes,d,cost,cp,o)
int *nodes;
struct _node *d;
int *cost;
char *cp;
int o;
{
char arg[SS],ln[SS];
int i;

	cp= &cp[1];		/* skip the '=' */
	cp= skip_delim(cp);	/* skip any leading spaces */
	cp= strip_path(ln,cp);	/* no pathnames */
	makesname(ln,cp);	/* prepend system path */
	for (cp= ln; *cp; cp++)	/* find a dot, if any */
		if (*cp == '.') break;
	strcpy(cp,".ndl");	/* add extention */
	stoupper(ln);		/* make it pretty */
	i= open(ln,0);		/* open for reading */
	if (i == -1) {
		mprintf(CM+237,ln);
		*nodes= 0;
		return;
	}
	while (rline(i,ln,sizeof(ln))) {
		cp= ln;
		while (*cp) {
			cpyatm(arg,cp);
			cp= next_arg(cp);
			if (! cknode(nodes,d,cost,arg,o)) {
				close(i);
				return;
			}
		}
	}
	close(i);
}
