/*
 * MultiMail offline mail reader
 * message display and editing

 Copyright (c) 1996 Kolossvary Tamas <thomas@tvnet.hu>
 Copyright (c) 1997 John Zero <john@graphisoft.hu>
 Copyright (c) 1999 William McBrine <wmcbrine@clark.net>

 Distributed under the GNU General Public License.
 For details, see the file COPYING in the parent directory. */

#include "interfac.h"
#include "mysystem.h"

extern "C" {
#include <sys/stat.h>
}

Line::Line(int size)
{
	next = 0;
	text = (size ? new char[size] : 0);
}

Line::~Line()
{
	delete[] text;
}

LetterWindow::LetterWindow()
{
	linelist = 0;
	NM.isSet = hidden = false;
	NumOfLines = 0;
	To[0] = tagline1[0] = '\0';
}

net_address &LetterWindow::PickNetAddr()
{
	Line *line;
	static net_address result;
	int i;

	for (i = 0; i < NumOfLines; i++)
		if (!strncmp(" * Origin:", linelist[i]->text, 10))
			break;

	if (i != NumOfLines) {
		line = linelist[i];
		i = line->length;
		while (((line->text[i - 1]) != '(') && i > 0)
			i--;
		//we have the opening bracket

		while ((i < (int) line->length) &&
			((line->text[i] < '0') || (line->text[i] > '9')))
				i++;
		//we have the begining of the address

		result = &line->text[i];
	} else
		result = mm.letterList->getNetAddr();

	return result;
}

void LetterWindow::ReDraw()
{
	headbar->wtouch();
	header->wtouch();
	text->wtouch();
	statbar->cursor_on();	// to get it out of the way
	statbar->wtouch();
	statbar->cursor_off();
}

void LetterWindow::DestroyChain()
{
	if (linelist) {
		while (NumOfLines)
			delete linelist[--NumOfLines];
		delete[] linelist;
		linelist = 0;
	}
	letter_in_chain = -1;
}

/* Take a message as returned by getBody() -- the whole thing as one C
   string, with paragraphs separated by '\n' -- and turn it into a linked
   list with an array index, wrapping long paragraphs if needed.
*/
void LetterWindow::MakeChain(int columns)
{
	Line head(0), *curr;
	unsigned char each;
	char *message, *j, *k, *begin;
	int c;
	bool end = false;

	DestroyChain();
	message = (char *) mm.letterList->getBody();

	letterconv_in(message);

	letter_in_chain = mm.letterList->getCurrent();
	curr = &head;

	j = message;
	while (!end) {
	    if (!hidden)	// skip ^A lines
		while (*j == 1) {
			do
				j++;
			while (*j && (*j != '\n'));
			while (*j == '\n')
				j++;
		}

	    if (*j) {
		curr->next = new Line(columns + 1);
		curr = curr->next;

		NumOfLines++;

		begin = 0;
		k = curr->text;
		c = 0;

		while (!end && (c < columns)) {
			each = *j;
			end = !each;	// a 0-terminated string

			if (each == '\n') {
				j++;
				break;
			}

			// begin == start of last word (for wrapping):

			if (each == ' ')
				begin = 0;
			else
				if (!begin)
					begin = j;

			if (rot13) {
			    if (each >= 'A' && each <= 'Z')
				each = (each - 'A' + 13) % 26 + 'A';
			    else if (each >= 'a' && each <= 'z')
				each = (each - 'a' + 13) % 26 + 'a';
			}

			if (each) {
				*k++ = each;
				j++;
				c++;
			}
		}

		// Start a new line on a word boundary (if needed):

		if ((c == columns) && begin && ((j - begin) < columns)) {
			c -= j - begin;
			j = begin;
		}

		// Strip any trailing spaces:

		while (curr->text[c - 1] == ' ')
			c--;

		curr->text[c] = '\0';
		curr->length = c;
	    } else
		end = true;
	}

	linelist = new Line *[NumOfLines];
	curr = head.next;
	c = 0;
	while (curr) {
		linelist[c++] = curr;
		curr = curr->next;
	}
}

void LetterWindow::StatToggle(int mask)
{
	int stat = mm.letterList->getStatus();
	stat ^= mask;
	mm.letterList->setStatus(stat);
	interface->setAnyRead();
	DrawHeader();
}

void LetterWindow::Draw(bool redo)
{
	if (redo) {
		rot13 = false;
		position = 0;
		tagline1[0] = '\0';
		if (!mm.letterList->getRead()) {
			mm.letterList->setRead();	// nem ide kene? de.
			interface->setAnyRead();
		}
	}

	if (letter_in_chain != mm.letterList->getCurrent())
		MakeChain(COLS);

	DrawHeader();
	DrawBody();
	DrawStat();
}

char *LetterWindow::netAdd(char *tmp)
{
	net_address &na = mm.letterList->getNetAddr();
	if (na.isSet) {
		const char *p = na;
		if (*p)
			tmp += sprintf(tmp, (na.isInternet ?
				" <%s>" : " @ %s"), p);
	}
	return tmp;
}

#define flagattr(x) header->attrib((x) ? C_LHFLAGSHI : C_LHFLAGS)

void LetterWindow::DrawHeader()
{
	char tmp[256], *p;
	int stat = mm.letterList->getStatus();
	bool hasTo = mm.areaList->hasTo();

	header->Clear(C_LHEADTEXT);

	header->put(0, 2, "Msg#:");
	header->put(1, 2, "From:");
	if (hasTo)
		header->put(2, 2, "  To:");
	else
		header->put(2, 2, "Addr:");
	header->put(3, 2, "Subj:");

	header->attrib(C_LHMSGNUM);
	sprintf(tmp, "%d (%d of %d)   ", mm.letterList->getMsgNum(),
		mm.letterList->getCurrent() + 1, mm.areaList->getNoOfLetters());
	header->put(0, 8, tmp);

	header->attrib(C_LHFROM);
	p = tmp + sprintf(tmp, "%.255s", mm.letterList->getFrom());
	if (mm.areaList->isEmail())
		netAdd(p);
	letterconv_in(tmp);
	header->put(1, 8, tmp);

	header->attrib(C_LHTO);
	p = (char *) mm.letterList->getTo();
	if (hasTo && *p) {
		p = tmp + sprintf(tmp, "%.255s", p);
		if (mm.areaList->isReplyArea())
			if (p != tmp)
				netAdd(p);
	} else
		sprintf(tmp, "%.255s",
			(const char *) mm.letterList->getNetAddr());
		
	letterconv_in(tmp);
	header->put(2, 8, tmp);

	header->attrib(C_LHSUBJ);
	sprintf(tmp, "%-71.71s", mm.letterList->getSubject());
	letterconv_in(tmp);
	header->put(3, 8, tmp);

	for (int i = strlen(tmp) + 8; i < COLS; i++)
		header->put(3, i, ' ');

	header->attrib(C_LHEADTEXT);
	header->put(0, COLS - 33, "Date:");
	header->put(1, COLS - 33, "Line:");
	header->put(2, COLS - 33, "Stat: ");

	//header->put(4, 0, ACS_LLCORNER);
	//header->horizline(4, COLS-2);
	//header->put(4, COLS-1, ACS_LRCORNER);

	flagattr(mm.letterList->getPrivate());
	header->put(2, COLS - 27, "Pvt ");
	flagattr(stat & MS_READ);
	header->put(2, COLS - 23, "Read ");
	flagattr(stat & MS_REPLIED);
	header->put(2, COLS - 18, "Replied ");
	flagattr(stat & MS_MARKED);
	header->put(2, COLS - 10, "Marked  ");

	header->attrib(C_LHDATE);
	strcpy(tmp, mm.letterList->getDate());
	letterconv_in(tmp);
	header->put(0, COLS - 27, tmp);

	lineCount();
}

void LetterWindow::lineCount()
{
	char tmp[80];

	header->attrib(C_LHMSGNUM);
	sprintf(tmp, "%6d/%-21d", position + 1, NumOfLines);
	header->put(1, COLS-28, tmp);
	header->delay_update();
}

void LetterWindow::oneLine(int i)
{
	char test, *currtext;
	int j, length, z = position + i;

	if (z < NumOfLines) {
		currtext = linelist[z]->text;
		length = (int) linelist[z]->length;

		text->attrib(C_LTEXT);

		for (j = 0; j < 5; j++)
			if (currtext[j] == '>') {	//quoting
				text->attrib(C_LQTEXT);
				break;
			}

		if (*currtext == 1) {	//hidden
			currtext++;
			length--;
			text->attrib(C_LHIDDEN);
		}

		test = *currtext;
		if ((currtext[1] == test) && (currtext[2] == test) &&
		   ((currtext[3] == ' ') || length == 3))
			switch (test) {
			case '.':
				text->attrib(C_LTAGLINE); //tagline
				if ((length > 3) && !tagline1[0]) {
					strncpy(tagline1, &currtext[4],
						TAGLINE_LENGTH);
					tagline1[TAGLINE_LENGTH] = '\0';
				}
				break;
			case '-':
			case '~':
				text->attrib(C_LTEAR);	//mailer
			}
		else if (!strncmp(currtext, " * Origin:", 10))
			text->attrib(C_LORIGIN);	//origin line

		length = text->put(i, 0, currtext);
	} else
		length = 0;

	for (j = length; j < COLS; j++)
		text->put(i, j, ' ');
}

void LetterWindow::DrawBody()
{
	lineCount();

	for (int i = 0; i < y; i++)
		oneLine(i);
	text->delay_update();
}

void LetterWindow::DrawStat()
{
	static const char *helpmsg = "F1 or ? - Help ";
	char format[40], *tmp = new char[COLS + 1];

	int maxw = COLS - 16;

	bool collflag = false;
	if (mm.areaList->isCollection()) {
		if (mm.areaList->isReplyArea()) {
			maxw -= 10;
			sprintf(format, " REPLY in: %%-%d.%ds%%s",
				maxw, maxw);
		} else {
			maxw -= 13;
			sprintf(format, " PERSONAL in: %%-%d.%ds%%s",
				maxw, maxw);
		}
		mm.areaList->gotoArea(mm.letterList->getAreaID());
		collflag = true;
	} else
		sprintf(format, " %%-%d.%ds%%s", maxw, maxw);

	sprintf(tmp, format, mm.areaList->getDescription(), helpmsg);
	areaconv_in(tmp);

	if (collflag)
		interface->areas.Select();

	statbar->cursor_on();
	statbar->put(0, 0, tmp);
	statbar->delay_update();
	statbar->cursor_off();

	delete[] tmp;
}

void LetterWindow::MakeActive(bool redo)
{
	DestroyChain();

	y = LINES - 7;

	headbar = new Win(1, COLS, 0, C_LBOTTSTAT);
	header = new Win(5, COLS, 1, C_LHEADTEXT);
	text = new Win(y, COLS, 6, C_LTEXT);
	statbar = new Win(1, COLS, LINES - 1, C_LBOTTSTAT);

	char *tmp = new char[COLS + 1];
	sprintf(tmp, " " MM_TOPHEADER, MM_NAME, MM_MAJOR, MM_MINOR);
	int i;
	for (i = strlen(tmp); i < COLS; i++)
		tmp[i] = ' ';
	tmp[i] = '\0';

	headbar->put(0, 0, tmp);
	headbar->delay_update();

	delete[] tmp;

 	Draw(redo);
}

bool LetterWindow::Next()
{
	if (mm.letterList->getActive() < (mm.letterList->noOfActive() - 1)) {
		interface->letters.Move(DOWN);
		mm.letterList->gotoActive(mm.letterList->getActive() + 1);
		Draw(true);
		return true;
	} else {
		interface->back();
		doupdate();
		interface->back();
		doupdate();
		interface->areas.KeyHandle(KEY_RIGHT);
	}
	return false;
}

bool LetterWindow::Previous()
{
	if (mm.letterList->getActive() > 0) {
		interface->letters.Move(UP);
		mm.letterList->gotoActive(mm.letterList->getActive() - 1);
		Draw(true);
		return true;
	} else {
		interface->back();
		doupdate();
		interface->back();
		doupdate();
		interface->areas.Move(UP);
	}
	return false;
}

void LetterWindow::Move(int dir)
{
	switch (dir) {
	case KEY_UP:
		if (position > 0) {
			position--;
			text->wscroll(-1);
			oneLine(0);
			text->delay_update();
			lineCount();
		}
		break;
	case KEY_DOWN:
		if (position < NumOfLines - y) {
			position++;
			lineCount();
			text->wscroll(1);
			oneLine(y - 1);
			text->delay_update();
		}
		break;
	case KEY_HOME:
		position = 0;
		DrawBody();
		break;
	case KEY_END:
		if (NumOfLines > y) {
			position = NumOfLines - y;
			DrawBody();
		}
		break;
	case 'B':
	case KEY_PPAGE:
		position -= ((y < position) ? y : position);
		DrawBody();
		break;
	case 'F':
	case KEY_NPAGE:
		if (position < NumOfLines - y) {
			position += y;
			if (position > NumOfLines - y)
				position = NumOfLines - y;
			DrawBody();
		}
		break;
	}
}

void LetterWindow::NextDown()
{
	position += y;
	if (position < NumOfLines)
		DrawBody();
	else
		Next();
}

void LetterWindow::Delete()
{
	DestroyChain();
	delete headbar;
	delete header;
	delete text;
	delete statbar;
}

bool LetterWindow::Save(int stype)
{
	FILE *fd;

	static const char *ntemplate[] = {
		"%.8s.%.03d", "%.8s.all", "%.8s.mkd"
	};

	static char keepname[3][128];
	char filename[128], oldfname[128];

	stype--;

	if (keepname[stype][0])
		strcpy(filename, keepname[stype]);
	else {
		switch (stype) {
		case 2:					// Marked
		case 1:					// All
			sprintf(filename, ntemplate[stype],
				mm.areaList->getName());
			break;
		case 0:					// This one
			sprintf(filename, ntemplate[0],
				mm.areaList->getName(),
					mm.letterList->getCurrent());
		}

		unspace(filename);
	}

	strcpy(oldfname, filename);

	if (interface->savePrompt(filename)) {
		mychdir(mm.resourceObject->get(SaveDir));
		if ((fd = fopen(filename, "at"))) {
			int num = mm.letterList->noOfActive();

			switch (stype) {
			case 2:
			case 1:
				for (int i = 0; i < num; i++) {
					mm.letterList->gotoActive(i);
					if ((stype == 1) ||
					  (mm.letterList->getStatus() &
					    MS_MARKED))
						write_to_file(fd);
				}
				break;
			case 0:
				write_to_file(fd);
			}
			fclose(fd);
			if (!stype)
				MakeChain(COLS);

			interface->setAnyRead();
		}
		if (strcmp(filename, oldfname))
			strcpy(keepname[stype], filename);

		return true;
	} else
		return false;

}

void LetterWindow::set_Letter_Params(net_address &nm, const char *to)
{
	NM = nm;
	strncpy(To, to ? to : "", 29);
}

void LetterWindow::set_Letter_Params(int area, char param_key)
{
	key = param_key;
	replyto_area = area;
}

void LetterWindow::QuoteText(FILE *reply)
{
	char TMP[81], mg[4];
	int i;
	bool end;

	const char *TMP2 = mm.letterList->getFrom();

	sprintf(TMP, "-=> %.30s wrote to %.30s <=-\n\n",
		TMP2, mm.letterList->getTo());
	letterconv_in(TMP);

	strncpy(mg, TMP2, 2);
	mg[2] = '\0';
	mg[3] = '\0';

	i = 1;
	for (int j = 1; j < 3; j++) {
		end = false;
		while (TMP2[i] && !end) {
			if ((TMP2[i - 1] == ' ') && (TMP2[i] != ' ')) {
				mg[j] = TMP2[i];
				if (j == 1)
					end = true;
			}
			i++;
		}
	}
	letterconv_in(mg);

	MakeChain(75);

	fputs(TMP, reply);
	for (i = 0; i < NumOfLines; i++)
		fprintf(reply, " %s> %s\n", mg, linelist[i]->text);

	MakeChain(COLS);
}

int LetterWindow::HeaderLine(ShadowedWin &win, char *buf, int limit,
				int pos, int color)
{
	areaconv_in(buf);
	int getit = win.getstring(pos, 8, buf, limit, color, color);
	areaconv_out(buf);
	return getit;
}

int LetterWindow::EnterHeader(char *FROM, char *TO, char *SUBJ, bool &privat)
{
	static const char *noyes[] = { "No", "Yes" };

	char NETADD[72];
	int result, current, maxitems = 2;
	bool end = false;
	bool hasNet = mm.areaList->isEmail();
	bool hasTo = mm.areaList->hasTo();

	if (hasNet) {
		strcpy(NETADD, NM);
		maxitems++;
	}
	if (hasTo)
		maxitems++;

	ShadowedWin rep_header(maxitems + 2, COLS - 2, (LINES / 2) - 3,
		C_LETEXT);

	rep_header.put(1, 2, "From:");
	if (hasTo)
		rep_header.put(2, 2, "  To:");
	if (hasNet)
		rep_header.put(3, 2, "Addr:");
	rep_header.put(maxitems, 2, "Subj:");

	rep_header.attrib(C_LEGET2);
	rep_header.put(1, 8, FROM);

	if (hasNet && !NM.isSet) {
		NM.isInternet = mm.areaList->isInternet();
		TO[0] = '\0';
		current = 1;
	} else {
		if (hasTo) {
			rep_header.attrib(C_LEGET1);
			rep_header.put(2, 8, TO);
		}
		if (hasNet) {
			rep_header.attrib(C_LEGET2);
			rep_header.put(3, 8, NETADD);
		}
		current = maxitems - 1;
	}

	rep_header.update();

	do {
		result = HeaderLine(rep_header, (current == (maxitems - 1)) ?
			SUBJ : (((current == 2) && hasNet) ?
			NETADD : ((current && hasTo) ? TO : FROM)),
			((current == (maxitems - 1)) ||
			((current == 2) && NM.isInternet)) ? 69 :
			mm.areaList->maxToLen(), current + 1,
			(current & 1) ? C_LEGET1 : C_LEGET2);

		switch (result) {
		case 0:
			end = true;
			break;
		case 1:
			current++;
			if (current == maxitems)
				end = true;
			break;
		case 2:
			if (current > 0)
				current--;
			break;
		case 3:
			if (current < maxitems - 1)
				current++;
		}
	} while (!end);

	if (result) {
		int pmode = (!hasNet ? mm.areaList->hasPublic() : 0) +
			((mm.areaList->hasPrivate() &&
			!mm.areaList->isUsenet()) ? 2 : 0);

		if (hasNet)
			NM = NETADD;

		switch (pmode) {
		case 1:
			privat = false;
			break;
		case 2:
			privat = true;
			break;
		case 3:
			if (!interface->WarningWindow(
				"Make letter private?", privat ? 0 : noyes))
						privat = !privat;
		}
	}
	return result;
}

void LetterWindow::editletter(const char *reply_filename)
{
	char command[256];

	sprintf(command, "%s %s", mm.resourceObject->get(editor),
		canonize(reply_filename));
	mysystem(command);
}

long LetterWindow::reconvert(const char *reply_filename)
{
	FILE *reply;

	reply = fopen(reply_filename, "rt");
	fseek(reply, 0, SEEK_END);
	long replen = ftell(reply);
	rewind(reply);

	char *body = new char[replen + 1];
	replen = (long) fread(body, 1, replen, reply);
	fclose(reply);

	body[replen] = '\0';
	areaconv_out(body);
	while (body[replen - 1] == '\n')
		replen--;

	reply = fopen(reply_filename, "wt");
	fwrite(body, 1, replen, reply);
	fclose(reply);

	delete[] body;
	return replen;
}

void LetterWindow::setToFrom(char *TO, char *FROM)
{
	char format[7];
	sprintf(format, "%%.%ds", mm.areaList->maxToLen());

	bool usealias = mm.areaList->getUseAlias();
	if (usealias) {
		sprintf(FROM, format, mm.resourceObject->get(AliasName));
		if (!FROM[0])
			usealias = false;
	}
	if (!usealias)
		sprintf(FROM, format, mm.resourceObject->get(LoginName));

	if (key == 'E')
		strcpy(TO, (To[0] ? To : "All"));
	else
		sprintf(TO, format, (key == 'O') ? mm.letterList->getTo() :
			mm.letterList->getFrom());
}

void LetterWindow::EnterLetter()
{
	FILE *reply;
	char reply_filename[256], FROM[36], TO[36], SUBJ[78];
	const char *orig_id;

	struct stat fileStat;
	time_t oldtime;

	long replen;
	int replyto_num;
	bool privat;

	mm.areaList->gotoArea(replyto_area);

	// HEADER

	setToFrom(TO, FROM);

	if (key == 'E')
		SUBJ[0] = '\0';	//we don't have subject yet
	else
		sprintf(SUBJ, "Re: %.65s", mm.letterList->getSubject());

	//if ((key == 'N') || mm.areaList->isEmail())
	//	privat = true;
	//else
		privat = (key == 'E') ? false : mm.letterList->getPrivate();

	if (!EnterHeader(FROM, TO, SUBJ, privat)) {
		NM.isSet = false;
		interface->areas.Select();
		return;
	}

	// Don't use refnum if posting in different area:

	replyto_num = ((key == 'E') || (key == 'N') || (replyto_area !=
		mm.letterList->getAreaID())) ? 0 : mm.letterList->getMsgNum();

	orig_id = replyto_num ? mm.letterList->getMsgID() : 0;

	// BODY

	mytmpnam(reply_filename);
	reply = fopen(reply_filename, "wt");

	// Quote the old text 

	if (key != 'E')
		QuoteText(reply);

	fclose(reply);
	stat(reply_filename, &fileStat);
	oldtime = fileStat.st_mtime;

	// Edit the reply

	editletter(reply_filename);
	interface->changestate(interface->active());

	// Check if modified

	stat(reply_filename, &fileStat);
	if (fileStat.st_mtime == oldtime)
		if (interface->WarningWindow("Cancel this letter?")) {
			remove(reply_filename);
			return;
		}

	// Mark original as replied

	if (key != 'E') {
		int origatt = mm.letterList->getStatus();
		mm.letterList->setStatus(origatt | MS_REPLIED);
		if (!(origatt & MS_REPLIED))
			interface->setAnyRead();
	}

	reply = fopen(reply_filename, "at");

	// Signature

	const char *sg = mm.resourceObject->get(sigFile);
	if (sg && *sg) {
		FILE *s = fopen(sg, "rt");
		if (s) {
			char bufx[81];

			fputc('\n', reply);
			while (fgets(bufx, sizeof bufx, s))
				fputs(bufx, reply);
			fclose(s);
		}
	}

	// Tagline

	tagline1[0] = '\0';

	interface->Tagwin();

	if (tagline1[0])
		fprintf(reply, "\n... %s\n", tagline1);

	// Tearline (not for Blue Wave -- it does its own)

	if (mm.driverList->useTearline()) {
		if (!tagline1[0])
			fputc('\n', reply);
		fprintf(reply, "--- %s/%s v%d.%d\n", MM_NAME, sysname(),
			MM_MAJOR, MM_MINOR);
	}

	fclose(reply);

	tagline1[0] = '\0';

	// Reconvert the text

	replen = reconvert(reply_filename);
	mm.areaList->enterLetter(replyto_area, FROM, TO, SUBJ, orig_id,
		replyto_num, privat, NM, reply_filename, (int) replen);

	NM.isSet = false;
	To[0] = '\0';

	interface->areas.Select();
	interface->setUnsaved();
}

void LetterWindow::EditLetter(bool forwarding)
{
	FILE *reply;
	char reply_filename[256], FROM[36], TO[36], SUBJ[78], *body;
	const char *msgid;
	long siz;
	int replyto_num, replyto_area;
	bool privat;

	bool is_reply_area = (mm.areaList->getAreaNo() == REPLY_AREA);

	if (forwarding) {
		key = 'E';
		setToFrom(TO, FROM);
		sprintf(SUBJ, "%.63s (fwd)", mm.letterList->getSubject());
		privat = false;
	} else {
		strcpy(FROM, mm.letterList->getFrom());
		strcpy(TO, mm.letterList->getTo());
		sprintf(SUBJ, "%.69s", mm.letterList->getSubject());
		privat = mm.letterList->getPrivate();
	}

	NM = mm.letterList->getNetAddr();

	replyto_area = interface->areaMenu();
	if (replyto_area == -1)
		return;

	// The refnum is only good for the original area:

	replyto_num = (replyto_area != mm.letterList->getAreaID()) ?
		0 : mm.letterList->getReplyTo();

	msgid = replyto_num ? mm.letterList->getMsgID() : 0;

	mm.areaList->gotoArea(replyto_area);
	if (!EnterHeader(FROM, TO, SUBJ, privat)) {
		interface->areas.Select();
		interface->changestate(interface->active());
		return;
	}

	DestroyChain();		// current letter's chain reset

	mytmpnam(reply_filename);

	body = (char *) mm.letterList->getBody();
	letterconv_in(body);
	reply = fopen(reply_filename, "wt");
	if (forwarding)
		write_header_to_file(reply);
	fwrite(body, strlen(body), 1, reply);
	fclose(reply);
	body = 0;		// it will be auto-dealloc'd by next getBody

	editletter(reply_filename);
	siz = reconvert(reply_filename);

	if (!forwarding)
		mm.areaList->killLetter(mm.letterList->getMsgNum());

	mm.areaList->enterLetter(replyto_area, FROM, TO, SUBJ, msgid,
		replyto_num, privat, NM, reply_filename, (int) siz);

	if (is_reply_area)
		mm.letterList->rrefresh();
	interface->areas.Select();
	if (interface->active() == letter)
		interface->back();

	NM.isSet = false;

	interface->setUnsaved();
}

void LetterWindow::set_Tagline(const char *tl)
{
	strncpy(tagline1, tl, 76);
}

void LetterWindow::GetTagline()
{
	interface->taglines.EnterTagline(tagline1);
	ReDraw();
}

void LetterWindow::write_header_to_file(FILE *fd)
{
	char Header[400], *p;
	int j;

	for (j = 0; j < 72; j++)
		fputc('=', fd);

	mm.areaList->gotoArea(mm.letterList->getAreaID());

	p = Header + sprintf(Header,
		"\n System: %.71s\n   Area: %.71s\n   Date: %.25s\n"
		"   From: %.45s", mm.resourceObject->get(BBSName),
		mm.areaList->getDescription(), mm.letterList->getDate(),
		mm.letterList->getFrom());

	interface->areas.Select();

	if (mm.areaList->isEmail())
		p = netAdd(p);

	p += sprintf(p, "\n     To: %.45s", mm.letterList->getTo());

	if (mm.areaList->isReplyArea())
		p = netAdd(p);

	sprintf(p, "\n   Subj: %.71s\n", mm.letterList->getSubject());

	letterconv_in(Header);
	fputs(Header, fd);

	for (j = 0; j < 72; j++)
		fputc('-', fd);
	fputc('\n', fd);
}

void LetterWindow::write_to_file(FILE *fd)
{
	write_header_to_file(fd);

	// write chain to file

	MakeChain(80);
	for (int j = 0; j < NumOfLines; j++)
		fprintf(fd, "%s\n", linelist[j]->text);
	fputc('\n', fd);

	// set read, unmarked -- not part of writing to a file, but anyway

	int stat = mm.letterList->getStatus();
	int oldstat = stat;
	stat |= MS_READ;
	stat &= ~MS_MARKED;
	mm.letterList->setStatus(stat);
	if (stat != oldstat)
		interface->setAnyRead();
}

bool LetterWindow::search(const char *item)
{
	bool found = false;

	for (int x = position + 1; (x < NumOfLines) && !found; x++) {

		found = !(!searchstr(linelist[x]->text, item));

		if (found) {
			position = x;
			if (interface->active() == letter)
				DrawBody();
		}
	}

	return found;
}

bool LetterWindow::EditOriginal()
{
	int old_area = mm.letterList->getAreaID();
	int old_mnum = mm.letterList->getMsgNum();

	letter_list *old_list = mm.letterList;
	mm.areaList->gotoArea(REPLY_AREA);
	mm.areaList->getLetterList();

	bool found = mm.letterList->findReply(old_area, old_mnum);

	if (found)
		EditLetter(false);

	delete mm.letterList;
	mm.letterList = old_list;

	return found;
}

void LetterWindow::KeyHandle(int key)
{
	int t_area;

	switch (key) {
	case 'D':
		rot13 = !rot13;
		interface->changestate(letter);
		break;
	case 'X':
		hidden = !hidden;
		interface->changestate(letter);
		break;
	case 'S':
		Save(1);
		DrawHeader();
		ReDraw();
		break;
	case '?':
	case KEY_F(1):
		interface->changestate(letter_help);
		break;
	case 'V':
	case 1: // CTRL-A
	case 11: // CTRL-V
		{
			int nextAns;
			bool cont = false;
			do {
			    nextAns = interface->ansiLoop(mm.letterList->
				getBody(), mm.letterList->getSubject());
			    if (nextAns == 1)
				cont = Next();
			    else if (nextAns == -1)
				cont = Previous();
			} while (nextAns && cont);
		}
		break;
	case MM_PLUS:
		Next();
		break;
	case MM_MINUS:
		Previous();
		break;
	case ' ':
		NextDown();
		break;
	case 6:				// Ctrl-F
		EditLetter(true);
		break;
	default:
	    if (mm.areaList->isReplyArea()) {
		switch(key) {
		case 'R':
		case 'E':
			EditLetter(false);
			break;
		case 'K':
			interface->kill_letter();
			break;
		default:
			Move(key);
		}
	    } else {
		switch (key) {
		case 'M':
		case 'U':
			StatToggle((key == 'M') ? MS_MARKED : MS_READ);
			break;
		case 'R':	// Allow (and force) re-editing from here:
		case 'O':
			if (mm.letterList->getStatus() & MS_REPLIED)
				if (EditOriginal()) {
					interface->changestate(letter);
					break;
				}
		case 'E':
			t_area = interface->areaMenu();
			if (t_area != -1) {
				mm.areaList->gotoArea(t_area);
				set_Letter_Params(t_area, key);

				if (mm.areaList->isEmail())
					if (key == 'E')
						interface->addressbook();
					else {
						net_address nm = PickNetAddr();
						set_Letter_Params(nm, 0);
					}
				EnterLetter();
				interface->changestate(letter);
			}
			break;
		case 'N':
			t_area = (mm.areaList->isUsenet() ||
				mm.areaList->isInternet()) ?
				mm.areaList->findInternet() :
				mm.areaList->findNetmail();
			if (t_area != -1) {
			    net_address nm = PickNetAddr();
			    if (nm.isSet) {
				set_Letter_Params(t_area, 'N');
				set_Letter_Params(nm, 0);
				EnterLetter();
				DrawHeader();
				ReDraw();
			    } else
				interface->nonFatalError(
					"No reply address");
			} else
				interface->nonFatalError(
					"Netmail is not available");
			break;
		case 'T':
			GetTagline();
			break;
		default:
			Move(key);
		}
	    }
	}
}
