/* +-------------------------------------------------------------------+ */
/* | Copyright 1993, David Koblas (koblas@netcom.com)                  | */
/* |                                                                   | */
/* | Permission to use, copy, modify, and to distribute this software  | */
/* | and its documentation for any purpose is hereby granted without   | */
/* | fee, provided that the above copyright notice appear in all       | */
/* | copies and that both that copyright notice and this permission    | */
/* | notice appear in supporting documentation.  There is no           | */
/* | representations about the suitability of this software for        | */
/* | any purpose.  this software is provided "as is" without express   | */
/* | or implied warranty.                                              | */
/* |                                                                   | */
/* +-------------------------------------------------------------------+ */

#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Toggle.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/List.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/Text.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Xaw/Scrollbar.h>
#include <X11/StringDefs.h>
#include <stdio.h>
#include <ctype.h>

#include "misc.h"

#define TAB_DISTANCE	4

static String helpText[] = {
#include "Help.txt.h"
};

typedef struct HelpInfo_s {
	char			*name, *topic;
	int			index;
	int			start, stop;
	struct HelpInfo_s	*next;
} HelpInfo;

typedef struct {
	Widget		shell;
	int		curTopic;
	Widget		topicList, topicPort;
	Widget		textText, textTitle;
	Widget		nextButton, prevButton;
	Widget		scrollbar;
	int		ninfo;
	String		*topics;
	HelpInfo	*info;
} LocalInfo;

static Widget	toplevel;

/*
**  Build heirarchitcal help information structures.
*/
char *matchGet(char *line, char *pat)
{
	static char	buf[256];
	int		len = strlen(pat);
	char		*sp, *ep;

	if (strncmp(line, pat, len) != 0) 
		return NULL;

	for (sp = line + len; isspace(*sp); sp++);
	for (ep = sp; isalnum(*ep); ep++);
	strncpy(buf, sp, ep - sp);
	buf[ep-sp] = '\0';

	return buf;
}

static HelpInfo	*buildInfo(int *count)
{
	Boolean		nc, flg;
	int		depth = 0, stop = -1;
	int		i, j;
	char		parts[10][20];
	HelpInfo	*head = NULL, **nxt = &head, *cur = NULL;
	char		*cp, *tp = NULL;
	int		idx = 0;

	if (count != NULL)
		*count = 0;

	for (i = 0; i < XtNumber(helpText); i++) {
		flg = nc = False;
		if ((cp = matchGet(helpText[i], "#BEGIN")) != NULL) {
			int	argc;
			char	*argv[128];
			char	buf[256];

			nc = True;
			strcpy(buf, helpText[i]);	/* Need R&W buffer */
			StrToArgv(buf, &argc, argv);
			strcpy(parts[depth], argv[1]);
			tp = (argc > 2) ? argv[2] : argv[1];
		} else if ((cp = matchGet(helpText[i], "#PUSH")) != NULL) {
			depth++;
			flg = True;
		} else if ((cp = matchGet(helpText[i], "#POP")) != NULL) {
			if (depth > 0)
				depth--;
			flg = True;
		} else if ((cp = matchGet(helpText[i], "#NL")) != NULL) {
			stop = i;
		} else {
			stop = i;
		}

		if (nc) {
			int	len;

			if (cur != NULL) {
				cur->stop = stop + 1;
				*nxt = cur;
				nxt = &cur->next;
			}
			

			cur = XtNew(HelpInfo);
			cur->index = idx++;
			cur->start = i + 1;
			cur->next = NULL;

			if (count != NULL)
				(*count)++;
		
			len = 0;
			for (j = 0; j <= depth; j++)
				len += strlen(parts[j]) + 1;
			cur->name = (char*)XtMalloc(len + 1);
			cur->name[0] = '\0';
			for (j = 0; j <= depth; j++) {
				strcat(cur->name, parts[j]);
				strcat(cur->name, ".");
			}
			cur->name[strlen(cur->name)-1] = '\0';
			cur->topic = (char*)XtMalloc(strlen(tp) + 7 + 2 * depth);
			for (j = 0; j < depth * 2; j++)
				cur->topic[j] = ' ';
			cur->topic[j] = '\0';
			strcat(cur->topic, tp);
			strcat(cur->topic, "  ");
		} else if (flg) {
			if (cur->start == i)
				cur->start++;
		}
	}

	if (cur != NULL) {
		cur->stop = i;
		*nxt = cur;
		nxt = &cur->next;
	}

	return head;
}


static void doneCB(Widget w, XtPointer lArg, XtPointer junk)
{
	LocalInfo	*l = (LocalInfo*)lArg;

	XtPopdown(l->shell);
}

static char *buildText(HelpInfo *cur)
{
	int		i, len;
	char		*txt;
	char		*tp, *cp;

	len = 0;
	for (i = cur->start; i < cur->stop; i++) 
		len += strlen(helpText[i]) + 2;
	
	tp = txt = (char*)XtCalloc(len + 8, sizeof(char));
	for (i = cur->start; i < cur->stop; i++) {
		/*
		**  Line of all whitespace is a paragraph break.
		*/
		for (cp = helpText[i]; *cp != '\0'; cp++)
			if (!isspace(*cp))
				break;
		if (*cp == '\0') {
			*tp++ = '\n';
			*tp++ = '\n';
			continue;
		}
		if (strncmp(helpText[i], "#NL", 3) == 0) {
			*tp++ = '\n';
			continue;
		}
		for (cp = helpText[i]; *cp != '\0'; *tp++ = *cp++);
		*tp++ = ' ';
	}
	*tp++ = '\n';
	*tp = '\0';

	return txt;
}

static void display(LocalInfo *l, HelpInfo *cur)
{
	char	*txt;
	float	self, top, shown;

	if (cur == NULL)
		cur = l->info;

	l->curTopic = cur->index;

	txt = buildText(cur);

	XtVaSetValues(l->textText, XtNstring, txt, NULL);
	XtVaSetValues(l->textTitle, XtNlabel, cur->topic, NULL);

	XtFree((XtPointer)txt);

	XtVaSetValues(l->nextButton, XtNsensitive, (cur->next != NULL), NULL);
	XtVaSetValues(l->prevButton, XtNsensitive, (cur->index != 0), NULL);

	XtCallActionProc(l->textText, "beginning-of-file", NULL, NULL, 0);

	/*
	**  Now position the scrollbar to be visible
	*/

	XtVaGetValues(l->scrollbar, XtNtopOfThumb, &top, 
				    XtNshown, &shown, 
				    NULL);
	self = (float)cur->index / (float)l->ninfo;
	if (self < top || self > top + shown) {
		static String	args[] = { "Forward" };
		top = self - shown / 2.0;
		if (top < 0.0)
			top = 0.0;
		if (top + shown > 1.0)
			top = 1.0 - shown;

		/*
		**  Scrollbar doesn't notify on a SetThumb()
		**    So we must do it
		*/
		XawScrollbarSetThumb(l->scrollbar, top, -1.0);
		XtCallCallbacks(l->scrollbar, XtNjumpProc, (XtPointer)&top);
	}
}

static void topicCB(Widget w, LocalInfo *l, XawListReturnStruct *list)
{
	HelpInfo	*cur = l->info;
	int		i;

	for (i = 0; i < list->list_index; i++)
		cur = cur->next;

	display(l, cur);
}
static void downCB(Widget w, LocalInfo *l, XtPointer junk)
{
	XtCallActionProc(l->textText, "next-page", NULL, NULL, 0);
}
static void upCB(Widget w, LocalInfo *l, XtPointer junk)
{
	XtCallActionProc(l->textText, "previous-page", NULL, NULL, 0);
}
static void prevCB(Widget w, LocalInfo *l, XtPointer junk)
{
	XawListReturnStruct	lrs;

	if (l->curTopic == 0)
		return;

	lrs.list_index = l->curTopic - 1;
	XawListHighlight(l->topicList, lrs.list_index);
	topicCB(w, l, &lrs);
}
static void nextCB(Widget w, LocalInfo *l, XtPointer junk)
{
	XawListReturnStruct	lrs;

	if (l->curTopic == l->ninfo - 1)
		return;

	lrs.list_index = l->curTopic + 1;
	XawListHighlight(l->topicList, lrs.list_index);
	topicCB(w, l, &lrs);
}

static LocalInfo *buildPopup(LocalInfo *l, Widget parent)
{
	Widget		shell, form;
	Widget		title;
	Widget		topicList, topicPort;
	Widget		textTitle, textList;
	Widget		done, prev, next;
	Widget		pgDown, pgUp;

	if (l == NULL) {
		HelpInfo	*cur;
		int		i;

		l = XtNew(LocalInfo);
		l->info = buildInfo(&l->ninfo);

		l->topics = (String *)XtCalloc(l->ninfo + 1, sizeof(String));
		for (i = 0, cur = l->info; cur != NULL; cur = cur->next, i++)
			l->topics[i] = cur->topic;
		l->topics[i] = NULL;
		l->shell = None;
	}

	if (l->shell != None)
		return l;

        shell = XtVaCreatePopupShell("helpDialog",
                        topLevelShellWidgetClass, GetShell(parent),
                        NULL);

        form = XtVaCreateManagedWidget("form",
                                formWidgetClass, shell,
                                XtNborderWidth, 0,
                                NULL);

	title = XtVaCreateManagedWidget("title",
				labelWidgetClass, form,
                                XtNborderWidth, 0,
				XtNtop, XtChainTop,
				XtNbottom, XtChainTop,
				XtNleft, XtChainLeft,
				XtNright, XtChainLeft,
				NULL);

	/*
	**
	*/
	topicPort = XtVaCreateManagedWidget("topicPort",
				viewportWidgetClass, form,
				XtNtop, XtChainTop,
				XtNbottom, XtChainBottom,
				XtNleft, XtChainLeft,
				XtNright, XtChainLeft,
				XtNfromVert, title,
				XtNallowVert, True,
				XtNforceBars, True,
				NULL);
	topicList = XtVaCreateManagedWidget("topic", 
				listWidgetClass, topicPort,
				XtNverticalList, True,
				XtNforceColumns, True,
				XtNdefaultColumns, 1,
				NULL);

	/*
	**
	*/
	textTitle = XtVaCreateManagedWidget("textTitle",
				labelWidgetClass, form,
                                XtNborderWidth, 0,
				XtNfromHoriz, topicPort,
				XtNfromVert, title,
				XtNleft, XtChainLeft,
				XtNright, XtChainRight,
				XtNtop, XtChainTop,
				XtNbottom, XtChainTop,
				XtNresize, False,
				NULL);

	textList = XtVaCreateManagedWidget("textText", 
				asciiTextWidgetClass, form,
				XtNwrap, XawtextWrapWord,
				XtNtop, XtChainTop,
				XtNbottom, XtChainBottom,
				XtNleft, XtChainLeft,
				XtNright, XtChainRight,
				XtNfromVert, textTitle,
				XtNfromHoriz, topicPort,
				XtNscrollVertical, XawtextScrollAlways,
				XtNdisplayCaret, False,
				NULL);
	{
		Widget	sink;
		int	tabs[10];
		int	i;

		for (i = 0; i < XtNumber(tabs); i++) 
			tabs[i] = i * TAB_DISTANCE;

		XtVaGetValues(textList, XtNtextSink, &sink, NULL);

		XawTextSinkSetTabs(sink, XtNumber(tabs), tabs);
	}

	/*
	**
	*/
	done = XtVaCreateManagedWidget("done", 
				commandWidgetClass, form,
				XtNfromVert, textList,
				XtNtop, XtChainBottom,
				XtNbottom, XtChainBottom,
				XtNleft, XtChainLeft,
				XtNright, XtChainLeft,
				NULL);
	next = XtVaCreateManagedWidget("next", 
				commandWidgetClass, form,
				XtNfromVert, textList,
				XtNfromHoriz, done,
				XtNtop, XtChainBottom,
				XtNbottom, XtChainBottom,
				XtNleft, XtChainLeft,
				XtNright, XtChainLeft,
				NULL);
	prev = XtVaCreateManagedWidget("prev", 
				commandWidgetClass, form,
				XtNfromVert, textList,
				XtNfromHoriz, next,
				XtNtop, XtChainBottom,
				XtNbottom, XtChainBottom,
				XtNleft, XtChainLeft,
				XtNright, XtChainLeft,
				NULL);
	pgDown = XtVaCreateManagedWidget("down", 
				commandWidgetClass, form,
				XtNfromVert, textList,
				XtNfromHoriz, topicPort,
				XtNtop, XtChainBottom,
				XtNbottom, XtChainBottom,
				XtNleft, XtChainLeft,
				XtNright, XtChainLeft,
				NULL);
	pgUp = XtVaCreateManagedWidget("up", 
				commandWidgetClass, form,
				XtNfromVert, textList,
				XtNfromHoriz, pgDown,
				XtNtop, XtChainBottom,
				XtNbottom, XtChainBottom,
				XtNleft, XtChainLeft,
				XtNright, XtChainLeft,
				NULL);

	l->shell     = shell;
	l->textText  = textList;
	l->textTitle = textTitle;
	l->topicPort = topicPort;
	l->topicList = topicList;
	l->nextButton = next;
	l->prevButton = prev;
	l->scrollbar  = XtNameToWidget(topicPort, "vertical");

	XawListChange(topicList, l->topics, 0, 0, True);

	XtAddCallback(topicList, XtNcallback, (XtCallbackProc)topicCB, (XtPointer)l);
	XtAddCallback(pgDown, XtNcallback, (XtCallbackProc)downCB, (XtPointer)l);
	XtAddCallback(pgUp, XtNcallback, (XtCallbackProc)upCB, (XtPointer)l);
	XtAddCallback(next, XtNcallback, (XtCallbackProc)nextCB, (XtPointer)l);
	XtAddCallback(prev, XtNcallback, (XtCallbackProc)prevCB, (XtPointer)l);

	XtAddCallback(done, XtNcallback, doneCB, (XtPointer)l);
	AddDestroyCallback(shell, (void (*)(Widget, void *, XEvent *))doneCB, (XtPointer)l);

	return l;
}

void HelpDialog(Widget parent, String name)
{
	static LocalInfo	*l = NULL;
	int			i;
	HelpInfo		*cur;

	l = buildPopup(l, toplevel);

	for (i = 0, cur = l->info; cur != NULL; cur = cur->next, i++)
		if (strcmp(name, cur->name) == 0)
			break;

	if (!XtIsRealized(l->shell))
		display(l, cur);

	XawListHighlight(l->topicList, cur == NULL ? 0 : i);

	XtPopup(l->shell, XtGrabNone);

	display(l, cur);
	XMapRaised(XtDisplay(l->shell), XtWindow(l->shell));
}

void HelpTextOutput(FILE *fd, String name)
{
	char		*txt;
	int		col, wlen, i;
	char		*tp, *cp, *wstart;
	HelpInfo	*head, *cur;

	head = buildInfo(NULL);

	for (i = 0, cur = head; cur != NULL; cur = cur->next, i++)
		if (strcmp(name, cur->name) == 0)
			break;

	if (cur == NULL)
		return;

	txt = buildText(cur);

	col = wlen = 0;
	for (tp = txt; *tp != '\0'; tp++) {
		if (isspace(*tp) || *tp == '\n') {
			for (i = 0; i < wlen; i++, wstart++)
				putc(*wstart, fd);
			col += wlen;
			wlen = 0;
			if (*tp == '\t') {
				do {
					putc(' ', fd);
					col++;
				} while (col % TAB_DISTANCE != 0);
			} else if (*tp == '\n') {
				putc(*tp, fd);
				col = 0;
			} else {
				putc(*tp, fd);
				col++;
			}

			if (col > 75) {
				putc('\n', fd);
				col = 0;
			}
		} else if (wlen != 0) {
			if (col != 0 && (wlen + col > 75)) {
				putc('\n', fd);
				col = 0;
			}
			wlen++;
		} else {
			wlen = 1;
			wstart = tp;
		}
	}
	for (i = 0; i < wlen; i++, wstart++)
		putc(*wstart, fd);
	if (col != 0)
		putc('\n', fd);

	XtFree((XtPointer)txt);
}

static void helpAction(Widget w, XEvent *event, String *prms, Cardinal *nprms)
{
	if (*nprms != 1) {
		fprintf(stderr,"Help called with wrong number of params\n");
		return;
	}

	HelpDialog(w, prms[0]);
}

void HelpInit(Widget top)
{
        static XtActionsRec     act = { "PaintHelp", (XtActionProc)helpAction };

        XtAppAddActions(XtWidgetToApplicationContext(top), &act, 1);

	toplevel = top;
}
