/*
 * Copyright (C) 2012-2016 Robin Haberkorn
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include 
#include 
#include 
#include 
#include 
#include 
#include "sciteco.h"
#include "string-utils.h"
#include "parser.h"
#include "qregisters.h"
#include "ring.h"
#include "interface.h"
#include "help.h"
namespace SciTECO {
HelpIndex help_index;
namespace States {
	StateGetHelp gethelp;
}
void
HelpIndex::load(void)
{
	gchar *lib_path;
	gchar *women_path;
	GDir *women_dir;
	const gchar *basename;
	if (G_LIKELY(min() != NULL))
		/* already loaded */
		return;
	lib_path = QRegisters::globals["$SCITECOPATH"]->get_string();
	women_path = g_build_filename(lib_path, "women", NIL);
	g_free(lib_path);
	women_dir = g_dir_open(women_path, 0, NULL);
	if (!women_dir) {
		g_free(women_path);
		return;
	}
	while ((basename = g_dir_read_name(women_dir))) {
		gchar *filename, *filename_tec;
		FILE *file;
		gchar buffer[1024];
		gchar *topic;
		if (!g_str_has_suffix(basename, ".woman"))
			continue;
		/*
		 * Open the corresponding SciTECO macro to read
		 * its first line.
		 */
		filename = g_build_filename(women_path, basename, NIL);
		filename_tec = g_strconcat(filename, ".tec", NIL);
		file = g_fopen(filename_tec, "r");
		g_free(filename_tec);
		if (!file) {
			/*
			 * There might simply be no support script for
			 * simple plain-text woman-pages.
			 * In this case we create a topic using the filename
			 * without an extension.
			 */
			topic = g_strndup(basename, strlen(basename)-6);
			set(topic, filename);
			g_free(topic);
			g_free(filename);
			continue;
		}
		/*
		 * Each womanpage script begins with a special comment
		 * header containing the position to topic index.
		 * Every topic will be on its own line and they are unlikely
		 * to be very long, so we can use fgets() here.
		 * NOTE: Since we haven't opened with the "b" flag,
		 * fgets() will translate linebreaks to LF even on
		 * MSVCRT (Windows).
		 */
		if (!fgets(buffer, sizeof(buffer), file) ||
		    !g_str_has_prefix(buffer, "!*")) {
			interface.msg(InterfaceCurrent::MSG_WARNING,
			              "Missing or invalid topic line in womanpage script \"%s\"",
			              filename);
			g_free(filename);
			continue;
		}
		/* skip opening comment */
		topic = buffer+2;
		do {
			gchar *endptr;
			tecoInt pos = strtoul(topic, &endptr, 10);
			gsize len;
			/*
			 * This also breaks at the last line of the
			 * header.
			 */
			if (*endptr != ':')
				break;
			/*
			 * Strip the likely LF at the end of the line.
			 */
			len = strlen(endptr)-1;
			if (G_LIKELY(endptr[len] == '\n'))
				endptr[len] = '\0';
			set(endptr+1, filename, pos);
		} while ((topic = fgets(buffer, sizeof(buffer), file)));
		fclose(file);
		g_free(filename);
	}
	g_dir_close(women_dir);
	g_free(women_path);
}
HelpIndex::Topic *
HelpIndex::find(const gchar *name)
{
	Topic *ret;
	/*
	 * The topic index contains printable characters
	 * only (to avoid having to perform string building
	 * on the topic terms to be able to define control
	 * characters).
	 * Therefore, we expand control characters in the
	 * look-up string to their printable forms.
	 */
	gchar *term = String::canonicalize_ctl(name);
	ret = (Topic *)RBTreeStringCase::find(term);
	g_free(term);
	return ret;
}
void
HelpIndex::set(const gchar *name, const gchar *filename, tecoInt pos)
{
	Topic *topic = new Topic(name, filename, pos);
	Topic *existing;
	existing = (Topic *)RBTree::find(topic);
	if (existing) {
		gchar *basename;
		if (!strcmp(existing->filename, filename)) {
			/*
			 * A topic with the same name already exists
			 * in the same file.
			 * For the time being, we simply overwrite the
			 * last topic.
			 * FIXME: Perhaps make it unique again!?
			 */
			existing->pos = pos;
			delete topic;
			return;
		}
		/* in another file -> make name unique */
		interface.msg(InterfaceCurrent::MSG_WARNING,
		              "Topic collision: \"%s\" defined in \"%s\" and \"%s\"",
		              name, existing->filename, filename);
		String::append(topic->name, ":");
		basename = g_path_get_basename(filename);
		String::append(topic->name, basename);
		g_free(basename);
	}
	RBTree::insert(topic);
}
/*
 * Command states
 */
/*$ "?" help
 * ?[topic]$ -- Get help for topic
 *
 * Look up  in the help index, opening
 * the corresponding womanpage as a buffer and scrolling
 * to the topic's position.
 * The help index is built when this command is first
 * executed, so the help system does not consume resources
 * when not used (e.g. in a batch-mode script).
 *
 * \*(ST's help documents must be installed in the
 * directory \fB$SCITECOPATH/women\fP, i.e. as part of
 * the standard library.
 * Each document consist of at least one plain-text file with
 * the extension \(lq.woman\(rq.
 * Optionally, a \*(ST script with the extension
 * \(lq.woman.tec\(rq can be installed alongside the
 * main document to define topics covered by this document
 * and set up styling.
 *
 * The beginning of the script must be a header of the form:
 * .EX
 * !*\fIposition\fP:\fItopic1\fP
 * \fIposition2\fP:\fItopic2\fP
 * \fI...\fP
 * *!
 * .EE
 * In other words it must be a \*(ST comment followed
 * by an asterisk sign, followed by the first topic which
 * is a buffer position, followed by a colon and the topic
 * string.
 * The topic string is terminated by the end of the line.
 * The end of the header is marked by a single \(lq*!\(rq.
 * Topic terms should be specified with printable characters
 * only (e.g. use Caret+A instead of CTRL+A).
 * When looking up a help term, control characters are
 * canonicalized to their printable form, so the term
 * \(lq^A\(rq is found both by Caret+A and CTRL+A.
 * Also, while topic terms are not case folded, lookup
 * is case insensitive.
 *
 * The rest of the script is not read by \*(ST internally
 * but should contain styling for the main document.
 * It is usually read by the standard library's lexer
 * configuration system when showing a womanpage.
 * If the \(lq.woman.tec\(rq macro is missing,
 * \*(ST will define a single topic for the document based
 * on the \(lq.woman\(rq file's name.
 *
 * The combination of plain-text document and script
 * is called a \(lqwomanpage\(rq because these files
 * are usually generated using \fBgroff\fP(1) with the
 * \fIgrosciteco\fP formatter and the \fIsciteco.tmac\fP
 * GNU troff macros.
 * When using womanpages generated by \fIgrosciteco\fP,
 * help topics can be defined using the \fBSCITECO_TOPIC\fP
 * Troff macro.
 * This flexible system allows \*(ST to access internal
 * and third-party help files written in plain-text or
 * with an arbitrary GNU troff macro package.
 * As all GNU troff documents are processed at build-time,
 * GNU troff is not required at runtime.
 *
 * The \fB?\fP command does not have string building enabled.
 */
void
StateGetHelp::initial(void)
{
	/*
	 * The help-index is populated on demand,
	 * so we start up quicker and batch mode does
	 * not depend on the availability of the standard
	 * library.
	 */
	help_index.load();
}
State *
StateGetHelp::done(const gchar *str)
{
	HelpIndex::Topic *topic;
	BEGIN_EXEC(&States::start);
	topic = help_index.find(str);
	if (!topic)
		throw Error("Topic \"%s\" not found", str);
	ring.undo_edit();
	/*
	 * ED hooks with the default lexer framework
	 * will usually load the styling SciTECO script
	 * when editing the buffer for the first time.
	 */
	ring.edit(topic->filename);
	/*
	 * Make sure the topic is visible.
	 * We do need undo tokens for this (even though
	 * the buffer is removed on rubout if the woman
	 * page is viewed first) since we might browse
	 * multiple topics in the same buffer without
	 * closing it first.
	 */
	interface.undo_ssm(SCI_GOTOPOS,
	                   interface.ssm(SCI_GETCURRENTPOS));
	interface.ssm(SCI_GOTOPOS, topic->pos);
	return &States::start;
}
} /* namespace SciTECO */