/* vi:set ts=4 sw=4:
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 */

/*
 * help.c: open a read-only window on the vim_help.txt file
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "option.h"

	void
do_help(arg)
	char_u		*arg;
{
	char_u	*fnamep;
	FILE	*helpfd;			/* file descriptor of help file */
	int		n;
	WIN		*wp;
	int		num_matches;
	char_u	**matches;
	int		need_free = FALSE;

	/*
	 * If an argument is given, check if there is a match for it.
	 */
	if (*arg != NUL)
	{
		n = find_help_tags(arg, &num_matches, &matches);
		if (num_matches == 0 || n == FAIL)
		{
			EMSG2("Sorry, no help for %s", arg);
			return;
		}

		/* The first match is the best match */
		arg = strsave(matches[0]);
		need_free = TRUE;
		FreeWild(num_matches, matches);
	}

	/*
	 * If there is already a help window open, use that one.
	 */
	if (!curwin->w_buffer->b_help)
	{
		for (wp = firstwin; wp != NULL; wp = wp->w_next)
			if (wp->w_buffer != NULL && wp->w_buffer->b_help)
				break;
		if (wp != NULL && wp->w_buffer->b_nwindows > 0)
			win_enter(wp, TRUE);
		else
		{
			/*
			 * There is no help buffer yet.
			 * Try to open the file specified by the "helpfile" option.
			 */
			fnamep = p_hf;
			if ((helpfd = fopen((char *)p_hf, READBIN)) == NULL)
			{
#if defined(MSDOS)
			/*
			 * for MSDOS: try the DOS search path
			 */
				fnamep = searchpath("vim_help.txt");
				if (fnamep == NULL ||
							(helpfd = fopen((char *)fnamep, READBIN)) == NULL)
				{
					smsg((char_u *)"Sorry, help file \"%s\" and \"vim_help.txt\" not found", p_hf);
					goto erret;
				}
#else
				smsg((char_u *)"Sorry, help file \"%s\" not found", p_hf);
				goto erret;
#endif
			}
			fclose(helpfd);

			if (win_split(0, FALSE) == FAIL)
				goto erret;
			
			if (curwin->w_height < p_hh)
				win_setheight((int)p_hh);

#ifdef RIGHTLEFT
			curwin->w_p_rl = 0;				/* help window is left-to-right */
#endif
			curwin->w_p_nu = 0;				/* no line numbers */

			/*
			 * open help file (do_ecmd() will set b_help flag, readfile() will
			 * set b_p_ro flag)
			 */
			(void)do_ecmd(0, fnamep, NULL, NULL, (linenr_t)0,
												   ECMD_HIDE + ECMD_SET_HELP);

			/* save the values of the options we change */
			vim_free(help_save_isk);
			help_save_isk = strsave(curbuf->b_p_isk);
			help_save_ts = curbuf->b_p_ts;

			/* accept all chars for keywords, except ' ', '*', '"', '|' */
			set_string_option((char_u *)"isk", -1,
											 (char_u *)"!-~,^*,^|,^\"", TRUE);
			curbuf->b_p_ts = 8;
			check_buf_options(curbuf);
			(void)init_chartab();		/* needed because 'isk' changed */
		}
	}

	restart_edit = 0;		/* don't want insert mode in help file */

	stuffReadbuff((char_u *)":ta ");
	if (arg != NULL && *arg != NUL)
		stuffReadbuff(arg);
	else
		stuffReadbuff((char_u *)"vim_help.txt");		/* go to the index */
	stuffcharReadbuff('\n');

erret:
	if (need_free)
		vim_free(arg);
}

/*
 * Return a heuristic indicating how well the given string matches.  The
 * smaller the number, the better the match.  This is the order of priorities,
 * from best match to worst match:
 *		- Match with least alpha-numeric characters is better.
 *		- Match with least total characters is better.
 *		- Match towards the start is better.
 * Assumption is made that the matched_string passed has already been found to
 * match some string for which help is requested.  webb.
 */
	int
help_heuristic(matched_string, offset)
	char_u	*matched_string;
	int		offset;				/* offset for match */
{
	int		num_letters;
	char_u	*p;

	num_letters = 0;
	for (p = matched_string; *p; p++)
		if (isalnum(*p))
			num_letters++;

	/*
	 * Multiply the number of letters by 100 to give it a much bigger
	 * weighting than the number of characters.
	 * If the match starts in the middle of a word, add 10000 to put it
	 * somewhere in the last half.
	 * If the match is more than 2 chars from the start, multiply by 200 to
	 * put it after matches at the start.
	 */
	if (isalnum(matched_string[offset]) && offset > 0 &&
										  isalnum(matched_string[offset - 1]))
		offset += 10000;
	else if (offset > 2)
		offset *= 200;
	return (int)(100 * num_letters + STRLEN(matched_string) + offset);
}

static int help_compare __ARGS((const void *s1, const void *s2));

/*
 * Compare functions for qsort() below, that checks the help heuristics number
 * that has been put after the tagname by find_tags().
 */
	static int
help_compare(s1, s2)
	const void	*s1;
	const void	*s2;
{
	char	*p1;
	char	*p2;

	p1 = *(char **)s1 + strlen(*(char **)s1) + 1;
	p2 = *(char **)s2 + strlen(*(char **)s2) + 1;
	return strcmp(p1, p2);
}

/*
 * Find all help tags matching "arg", sort them and return in matches[], with
 * the number of matches in num_matches.
 * We try first with case, and then ignoring case.  Then we try to choose the
 * "best" match from the ones found.
 */
	int
find_help_tags(arg, num_matches, matches)
	char_u		*arg;
	int			*num_matches;
	char_u		***matches;
{
	char_u	*s, *d;
	regexp	*prog;
	int		attempt;
	int		retval = FAIL;
	int		i;
	static char *(mtable[]) = {"*", "g*", "[*", "]*",
							   "/*", "/\\*", "/\\(\\)",
							   "?", ":?", "?<CR>"};
	static char *(rtable[]) = {"star", "gstar", "[star", "]star",
							   "/star", "/\\\\star", "/\\\\(\\\\)",
							   "?", ":?", "?<CR>"};

	reg_magic = p_magic;
	d = IObuff;				/* assume IObuff is long enough! */

	/*
	 * Recognize a few exceptions to the rule.  Some strings that contain '*'
	 * with "star".  Otherwise '*' is recognized as a wildcard.
	 */
	for (i = sizeof(mtable) / sizeof(char *); --i >= 0; )
	{
		if (STRCMP(arg, mtable[i]) == 0)
		{
			STRCPY(d, rtable[i]);
			break;
		}
	}

	if (i < 0)		/* no match in table, replace single characters */
	{
		for (s = arg; *s; ++s)
		{
			/*
			 * Replace "|" with "bar" and """ with "quote" to match the name of
			 * the tags for these commands.
			 * Replace "*" with ".*" and "?" with "." to match command line
			 * completion.
			 * Insert a backslash before '~', '$' and '.' to avoid their
			 * special meaning.
			 */
			if (d - IObuff > IOSIZE - 10)		/* getting too long!? */
				break;
			switch (*s)
			{
				case '|':	STRCPY(d, "bar");
							d += 3;
							continue;
				case '\"':	STRCPY(d, "quote");
							d += 5;
							continue;
				case '*':	*d++ = '.';
							break;
				case '?':	*d++ = '.';
							continue;
				case '$':
				case '.':
				case '~':	*d++ = '\\';
							break;
			}

			/*
			 * Replace "^x" by "CTRL-X". Don't do this for "^_" to make
			 * ":help i_^_CTRL-D" work.
			 */
			if (*s < ' ' || (*s == '^' && s[1] && s[1] != '_'))	/* ^x */
			{
				STRCPY(d, "CTRL-");
				d += 5;
				if (*s < ' ')
				{
					*d++ = *s + '@';
					continue;
				}
				++s;
			}
			else if (*s == '^')			/* "^" or "CTRL-^" or "^_" */
				*d++ = '\\';

			/*
			 * Insert a backslash before a backslash after a slash, for search
			 * pattern tags: "/\|" --> "/\\|".
			 */
			else if (s[0] == '\\' && s[1] != '\\' &&
												  *arg == '/' && s == arg + 1)
				*d++ = '\\';

			*d++ = *s;

			/*
			 * If tag starts with ', toss everything after a second '. Fixes
			 * CTRL-] on 'option'. (would include the trailing '.').
			 */
			if (*s == '\'' && s > arg && *arg == '\'')
				break;
		}
		*d = NUL;
	}

	reg_ic = FALSE;
	prog = vim_regcomp(IObuff);
	if (prog == NULL)
		return FAIL;

	/* First try to match with case, then without */
	for (attempt = 0; attempt < 2; ++attempt, reg_ic = TRUE)
	{
		*matches = (char_u **)"";
		*num_matches = 0;
		retval = find_tags(NULL, prog, num_matches, matches, TRUE, FALSE);
		if (retval == FAIL || *num_matches)
			break;
	}
	vim_free(prog);

#ifdef HAVE_QSORT
	/*
	 * Sort the matches found on the heuristic number that is after the
	 * tag name.  If there is no qsort, the output will be messy!
	 */
	qsort((void *)*matches, (size_t)*num_matches,
											  sizeof(char_u *), help_compare);
#endif
	return OK;
}
