/*
 * MultiMail offline mail reader
 * OMEN

 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 "omen.h"
#include "compress.h"

// Area attributes in SYSTEMxy.BBS
enum {OM_WRITE = 1, OM_SYSOP = 2, OM_PRIVATE = 4, OM_PUBLIC = 8,
	OM_NETMAIL = 16, OM_ALIAS = 32, OM_ACTIVE = 64};

// ---------------------------------------------------------------------------
// The OMEN methods
// ---------------------------------------------------------------------------

omen::omen(mmail *mmA)
{
	mm = mmA;
	ID = 0;
	bodyString = 0;

	strcpy(extent, defExtent());

	readSystemBBS();
	buildIndices();

	const char x[1][13] = {"bullet"};
	listBulletins(x, 1, false);
}

omen::~omen()
{
	delete[] bulletins;

	while (maxConf--) {
		delete[] body[maxConf];
		delete[] areas[maxConf].name;
	}
	delete[] body;
	delete[] areas;
	delete[] bodyString;

	fclose(infile);
}

file_header *omen::getFileList()
{
	return mm->workList->existsF("nfile");
}

area_header *omen::getNextArea()
{
	int cMsgNum = areas[ID].nummsgs;

	area_header *tmp = new area_header(mm,
		ID + mm->driverList->getOffset(this), areas[ID].numA,
		areas[ID].name, areas[ID].name, "OMEN",
		areas[ID].attr | (cMsgNum ? ACTIVE : 0), cMsgNum,
		0, 35, 72);
	ID++;
	return tmp;
}

int omen::getNoOfLetters()
{
	return areas[currentArea].nummsgs;
}

int omen::getXNum(int area)
{
	int c;

	for (c = 0; c < maxConf; c++)
		if (areas[c].num == area)
			break;
	return c;
}

// Build indices from NEWMSGxy.TXT
void omen::buildIndices()
{
	int x;

	body = new bodytype *[maxConf];

	for (x = 0; x < maxConf; x++) {
		body[x] = 0;
		areas[x].nummsgs = 0;
	}

	class ndx_fake {
	 public:
		int confnum;
		long pointer, length;
		ndx_fake *next;
	} base, *oldndx, *tmpndx = &base;

	char junk[12];
	const char *p;
	int cMsgNum, counter, current = 0;

	infile = mm->workList->ftryopen("newmsg", "rb");
	if (infile) {
		while (!feof(infile)) {
			counter = ftell(infile);
			p = nextLine();

			if (!feof(infile) && (*p++ == 1)) {
				tmpndx->next = new ndx_fake;
				tmpndx = tmpndx->next;

				// get area number:
				sscanf(p, "#%s  %d:", junk, &x);
				x = getXNum(x);

				tmpndx->confnum = x;
				tmpndx->pointer = counter;
				current++;
				areas[x].nummsgs++;

				// skip rest of header:
				while (!feof(infile) && (fgetc(infile) != 2));

				// find length of text:
				long len = 0;
				while (!feof(infile) && (fgetc(infile) != 3))
					len++;
				tmpndx->length = len;
			}
		}
	} else
		fatalError("Could not open NEWMSGxy.TXT");

	numMsgs = current;

	for (x = 0; x < maxConf; x++) {
		cMsgNum = areas[x].nummsgs;
		if (cMsgNum) {
			body[x] = new bodytype[cMsgNum];
			areas[x].nummsgs = 0;
		}
	}

	tmpndx = base.next;
	for (x = 0; x < numMsgs; x++) {
		current = tmpndx->confnum;
		body[current][areas[current].nummsgs].pointer =
			tmpndx->pointer;
		body[current][areas[current].nummsgs++].msgLength =
			tmpndx->length;
		oldndx = tmpndx;
		tmpndx = tmpndx->next;
		delete oldndx;
	}
}

letter_header *omen::getNextLetter()
{
	char date[20], subject[73], c, priflag = 0, *from, *to, *net;
	int x, msgnum, junk, refnum = 0;
	net_address na;

	fseek(infile, body[currentArea][currentLetter].pointer + 1, SEEK_SET);
	long len = body[currentArea][currentLetter].msgLength;

	// First header line -- msgnum, date, refnum, priflag:

	from = nextLine() + 1;
	to = strchr(from, ' ');
	*to = '\0';
	msgnum = atoi(from);

	from = to + 2;
	while (!((from[0] == ' ') && (from[1] == ' ')))
		from = strchr(from + 1, ' ');
	from += 2;

	to = strchr(from, '(');
	if (to) {
		to[-2] = '\0';
		sscanf(to, "(%d/%d)", &refnum, &junk);
		to = strchr(to + 1, '(');
		priflag = to[1];
	}
	strcpy(date, from);

	// Second line -- to and from:

	na.zone = 0;

	from = nextLine();
	to = strchr(from, '=');
	if (to) {
		to[-1] = '\0';
		to += 3;

		net = strchr(to, '(');
		if (net) {
			net[-7] = '\0';
			na = ++net;
		}
	}

	// Third line -- subject:

	// skip "Subj: "
	for (x = 0; x < 6; x++)
		fgetc(infile);
	x = 0;

	// can't use fgets because line is not LF-terminated
	do {
		c = fgetc(infile);
		subject[x++] = c;
	} while ((c != 2) && (x != 73));
	subject[--x] = '\0';

	return new letter_header(mm, subject, to, from, date, 0, refnum,
		currentLetter++, msgnum, currentArea, (priflag == 'P'),
		len, this, na, !(!(areas[currentArea].attr & LATINCHAR)));
}

// returns the body of the requested letter
const char *omen::getBody(letter_header &mhead)
{
	unsigned char *p;
	int c, kar, AreaID, LetterID;

	AreaID = mhead.getAreaID() - mm->driverList->getOffset(this);
	LetterID = mhead.getLetterID();

	delete[] bodyString;
	bodyString = new char[body[AreaID][LetterID].msgLength + 1];
	fseek(infile, body[AreaID][LetterID].pointer, SEEK_SET);

	while (!feof(infile) && (fgetc(infile) != 2));	// Skip header

	for (c = 0, p = (unsigned char *) bodyString;
	     c < body[AreaID][LetterID].msgLength; c++) {
		kar = fgetc(infile);

		if (!kar)
			kar = ' ';

		if (kar != '\r')
			*p++ = kar;
	}
	do
		p--;
	while ((*p == ' ') || (*p == '\n'));	// Strip blank lines
	p[1] = '\0';

	return bodyString;
}

char *omen::nextLine()
{
	static char line[128];

	char *end = myfgets(line, sizeof line, infile);
	if (end)
		while ((*end == '\n') || (*end == '\r'))
			*end-- = '\0';
	return line;
}

// Area and packet init: SYSTEMxy.BBS, BNAMESxy.BBS, INFOxy.BBS
void omen::readSystemBBS()
{
	unsigned useLatin;
	file_list *wl = mm->workList;

	hasOffConfig = useLatin = 0;

	// The following info is unavailable in OMEN:
	const char *defName = mm->resourceObject->get(UserName);

	mm->resourceObject->set(LoginName, (defName && *defName) ?
		defName: "(set on upload)");
	mm->resourceObject->set(AliasName, "(set on upload)");

	// INFOxy.BBS:

	infile = wl->ftryopen("info", "rb");
	if (infile) {
		while (!feof(infile)) {
			const char *line = nextLine();
			if (!strncasecmp(line, "sysop:", 6))
				mm->resourceObject->set(SysOpName, line + 6);
			else
				if (!strcasecmp(line, "select:on"))
					hasOffConfig = OFFCONFIG;
				else
					if (!strcasecmp(line, "c_set:iso"))
						useLatin = LATINCHAR;
		}
		fclose(infile);
	} else
			mm->resourceObject->set(SysOpName, 0);

	// SYSTEMxy.BBS, and BNAMESxy.BBS if available:

	infile = wl->ftryopen("system", "rb");
	if (infile) {
		const char *s = wl->getName();
		packetBaseName[0] = toupper(s[6]);
		packetBaseName[1] = toupper(s[7]);
		packetBaseName[2] = '\0';

		maxConf = (wl->getSize() - 41) / sizeof(ATMP);
		struct {
			unsigned char len;
			char name[41];
		} b;

		fread(&b, 1, 41, infile);
		b.name[b.len] = '\0';
		mm->resourceObject->set(BBSName, b.name);

		areas = new AREAs[maxConf];
		areatmp = new ATMP[maxConf];

		fread(areatmp, sizeof(ATMP), maxConf, infile);
		fclose(infile);

		infile = wl->ftryopen("bnames", "rb");
		for (int x = 0; x < maxConf; x++) {
			areas[x].num = (int) (areatmp[x].BrdHighNum << 8) +
				areatmp[x].BrdNum;
			sprintf(areas[x].numA, "%d", areas[x].num);

			int a = areatmp[x].BrdStatus;
			areas[x].attr = ((a & OM_ACTIVE) ? ACTIVE : 0) |
				((a & OM_PRIVATE) ? PRIVATE : 0) |
				((a & OM_PUBLIC) ? PUBLIC : 0) |
				((a & OM_NETMAIL) ? NETMAIL : 0) |
				((a & OM_ALIAS) ? ALIAS : 0) |
				hasOffConfig | useLatin;

			if (infile)		// use long area names
				areas[x].name =
				    strdupplus(strchr(nextLine(), ':') + 1);
			else {
				int len = areatmp[x].BrdNameLen;
				areas[x].name = new char[len + 1];
				strncpy(areas[x].name,
				    areatmp[x].BrdName, len);
				areas[x].name[len] = '\0';
			}
		}
		if (infile)
			fclose(infile);
		delete[] areatmp;
	}
}

const char *omen::getExtent()
{
	return extent;
}

// ---------------------------------------------------------------------------
// The OMEN reply methods
// ---------------------------------------------------------------------------

// Letter attributes in HEADERxy.BBS
enum {OMR_SAVE = 1, OMR_DEL = 2, OMR_TOGGLE = 4, OMR_MOVE = 8,
	OMR_PRIVATE = 16, OMR_ALIAS = 32};

// convert OMEN reply packet header to C structures
bool omenrep::upl_omen::init(FILE *rep)
{
	if (fread(&omen_rec, sizeof omen_rec, 1, rep) != 1)
		return false;

	refnum = getshort(omen_rec.msghighnumber) << 16 +
		getshort(omen_rec.msgnumber);
	privat = !(!(omen_rec.command & OMR_PRIVATE));

	na.zone = getshort(omen_rec.destzone);
	if (na.zone) {
		na.net = getshort(omen_rec.destnet);
		na.node = getshort(omen_rec.destnode);
		na.point = 0;	// points aren't supported :-(
		na.isSet = true;
	}
	strncpy(subject, omen_rec.subject, omen_rec.sublen);
	subject[omen_rec.sublen] = '\0';
	strncpy(to, omen_rec.to, omen_rec.tolen);
	to[omen_rec.tolen] = '\0';

	origArea = getshort(&omen_rec.curboard);

	return true;
}

// write out OMEN reply packet header
void omenrep::upl_omen::output(FILE *rep)
{
	memset(&omen_rec, 0, sizeof omen_rec);

	omen_rec.command = OMR_SAVE | (privat ? OMR_PRIVATE : 0);

	int refhigh = refnum >> 16;
	int reflow = refnum & 0xffff;
	putshort(omen_rec.msghighnumber, refhigh);
	putshort(omen_rec.msgnumber, reflow);

	if (na.isSet) {
		putshort(omen_rec.destzone, na.zone);
		putshort(omen_rec.destnet, na.net);
		putshort(omen_rec.destnode, na.node);
	}
	omen_rec.sublen = strlen(subject);
	strncpy(omen_rec.subject, subject, omen_rec.sublen);
	omen_rec.tolen = strlen(to);
	strncpy(omen_rec.to, to, omen_rec.tolen);

	putshort(&omen_rec.curboard, origArea);

	fwrite(&omen_rec, sizeof omen_rec, 1, rep);
}

omenrep::omenrep(mmail *mmA, specific_driver *baseClassA)
{
	mm = mmA;
	baseClass = (pktbase *) baseClassA;

	replyText = 0;
	uplListHead = 0;

	replyExists = false;
}

omenrep::~omenrep()
{
	cleanup();
}

// convert one reply to MultiMail's internal format
bool omenrep::getRep1(FILE *rep, upl_omen *l, int recnum)
{
	FILE *orgfile, *destfile;
	char orgname[13];
	int c, count = 0;

	if (!l->init(rep))
		return false;

	sprintf(orgname, "msg%s%02d.txt", baseClass->getBaseName(),
		recnum);
	mytmpnam(l->fname);

	if ((orgfile = upWorkList->ftryopen(orgname, "rb"))) {
	    if ((destfile = fopen(l->fname, "wt"))) {
		while ((c = fgetc(orgfile)) != EOF) {
			if (c != '\r') {
				fputc(c, destfile);
				count++;
			}
		}
		fclose(destfile);
	    }
	    fclose(orgfile);
	    upWorkList->kill();
	}

	l->msglen = count;

	return true;
}

// convert all replies
void omenrep::getReplies(FILE *repFile)
{
	noOfLetters = upWorkList->getSize() / sizeof(omenReplyRec);

	upl_omen baseUplList, *currUplList = &baseUplList;

	for (int c = 0; c < noOfLetters; c++) {
		currUplList->nextRecord = new upl_omen;
		currUplList = (upl_omen *) currUplList->nextRecord;
		if (!getRep1(repFile, currUplList, c)) {
			delete currUplList;
			break;
		}
	}
	uplListHead = baseUplList.nextRecord;
}

area_header *omenrep::getNextArea()
{
	return new area_header(mm, 0, "REPLY", "REPLIES",
		"Letters written by you",  "OMEN replies",
		(COLLECTION | REPLYAREA | ACTIVE | PUBLIC | PRIVATE),
		noOfLetters, 0, 35, 72);
}

letter_header *omenrep::getNextLetter()
{
	upl_omen *current = (upl_omen *) uplListCurrent;
	const char *defName = mm->resourceObject->get(UserName);

	letter_header *newLetter = new letter_header(mm,
		current->subject, current->to, (defName && *defName) ?
		defName : "(set on upload)", "(set on upload)", 0,
		current->refnum, currentLetter, currentLetter,
		((omen *) baseClass)->getXNum(current->origArea) + 1,
		current->privat, current->msglen, this, current->na);

	currentLetter++;
	uplListCurrent = uplListCurrent->nextRecord;
	return newLetter;
}

void omenrep::enterLetter(letter_header &newLetter,
				const char *newLetterFileName, int length)
{
	upl_omen *newList = new upl_omen;
	memset(newList, 0, sizeof(upl_omen));

	strncpy(newList->subject, newLetter.getSubject(), 72);
	strncpy(newList->to, newLetter.getTo(), 35);

	newList->origArea = atoi(mm->areaList->getShortName());
	newList->privat = newLetter.getPrivate();
	newList->refnum = newLetter.getReplyTo();
	newList->na = newLetter.getNetAddr();
	strcpy(newList->fname, newLetterFileName);

	newList->msglen = length;

	addUpl(newList);
}

// write out one reply in OMEN format
void omenrep::addRep1(FILE *rep, upl_base *node, int recnum)
{
	FILE *orgfile, *destfile;
	upl_omen *l = (upl_omen *) node;
	char dest[13];

	l->output(rep);

	sprintf(dest, "MSG%s%02d.TXT", baseClass->getBaseName(),
		recnum);

	if ((orgfile = fopen(l->fname, "rt"))) {

		char *replyText = new char[l->msglen + 1];

		fread(replyText, l->msglen, 1, orgfile);
		fclose(orgfile);

		replyText[l->msglen] = '\0';

		if ((destfile = fopen(dest, "wb"))) {

			char *lastsp = 0, *q = replyText;
			int count = 0;

			for (char *p = replyText; *p; p++) {
				if (*p == '\n') {
					*p = '\0';
					fprintf(destfile, "%s\r\n", q);
					q = p + 1;
					count = 0;
					lastsp = 0;
				} else {
					count++;
					if (*p == ' ')
						lastsp = p;
				}

				// wrap at 80 characters
				if ((count >= 80) && lastsp) {
					*lastsp = '\n';
					p = lastsp - 1;
				}
			}
			if (count)
				fprintf(destfile, "%s\r\n", q);
			fclose(destfile);
		}
		delete[] replyText;
	}
}

void omenrep::addHeader(FILE *repFile)
{
	repFile = repFile;	// nothing to do but supress warnings
}

// set names for reply packet files
void omenrep::repFileName()
{
        const char *basename = baseClass->getBaseName();

	sprintf(replyPacketName, "return%c%c.%s", tolower(basename[0]),
		tolower(basename[1]), ((omen *) baseClass)->getExtent());
	sprintf(replyInnerName, "HEADER%s.BBS", basename);
}

// list files to be archived when creating reply packet
const char *omenrep::repTemplate(bool offres)
{
	static char buff[30];

	sprintf(buff, offres ? "%s *.TXT *.CNF" : "%s *.TXT",
		replyInnerName);

	return buff;
}

// re-read an offline config file
bool omenrep::getOffConfig()
{
	FILE *olc;

	bool status = false;
	upWorkList = new file_list(mm->resourceObject->get(UpWorkDir));

	if ((olc = upWorkList->ftryopen("select", "rb"))) {
		char line[128];
		int areaOMEN, areaNo, current = -1;

		area_list *al = mm->areaList;
		int maxareas = al->noOfAreas();

		// Like Blue Wave, areas are dropped unless listed in
		// the config file. Unlike Blue Wave, we can't be certain
		// of an area's status (SUBKNOWN), hence the extra checks.

		do {
			myfgets(line, sizeof line, olc);
			sscanf(line, "%d", &areaOMEN);
			areaNo = ((omen *) baseClass)->
				getXNum(areaOMEN) + 1;

			for (int c = current + 1; c < maxareas; c++) {
				al->gotoArea(c);
				if (c == areaNo) {
					if (!(al->getType() & ACTIVE))
						al->Add();
					current = c;
					break;
				} else
					if (al->getType() & ACTIVE)
						al->Drop();
			}
		} while (!feof(olc));

		fclose(olc);
		upWorkList->kill();

		status = true;
	}
	delete upWorkList;
	return status;
}

bool omenrep::makeOffConfig()
{
	FILE *todoor;
	char fname[13];

	sprintf(fname, "SELECT%s.CNF", baseClass->getBaseName());

	todoor = fopen(fname, "wb");
	if (!todoor)
		return false;

	int maxareas = mm->areaList->noOfAreas();
	for (int areaNo = 0; areaNo < maxareas; areaNo++) {
		mm->areaList->gotoArea(areaNo);
		int attrib = mm->areaList->getType();

		if (!(attrib & COLLECTION) && (((attrib & ACTIVE)
		    && !(attrib & DROPPED)) || (attrib & ADDED)))
 			fprintf(todoor, "%s\n", mm->areaList->getShortName());
	}
	fclose(todoor);

	return true;
}
