/*
 *	Help system
 *	Copyright
 *		(C) 1992 Joseph H. Allen
 *		(C) 2001 Marek 'Marx' Grac
 *
 *	This file is part of JOE (Joe's Own Editor)
 */
#include "types.h"

struct help {
	char	*text;		/* help text with attributes */
	int	lines;		/* number of lines */
	struct help	*prev;		/* previous help screen */
	struct help	*next;		/* nex help screen */
	char	*name;		/* context name for context sensitive help */
};

#define NOT_ENOUGH_MEMORY -11

int bg_help;	/* Background color for help */

struct help *help_actual = NULL;	/* actual help screen */
struct help *help_ptr = NULL;		/* build pointer */

/*
 * Process help file
 * Returns new line number
 */

int help_init(JFILE *fd,char *bf,int line)
{
	char buf[1024];			/* input buffer */

	struct help *tmp;
	ptrdiff_t bfl;				/* buffer length */
	ptrdiff_t hlpsiz, hlpbsz;		/* number of used/allocated bytes for tmp->text */
	char *tempbuf;

	if (bf[0] == '{') {			/* start of help screen */
		tmp = (struct help *) joe_malloc(SIZEOF(struct help));

		tmp->text = NULL;
		tmp->lines = 0;
		hlpsiz = 0;
		hlpbsz = 0;
		tmp->name = vsncpy(NULL, 0, sz(bf + 1) - 1); /* -1 kill the \n */

		while ((jfgets(buf, SIZEOF(buf), fd)) && (buf[0] != '}')) {
			++line;
			bfl = zlen(buf);
			if (hlpsiz + bfl > hlpbsz) {
				if (tmp->text) {
					tempbuf = (char *)joe_realloc(tmp->text, hlpbsz + bfl + 1024);
					tmp->text = tempbuf;
				} else {
					tmp->text = (char *)joe_malloc(bfl + 1024);
					tmp->text[0] = 0;
				}
				hlpbsz += bfl + 1024;
			}
			mcpy(tmp->text + hlpsiz, buf, bfl);
			hlpsiz += bfl;
			++tmp->lines;
		}
		tmp->prev = help_ptr;
		tmp->next = NULL;
		if (help_ptr) {
			help_ptr->next = tmp;
		} else {
			help_actual = tmp;
		}
		help_ptr = tmp;
		if (buf[0] == '}') {		/* set new help screen as actual one */
			++line;
		} else {
			logerror_1(joe_gettext(_("\n%d: EOF before end of help text\n")), line);
		}
	}
	return line;
}

/*
 * Find context help - find help entry with the same name
 */

static struct help *find_context_help(const char *name)
{
	struct help *tmp = help_actual;

	while (tmp->prev != NULL)	/* find the first help entry */
		tmp = tmp->prev;

	while (tmp != NULL && zcmp(tmp->name, name) != 0)
		tmp = tmp->next;

	return tmp;
}

int help_is_utf8;

/*
 * Display help text
 */
void help_display(Screen *t)
{
	const char *str;
	int y, x, c, z;
	int atr = BG_COLOR(bg_help);

	if (help_actual) {
		str = help_actual->text;
	} else {
		str = NULL;
	}

	for (y = skiptop; y != t->wind; ++y) {
		if (t->t->updtab[y]) {
			const char *start = str, *eol;
			ptrdiff_t width=0;
			ptrdiff_t nspans=0;
			ptrdiff_t spanwidth;
			ptrdiff_t spancount=0;
			ptrdiff_t spanextra;
			ptrdiff_t len;

			eol = zchr(str, '\n');

			/* First pass: count no. springs \| and determine minimum width */
			while(*str && *str!='\n') {
				if (*str == '\\') {
					++str;
					switch(*str) {
						case 'i':
						case 'I':
						case 'u':
						case 'U':
						case 'd':
						case 'D':
						case 'b':
						case 'B':
						case 'f':
						case 'F':
						case 's':
						case 'S':
						case 'z':
						case 'Z':
							++str;
							break;
						case '|':
							++str;
							++nspans;
							break;
						case 0:
							break;
						default:
							++str;
							++width;
					}
				} else {
					len = eol - str;
					if (help_is_utf8)
						c = utf8_decode_fwrd(&str, &len);
					else {
						c = *(const unsigned char *)str++;
						--len;
					}
					width += joe_wcwidth((help_is_utf8 ? 1 : !!locale_map->type), c);
				}
			}
			str = start;
			/* Now calculate span width */
			if (width >= t->w || nspans==0) {
				spanwidth = 0;
				spanextra = nspans;
			} else {
				spanwidth = (t->w - width)/nspans;
				spanextra = nspans - (t->w - width - nspans*spanwidth);
			}
			/* Second pass: display text */
			for (x = 0; x != t->w; ++x) {
				if (*str == '\n' || !*str) {
					if (eraeol(t->t, x, y, BG_COLOR(bg_help))) {
						return;
					} else {
						break;
					}
				} else {
					if (*str == '\\') {
						switch (*++str) {
						case '|':
							++str;
							for (z=0;z!=spanwidth;++z)
								outatr((help_is_utf8 ? utf8_map : locale_map),t->t,t->t->scrn+x+y*t->w+z,t->t->attr+x+y*t->w+z,x+z,y,' ',atr);
							if (spancount++ >= spanextra) {
								outatr((help_is_utf8 ? utf8_map : locale_map),t->t,t->t->scrn+x+y*t->w+z,t->t->attr+x+y*t->w+z,x+z,y,' ',atr);
								++z;
							}
							x += z-1;
							continue;
						case 'i':
						case 'I':
							atr ^= INVERSE;
							/* Take status line colors for inverse */
							atr = (atr & ~(BG_MASK | FG_MASK)) | ((atr & INVERSE ? bg_stalin : bg_menu) & (BG_MASK | FG_MASK));
							++str;
							--x;
							continue;
						case 'u':
						case 'U':
							atr ^= UNDERLINE;
							++str;
							--x;
							continue;
						case 'd':
						case 'D':
							atr ^= DIM;
							++str;
							--x;
							continue;
						case 'b':
						case 'B':
							atr ^= BOLD;
							++str;
							--x;
							continue;
						case 'l':
						case 'L':
							atr ^= ITALIC;
							++str;
							--x;
							continue;
						case 'f':
						case 'F':
							atr ^= BLINK;
							++str;
							--x;
							continue;
						case 'z':
						case 'Z':
							atr ^= DOUBLE_UNDERLINE;
							++str;
							--x;
							continue;
						case 's':
						case 'S':
							atr ^= CROSSED_OUT;
							++str;
							--x;
							continue;
						case 0:
							--x;
							continue;
						}
					}
					len = eol - str;
					if (help_is_utf8)
						c = utf8_decode_fwrd(&str, &len);
					else {
						c = *(const unsigned char *)str++;
						--len;
					}

					outatr((help_is_utf8 ? utf8_map : locale_map),
					       t->t, t->t->scrn + x + y * t->w,
				       	       t->t->attr + x + y * t->w, x, y,
					       c, atr);
					x += (joe_wcwidth((help_is_utf8 ? 1 : !!locale_map->type), c) - 1);
				}
			}
			atr = BG_COLOR(bg_help);
			t->t->updtab[y] = 0;
			outatr_complete(t->t);
		}

		while (*str && *str != '\n')
			++str;
		if (*str == '\n')
			++str;
	}
}

/*
 * Show help screen
 */
int help_on(Screen *t)
{
	if (help_actual) {
		t->wind = help_actual->lines + skiptop;
		if ((t->h - t->wind) < FITMIN) {
			t->wind = t->h - FITMIN;
		}
		if (t->wind <= skiptop) {
			t->wind = skiptop;
			return -1;
		}
		wfit(t);
		msetI(t->t->updtab + skiptop, 1, t->wind - skiptop);
		return 0;
	} else {
		return -1;
	}
}

/*
 * Hide help screen
 */
static void help_off(Screen *t)
{
	t->wind = skiptop;
	wfit(t);
}

/*
 * Show/hide current help screen
 */
int u_help(W *w, int k)
{
	struct help *new_help;

	if (w->huh && (new_help = find_context_help(w->huh)) != NULL) {
		if (help_actual != new_help) {
			if (w->t->wind != skiptop)
				help_off(w->t);
			help_actual = new_help;		/* prepare context help */
		}
	}
	if (w->t->wind == skiptop) {
		return help_on(w->t);			/* help screen is hidden, so show the actual one */
	} else {
		help_off(w->t);				/* hide actual help screen */
		return 0;
	}
}

/*
 * Show next help screen (if it is possible)
 */
int u_help_next(W *w, int k)
{
	if (help_actual && help_actual->next) {		/* is there any next help screen? */
		if (w->t->wind != skiptop) {
			help_off(w->t);			/* if help screen was visible, then hide it */
		}
		help_actual = help_actual->next;	/* change to next help screen */
		return help_on(w->t);			/* show actual help screen */
	} else {
		return -1;
	}
}

/*
 * Show previous help screen (if it is possible)
 */
int u_help_prev(W *w, int k)
{
	if (help_actual && help_actual->prev) {		/* is there any previous help screen? */
		if (w->t->wind != skiptop)
			help_off(w->t);			/* if help screen was visible, then hide it */
		help_actual = help_actual->prev;	/* change to previous help screen */
		return help_on(w->t);			/* show actual help screen */
	} else {
		return -1;
	}
}
