/*
 * $XConsortium: tocutil.c,v 2.60 95/01/09 16:52:53 swick Exp $
 * $XFree86: xc/programs/xmh/tocutil.c,v 3.4 2002/04/05 21:06:29 dickey Exp $
 *
 *
 *			COPYRIGHT 1987, 1989
 *		   DIGITAL EQUIPMENT CORPORATION
 *		       MAYNARD, MASSACHUSETTS
 *			ALL RIGHTS RESERVED.
 *
 * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND
 * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION.
 * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR
 * ANY PURPOSE.  IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
 *
 * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT
 * RIGHTS, APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN
 * ADDITION TO THAT SET FORTH ABOVE.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Digital Equipment Corporation not be
 * used in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 */

/* tocutil.c -- internal routines for toc stuff. */

#include "xmh.h"
#include "toc.h"
#include "tocutil.h"
#include "tocintrnl.h"

#ifdef X_NOT_POSIX
extern long lseek();
#endif

Toc TUMalloc(void)
{
    Toc toc;
    toc = XtNew(TocRec);
    bzero((char *)toc, (int) sizeof(TocRec));
    toc->msgs = (Msg *) NULL;
    toc->seqlist = (Sequence *) NULL;
    toc->validity = unknown;
    return toc;
}


/* Returns TRUE if the scan file for the given toc is out of date. */

int TUScanFileOutOfDate(Toc toc)
{
    return LastModifyDate(toc->path) > toc->lastreaddate;
}


/* Make sure the sequence menu entries correspond exactly to the sequences 
 * for this toc.
 */

void TUCheckSequenceMenu(Toc toc)
{
    Scrn	scrn;
    register int i, n;
    Arg		query_args[2];
    char 	*name;
    Cardinal	j;
    int		numChildren;
    Widget	menu, item;
    Button	button;
    WidgetList	children;

    static XtCallbackRec callbacks[] = {
	{ DoSelectSequence,		(XtPointer) NULL},
	{ (XtCallbackProc) NULL,	(XtPointer) NULL},
    };
    static Arg  args[] = {
	{ XtNcallback,			(XtArgVal) callbacks},
	{ XtNleftMargin, 		(XtArgVal) 18},
    };

    for (j=0; j < toc->num_scrns; j++) {
	scrn = toc->scrn[j];

	/* Find the sequence menu and the number of entries in it. */

	name = MenuBoxButtons[XMH_SEQUENCE].button_name;
	button = BBoxFindButtonNamed(scrn->mainbuttons, name);
	menu = BBoxMenuOfButton(button);
	XtSetArg(query_args[0], XtNnumChildren, &numChildren);
	XtSetArg(query_args[1], XtNchildren, &children);
	XtGetValues(menu, query_args, (Cardinal) 2);
	n = MenuBoxButtons[XMH_SEQUENCE].num_entries;
	if (strcmp(XtName(children[0]), "menuLabel") == 0)
	    n++;

	/* Erase the current check mark. */

	for (i=(n-1); i < numChildren; i++) 
	    ToggleMenuItem(children[i], False);

	/* Delete any entries which should be deleted. */

	for (i=n; i < numChildren; i++)
	    if (! TocGetSeqNamed(toc, XtName(children[i])))
		XtDestroyWidget(children[i]);

	/* Create any entries which should be created. */
	
	callbacks[0].closure = (XtPointer) scrn;
	for (i=1; i < toc->numsequences; i++) 
	    if (! XtNameToWidget(menu, toc->seqlist[i]->name))
		XtCreateManagedWidget(toc->seqlist[i]->name, smeBSBObjectClass,
				      menu, args, XtNumber(args));

	/* Set the check mark. */

	name = toc->viewedseq->name;
	if ((item = XtNameToWidget(menu, name)) != NULL)
	    ToggleMenuItem(item, True);
    }
    TocSetSelectedSequence(toc, toc->viewedseq);
}


void TUScanFileForToc(Toc toc)
{
    Scrn scrn;
    char  **argv, str[100];
    if (toc) {
	TUGetFullFolderInfo(toc);
	if (toc->num_scrns) scrn = toc->scrn[0];
	else scrn = scrnList[0];

	(void) sprintf(str, "Rescanning %s", toc->foldername);
	ChangeLabel(scrn->toclabel, str);

	argv = MakeArgv(5);
	argv[0] = "scan";
	argv[1] = TocMakeFolderName(toc);
	argv[2] = "-width";
	(void) sprintf(str, "%d", app_resources.toc_width);
	argv[3] = str;
	argv[4] = "-noheader";
	DoCommand(argv, (char *) NULL, toc->scanfile);
	XtFree(argv[1]);
	XtFree((char *) argv);

	toc->needslabelupdate = True;
	toc->validity = valid;
	toc->curmsg = NULL;	/* Get cur msg somehow! %%% */
    }
}



int TUGetMsgPosition(Toc toc, Msg msg)
{
    int msgid, h = 0, l, m;
    char str[100];
    static Boolean ordered = True;
    msgid = msg->msgid;
    if (ordered) {
	l = 0;
	h = toc->nummsgs - 1;
	while (l < h - 1) {
	    m = (l + h) / 2;
	    if (toc->msgs[m]->msgid > msgid)
		h = m;
	    else
		l = m;
	}
	if (toc->msgs[l] == msg) return l;
	if (toc->msgs[h] == msg) return h;
    }
    ordered = False;
    for (l = 0; l < toc->nummsgs; l++) {
	if (msgid == toc->msgs[l]->msgid) return l;
    }
    (void) sprintf(str,
		   "TUGetMsgPosition search failed! hi=%d, lo=%d, msgid=%d",
		   h, l, msgid);
    Punt(str);
    return 0; /* Keep lint happy. */
}


void TUResetTocLabel(Scrn scrn)
{
    char str[500];
    Toc toc;
    if (scrn) {
	toc = scrn->toc;
	if (toc == NULL)
	    (void) strcpy(str, " ");
	else {
	    if (toc->stopupdate) {
		toc->needslabelupdate = TRUE;
		return;
	    }
	    (void) sprintf(str, "%s:%s", toc->foldername,
			   toc->viewedseq->name);
	    toc->needslabelupdate = FALSE;
	}
	ChangeLabel((Widget) scrn->toclabel, str);
    }
}


/* A major toc change has occured; redisplay it.  (This also should work even
   if we now have a new source to display stuff from.) */

void TURedisplayToc(Scrn scrn)
{
    Toc toc;
    Widget source;
    if (scrn != NULL && scrn->tocwidget != NULL) {
	toc = scrn->toc;
 	if (toc) {
	    if (toc->stopupdate) {
		toc->needsrepaint = TRUE;
		return;
	    }
	    XawTextDisableRedisplay(scrn->tocwidget);
	    source = XawTextGetSource(scrn->tocwidget);
	    if (toc->force_reset || source != toc->source) {
		XawTextSetSource(scrn->tocwidget, toc->source,
				 (XawTextPosition) 0);
		toc->force_reset = False; /* %%% temporary */
	    }
	    TocSetCurMsg(toc, TocGetCurMsg(toc));
	    XawTextEnableRedisplay(scrn->tocwidget);
	    TUCheckSequenceMenu(toc);
	    toc->needsrepaint = FALSE;
	} else {
	    XawTextSetSource(scrn->tocwidget, PNullSource, (XawTextPosition) 0);
	}
    }
}


void TULoadSeqLists(Toc toc)
{
    Sequence seq;
    FILEPTR fid;
    char    str[500], *ptr, *ptr2, viewed[500], selected[500];
    int     i;
    if (toc->viewedseq) (void) strcpy(viewed, toc->viewedseq->name);
    else *viewed = 0;
    if (toc->selectseq) (void) strcpy(selected, toc->selectseq->name);
    else *selected = 0;
    for (i = 0; i < toc->numsequences; i++) {
	seq = toc->seqlist[i];
	XtFree((char *) seq->name);
	if (seq->mlist) FreeMsgList(seq->mlist);
	XtFree((char *)seq);
    }
    toc->numsequences = 1;
    toc->seqlist = (Sequence *) XtRealloc((char *) toc->seqlist,
					  (Cardinal) sizeof(Sequence));
    seq = toc->seqlist[0] = XtNew(SequenceRec);
    seq->name = XtNewString("all");
    seq->mlist = NULL;
    toc->viewedseq = seq;
    toc->selectseq = seq;
    (void) sprintf(str, "%s/.mh_sequences", toc->path);
    fid = myfopen(str, "r");
    if (fid) {
	while ((ptr = ReadLine(fid))) {
	    ptr2 = strchr(ptr, ':');
	    if (ptr2) {
		*ptr2 = 0;
		if (strcmp(ptr, "all") != 0 &&
		    strcmp(ptr, "cur") != 0 &&
		    strcmp(ptr, "unseen") != 0) {
		    toc->numsequences++;
		    toc->seqlist = (Sequence *)
			XtRealloc((char *) toc->seqlist, (Cardinal)
				  toc->numsequences * sizeof(Sequence));
		    seq = toc->seqlist[toc->numsequences - 1] =
			XtNew(SequenceRec);
		    seq->name = XtNewString(ptr);
		    seq->mlist = StringToMsgList(toc, ptr2 + 1);
		    if (strcmp(seq->name, viewed) == 0) {
			toc->viewedseq = seq;
			*viewed = 0;
		    }
		    if (strcmp(seq->name, selected) == 0) {
			toc->selectseq = seq;
			*selected = 0;
		    }
		}
	    }
	}
	(void) myfclose(fid);
    }
}



/* Refigure what messages are visible. */

void TURefigureWhatsVisible(Toc toc)
{
    MsgList mlist;
    Msg msg, oldcurmsg;
    int i;
    int	w, changed, newval, msgid;
    Sequence seq = toc->viewedseq;
    mlist = seq->mlist;
    oldcurmsg = toc->curmsg;
    TocSetCurMsg(toc, (Msg)NULL);
    w = 0;
    changed = FALSE;

    for (i = 0; i < toc->nummsgs; i++) {
	msg = toc->msgs[i];
	msgid = msg->msgid;
	while (mlist && mlist->msglist[w] && mlist->msglist[w]->msgid < msgid)
	    w++;
	newval = (!mlist
		  || (mlist->msglist[w] && mlist->msglist[w]->msgid == msgid));
	if (newval != msg->visible) {
	    changed = TRUE;
	    msg->visible = newval;
	}
    }
    if (changed) {
	TURefigureTocPositions(toc);
	if (oldcurmsg) {
	    if (!oldcurmsg->visible) {
		toc->curmsg = TocMsgAfter(toc, oldcurmsg);
		if (toc->curmsg == NULL)
		    toc->curmsg = TocMsgBefore(toc, oldcurmsg);
	    } else toc->curmsg = oldcurmsg;
	}
	for (i=0 ; i<toc->num_scrns ; i++)
	    TURedisplayToc(toc->scrn[i]);
    } else TocSetCurMsg(toc, oldcurmsg);
    for (i=0 ; i<toc->num_scrns ; i++)
	TUResetTocLabel(toc->scrn[i]);
}


/* (Re)load the toc from the scanfile.  If reloading, this makes efforts to
   keep the fates of msgs, and to keep msgs that are being edited.  Note that
   this routine must know of all places that msg ptrs are stored; it expects
   them to be kept only in tocs, in scrns, and in msg sequences. */

#define SeemsIdentical(msg1, msg2) ((msg1)->msgid == (msg2)->msgid &&	      \
				    ((msg1)->temporary || (msg2)->temporary ||\
				     strcmp((msg1)->buf, (msg2)->buf) == 0))

void TULoadTocFile(Toc toc)
{
    int maxmsgs, l, orignummsgs, i, j, origcurmsgid;
    FILEPTR fid;
    XawTextPosition position;
    char *ptr;
    Msg msg, curmsg;
    Msg *origmsgs;
    int bufsiz = app_resources.toc_width + 1;
    static char *buf;

    if (!buf)
	buf = XtMalloc((Cardinal) bufsiz);
    TocStopUpdate(toc);
    toc->lastreaddate = LastModifyDate(toc->scanfile);
    if (toc->curmsg) {
	origcurmsgid = toc->curmsg->msgid;
	TocSetCurMsg(toc, (Msg)NULL);
    } else origcurmsgid = 0;  /* The "default" current msg; 0 means none */
    fid = FOpenAndCheck(toc->scanfile, "r");
    maxmsgs = orignummsgs = toc->nummsgs;
    if (maxmsgs == 0) maxmsgs = 100;
    toc->nummsgs = 0;
    origmsgs = toc->msgs;
    toc->msgs = (Msg *) XtMalloc((Cardinal) maxmsgs * sizeof(Msg));
    position = 0;
    i = 0;
    curmsg = NULL;
    while ((ptr = fgets(buf, bufsiz, fid))) {
	toc->msgs[toc->nummsgs++] = msg = XtNew(MsgRec);
	bzero((char *) msg, sizeof(MsgRec));
	msg->toc = toc;
	msg->position = position;
	msg->length = l = strlen(ptr);
	position += l;
	if (l == app_resources.toc_width && buf[bufsiz-2] != '\n') {
	    buf[bufsiz-2] = '\n';
	    msg->buf = strcpy(XtMalloc((Cardinal) ++l), ptr);
	    msg->msgid = atoi(ptr);
	    do 
		ptr = fgets(buf, bufsiz, fid);
	    while (ptr && (int) strlen(ptr) == app_resources.toc_width
		   && buf[bufsiz-2] != '\n');
	} else {
	    msg->buf = strcpy(XtMalloc((Cardinal) ++l), ptr);
	    msg->msgid = atoi(ptr);
	}
	if (msg->msgid == origcurmsgid)
	    curmsg = msg;
	msg->buf[MARKPOS] = ' ';
	msg->changed = FALSE;
	msg->fate = Fignore;
	msg->desttoc = NULL;
	msg->visible = TRUE;
	if (toc->nummsgs >= maxmsgs) {
	    maxmsgs += 100;
	    toc->msgs = (Msg *) XtRealloc((char *) toc->msgs,
					  (Cardinal) maxmsgs * sizeof(Msg));
	}
	while (i < orignummsgs && origmsgs[i]->msgid < msg->msgid) i++;
	if (i < orignummsgs) {
	    origmsgs[i]->buf[MARKPOS] = ' ';
	    if (SeemsIdentical(origmsgs[i], msg))
		MsgSetFate(msg, origmsgs[i]->fate, origmsgs[i]->desttoc);
	}
    }
    toc->length = toc->origlength = toc->lastPos = position;
    toc->msgs = (Msg *) XtRealloc((char *) toc->msgs,
				  (Cardinal) toc->nummsgs * sizeof(Msg));
    (void) myfclose(fid);
    if ( (toc->source == NULL) && ( toc->num_scrns > 0 ) ) {
        Arg args[1];

	XtSetArg(args[0], XtNtoc, toc);
	toc->source = XtCreateWidget("tocSource", tocSourceWidgetClass,
				     toc->scrn[0]->tocwidget,
				     args, (Cardinal) 1);
    }
    for (i=0 ; i<numScrns ; i++) {
	msg = scrnList[i]->msg;
	if (msg && msg->toc == toc) {
	    for (j=0 ; j<toc->nummsgs ; j++) {
		if (SeemsIdentical(toc->msgs[j], msg)) {
		    msg->position = toc->msgs[j]->position;
		    msg->visible = TRUE;
		    ptr = toc->msgs[j]->buf;
		    l = toc->msgs[j]->length;
		    *(toc->msgs[j]) = *msg;
		    toc->msgs[j]->buf = ptr;
		    toc->msgs[j]->length = l;
		    scrnList[i]->msg = toc->msgs[j];
		    break;
		}
	    }
	    if (j >= toc->nummsgs) {
		msg->temporary = FALSE;	/* Don't try to auto-delete msg. */
		MsgSetScrnForce(msg, (Scrn) NULL);
	    }
	}
    }
    for (i=0 ; i<orignummsgs ; i++)
	MsgFree(origmsgs[i]);
    XtFree((char *)origmsgs);
    TocSetCurMsg(toc, curmsg);
    TULoadSeqLists(toc);
    TocStartUpdate(toc);
}


void TUSaveTocFile(Toc toc)
{
    Msg msg;
    int fid;
    int i;
    XawTextPosition position;
    char c;
    if (toc->stopupdate) {
	toc->needscachesave = TRUE;
	return;
    }
    fid = -1;
    position = 0;
    for (i = 0; i < toc->nummsgs; i++) {
	msg = toc->msgs[i];
	if (fid < 0 && msg->changed) {
	    fid = myopen(toc->scanfile, O_RDWR, 0666);
	    (void) lseek(fid, (long)position, 0);
	}
	if (fid >= 0) {
	    c = msg->buf[MARKPOS];
	    msg->buf[MARKPOS] = ' ';
	    (void) write(fid, msg->buf, msg->length);
	    msg->buf[MARKPOS] = c;
	}
	position += msg->length;
    }
    if (fid < 0 && toc->length != toc->origlength)
	fid = myopen(toc->scanfile, O_RDWR, 0666);
    if (fid >= 0) {
#if defined(SYSV) && (defined(i386) || defined(MOTOROLA))
	(void) ftruncate_emu(fid, toc->length, toc->scanfile);
#else
	(void) ftruncate(fid, toc->length);
	myclose(fid);
#endif
	toc->origlength = toc->length;
    }
    toc->needscachesave = FALSE;
    toc->lastreaddate = LastModifyDate(toc->scanfile);
}


static Boolean UpdateScanFile(
  XtPointer client_data)	/* Toc */
{
    Toc toc = (Toc)client_data;
    int i;

    if (app_resources.block_events_on_busy) ShowBusyCursor();

    TUScanFileForToc(toc);
    TULoadTocFile(toc);

    for (i=0 ; i<toc->num_scrns ; i++)
	TURedisplayToc(toc->scrn[i]);

    if (app_resources.block_events_on_busy) UnshowBusyCursor();

    return True;
}


void TUEnsureScanIsValidAndOpen(Toc toc, Boolean delay)
{
    if (toc) {
	TUGetFullFolderInfo(toc);
	if (TUScanFileOutOfDate(toc)) {
	    if (!delay)
		UpdateScanFile((XtPointer)toc);
	    else {
		/* this is a hack to get the screen mapped before
		 * spawning the subprocess (and blocking).
		 * Need to make sure the scanfile exists at this point.
		 */
		int fid = myopen(toc->scanfile, O_RDWR|O_CREAT, 0666);
		myclose(fid);
		XtAppAddWorkProc(XtWidgetToApplicationContext(toplevel),
				 UpdateScanFile,
				 (XtPointer)toc);
	    }
	}
	if (toc->source == NULL)
	    TULoadTocFile(toc);
    }
}



/* Refigure all the positions, based on which lines are visible. */

void TURefigureTocPositions(Toc toc)
{
    int i;
    Msg msg;
    XawTextPosition position, length;
    position = length = 0;
    for (i=0; i<toc->nummsgs ; i++) {
	msg = toc->msgs[i];
	msg->position = position;
	if (msg->visible) position += msg->length;
	length += msg->length;
    }
    toc->lastPos = position;
    toc->length = length;
}



/* Make sure we've loaded ALL the folder info for this toc, including its
   path and sequence lists. */

void TUGetFullFolderInfo(Toc toc)
{
    char str[500];
    if (! toc->scanfile) {
	if (! toc->path) {
	    /* usually preset by TocFolderExists */
	    (void) sprintf(str, "%s/%s", app_resources.mail_path,
			   toc->foldername);
	    toc->path = XtNewString(str);
	}
	(void) sprintf(str, "%s/.xmhcache", toc->path);
	toc->scanfile = XtNewString(str);
	toc->lastreaddate = LastModifyDate(toc->scanfile);
	if (TUScanFileOutOfDate(toc))
	    toc->validity = invalid;
	else {
	    toc->validity = valid;
	    TULoadTocFile(toc);
	}
    }
}

/* Append a message to the end of the toc.  It has the given scan line.  This
   routine will figure out the message number, and change the scan line
   accordingly. */

Msg TUAppendToc(Toc toc, char *ptr)
{
    Msg msg;
    int msgid;

    TUGetFullFolderInfo(toc);
    if (toc->validity != valid)
	return NULL;
	    
    if (toc->nummsgs > 0)
	msgid = toc->msgs[toc->nummsgs - 1]->msgid + 1;
    else
	msgid = 1;
    (toc->nummsgs)++;
    toc->msgs = (Msg *) XtRealloc((char *) toc->msgs,
				  (Cardinal) toc->nummsgs * sizeof(Msg));
    toc->msgs[toc->nummsgs - 1] = msg = XtNew(MsgRec);
    bzero((char *) msg, (int) sizeof(MsgRec));
    msg->toc = toc;
    msg->buf = XtNewString(ptr);
    if (msgid >= 10000)
	msgid %= 10000;
    (void)sprintf(msg->buf, "%4d", msgid);
    msg->buf[MARKPOS] = ' ';
    msg->msgid = msgid;
    msg->position = toc->lastPos;
    msg->length = strlen(ptr);
    msg->changed = TRUE;
    msg->fate = Fignore;
    msg->desttoc = NULL;
    if (toc->viewedseq == toc->seqlist[0]) {
	msg->visible = TRUE;
	toc->lastPos += msg->length;
    }
    else
	msg->visible = FALSE;
    toc->length += msg->length;
    if ( (msg->visible) && (toc->source != NULL) )
	TSourceInvalid(toc, msg->position, msg->length);
    TUSaveTocFile(toc);
    return msg;
}
