/* $Id: spellchecker.cpp,v 1.12 2004/01/02 04:05:37 fesnel Exp $ */

 /*
 * Ispell spellchecker front end for XFMail.
 *
 * Parts of this file are based upon the LyX frontend to ispell.
 * LyX is Copyright (C) 1995 by Matthias Ettrich, 1995-1997 LyX Team
 * and distributed under the terms of the GNU General Public License.
 */

/*******************************************************************************
 *   This program is part of the XFMail email client.                          *
 *                                                                             *
 *   Copyright : (C) 1995-1998 Gennady B. Sorokopud (gena@NetVision.net.il)    *
 *               (C) 1995 Ugen. J. S. Antsilevich (ugen@latte.worldbank.org)   *
 *               (C) 1998-2004 by the Archimedes Project                       *
 *                   http://sourceforge.net/projects/archimedes                *
 *                                                                             *
 *             --------------------------------------------                    *
 *                                                                             *
 *   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 2 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, write to the Free Software               *
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307, USA.  *
 *                                                                             *
 *   Additional Permission granted:                                            *
 *                                                                             *
 *   This program is designed to use the XForms library, so we consider        *
 *   permission to link to that non-GPL-compatible library is implicit.        *
 *   However, in case this is not considered so, we explicitly state:          *
 *                                                                             *
 *   "As a special exception, the Archimedes Project, with the permission      *
 *    of all earlier copyright holders, formally gives permission to link      *
 *    this program with the XForms library, and distribute the resulting       *
 *    executable without the source code for XForms in the source              *
 *    distribution".                                                           *
 *                                                                             *
 ******************************************************************************/

#include <glib.h>

#include <fmail.h>
#include <umail.h>
#include <choose_folder.h>
#include <cfgfile.h>
#include "configform.h"
#include <fl_edit.h>
#ifdef HAVE_SYS_SELECT_H
	#include <sys/select.h>
#endif
#include "dialogs.h"
#include "spellchecker.h"
#ifdef HAVE_REGCOMP
	#include <regex.h>
#else
	#include "../regex/regex.h"
#endif

extern cfgfile Config;

static FD_config_spell *fd_spell_options;
static FD_spell_check *fd_spell_check = NULL;

static FL_OBJECT *textobj;
static FL_FORM *msg_form;

/* file handles for communication with ispell */
static FILE *out = NULL, *in = NULL;

/* set to -1 in sighandler if ispell terminates */
static int speller_alive;

/*
 * Display error messages and close pipes, if ispell terminates.
 * Set speller_alive, so that other routines can check if ispell is still
 * alive.
 */
void spell_checker_cleanup(proc_info * pinfo) {
	fd_set rdfs;
	struct timeval tv;
	char buf[1024], *p;
	ssize_t r;

	FD_ZERO(&rdfs);
	FD_SET(pinfo->fd_err[0], &rdfs);
	tv.tv_sec = 0;
	tv.tv_usec = 0;

	if(pinfo->status != 0)
#ifdef  HPUX9
		if(select
		   (pinfo->fd_err[0] + 1, (int *) &rdfs, (int *) NULL,
			(int *) NULL, &tv) > 0) {
#else
		if(select(pinfo->fd_err[0] + 1, &rdfs, NULL, NULL, &tv) > 0) {
#endif
			if ((r = read(pinfo->fd_err[0], buf, sizeof(buf) - 1)) > 0) {
				buf[r] = 0;
				if((p = strchr(buf, '\n')))
					*p = '\0';
			} else
				sprintf(buf, "Speller terminated");
			display_msg(MSG_WARN, "Ispell", buf);
		}

	if(pinfo->fd_in[1] > 0)
		close(pinfo->fd_in[1]);
	if(pinfo->fd_out[0] > 0)
		close(pinfo->fd_out[0]);
	if(pinfo->fd_err[0] > 0)
		close(pinfo->fd_err[0]);

	speller_alive = -1;
	return;
}

/*
 * create ispell process
 * returns -1 on error, 0 on success
 */
static int spell_checker_setup(char *lang) {
	char buf[1024], *p;
	int res;
	proc_info pinfo;
	ssize_t r;

	init_pinfo(&pinfo);
	pinfo.handle = spell_checker_cleanup;
	pinfo.fd_in[0] = pinfo.fd_out[0] = pinfo.fd_err[0] = 0;
	res = Config.getInt("ispell_use_esc_chars", 0);
	snprintf(buf, sizeof(buf), "%s -a %s%s %s %s%s%s %s%s %s",
			 Config.get("ispell_command", _PATH_ISPELL).c_str(),
			 strlen(lang) ? "-d " : "", lang, Config.getInt("ispell_accept_compound",
															0) ? "-C" :
			 "-B", res ? "-w \"" : "", res ? Config.get("ispell_escchars", "").c_str() : "",
			 res ? "\"" : "", (res =
							   Config.getInt("ispell_use_pers_dict",
											 0)) ? "-p " : "",
			 res ? Config.get("ispell_dictionary", "").c_str() : "", Config.getInt("ispell_use_input_encoding", 0) ?  "-T latin1" : "");
	free(lang);
	speller_alive = 0;
	if(exec_child(buf, &pinfo) == -1) {
		if((r = read(pinfo.fd_err[0], buf, sizeof(buf) - 1)) > 0) {
			buf[r] = '\0';
			if((p = strchr(buf, '\n')) != NULL)
				*p = '\0';
		} else
			sprintf(buf, "Can not execute");
		display_msg(MSG_WARN, "Ispell", buf);
		spell_checker_cleanup(&pinfo);
		return -1;
	}

	in = fdopen(pinfo.fd_out[0], "r");
	out = fdopen(pinfo.fd_in[1], "w");

	check_extprocs();
	if(speller_alive == -1)
		return -1;

	if(!fgets(buf, 1024, in))  /* Read ispells identification message */
		return -1;      /* no identification => error */
	fputs("!\n", out);      /* Terse mode (silently accept correct words) */
	fflush(out);
	return 0;
}

/*
 * Configuration
 */
void spell_checker_options_update(int set_default, FD_config_spell * form) {
	fd_spell_options = form;
	fl_set_input(fd_spell_options->ispell_prg, Config.get("ispell_command", _PATH_ISPELL, set_default).c_str());
	fl_set_input(fd_spell_options->altlang_input, Config.get("ispell_language", "", set_default).c_str());
	fl_set_input(fd_spell_options->perdict_input, Config.get("ispell_dictionary", "", set_default).c_str());
	fl_set_input(fd_spell_options->esc_chars_input, Config.get("ispell_escchars", "", set_default).c_str());
	fl_set_button(fd_spell_options->altlang, Config.getIntDefault("ispell_use_alt_lang", 0, set_default));
	fl_set_button(fd_spell_options->buflang,
				  !fl_get_button(fd_spell_options->altlang));
	fl_set_button(fd_spell_options->perdict,
				  Config.getIntDefault("ispell_use_pers_dict",
									   0, set_default));
	fl_set_button(fd_spell_options->esc_chars,
				  Config.getIntDefault("ispell_use_esc_chars",
									   0, set_default));
	fl_set_button(fd_spell_options->compounds,
				  Config.getIntDefault("ispell_accept_compound",
									   0, set_default));
	fl_set_button(fd_spell_options->inputenc,
				  Config.getIntDefault("ispell_use_input_encoding", 0,
									   set_default));
	fl_set_button(fd_spell_options->citation,
				  Config.getIntDefault("ispell_ignore_citation", 0,
									   set_default));
}
void handle_spell_input(FD_config_spell * form) {
	fd_spell_options = form;
	Config.set("ispell_command",
					 (char *) fl_get_input(fd_spell_options->ispell_prg));
	Config.set("ispell_language",
					 (char *) fl_get_input(fd_spell_options->
										   altlang_input));
	Config.set("ispell_dictionary",
					 (char *) fl_get_input(fd_spell_options->
										   perdict_input));
	Config.set("ispell_escchars",
					 (char *) fl_get_input(fd_spell_options->
										   esc_chars_input));
	Config.set("ispell_use_alt_lang",
				  fl_get_button(fd_spell_options->altlang));
	Config.set("ispell_use_pers_dict",
				  fl_get_button(fd_spell_options->perdict));
	Config.set("ispell_accept_compound",
				  fl_get_button(fd_spell_options->compounds));
	Config.set("ispell_use_esc_chars",
				  fl_get_button(fd_spell_options->esc_chars));
	Config.set("ispell_use_input_encoding",
				  fl_get_button(fd_spell_options->inputenc));
	Config.set("ispell_ignore_citation",
				  fl_get_button(fd_spell_options->citation));
}
void spell_conf(int set_default, FD_config_spell * form) {
	fd_spell_options = form;
	spell_checker_options_update(set_default, form);
	fl_set_focus_object(fd_spell_options->config_spell,
						fd_spell_options->altlang_input);
}  /*
* spell checking
*/
typedef struct ispell_result {
	int count;
	char *reply;
	char **suggestion;
}
ispell_result;

/*
 * Send word to ispell and parse reply;
 * returns NULL, if ispell has terminated,
 * else structure with ispell reply result.
 */
static struct ispell_result *spellcheck_word(char *word) {
	struct ispell_result *result;
	char buf[1024], *p;
	int i;

	check_extprocs();
	if(speller_alive == -1)
		return NULL;

	fputs("^", out);
	fputs(word, out);
	fputs("\n", out);
	if(fflush(out) == EOF)
		return NULL;

	if(fgets(buf, 1024, in) == NULL)
		return NULL;

	result = (ispell_result *) calloc(sizeof(ispell_result), 1);
	result->count = -1;     /* "-1" indicates a correct word */

	switch(*buf) {
		case '\n':      /* Number or when in terse mode: no problems, no next line */
			break;
		case '&':       /* "& <original> <count> <offset>: <miss>, <miss>, ..." */
			result->reply = strdup(buf);
			p = strchr(result->reply + 2, ' ');
			sscanf(p, "%d", &result->count);
			if(result->count)
				result->suggestion =
				(char **) calloc(sizeof(char *), result->count);
			p = strtok(strchr(p, ':') + 1, ",\n") + 1;
			for(i = 0; i < result->count; i++) {
				result->suggestion[i] = p;
				p = strtok(NULL, ",\n") + 1;
			}
		case '#':       /* Not found, no near misses and guesses */
		case '?':       /* Not found, and no near misses, but guesses */
			if(result->count == -1)    /* did we run through "case '&'" ? */
				result->count = 0;
		case '*':       /* Word found */
		case '+':       /* Word found through affix removal */
		case '-':       /* Word found through compound formation */
			/* we have to read one more line from ispell */
			check_extprocs();
			*buf = '\0';
			while((*buf != '\n') && (speller_alive != -1))
				fgets(buf, 1024, in);
	}

	return result;
}

static void spellcheck_delete_result(ispell_result * result) {
	if(result->reply)
		free(result->reply);
	if(result->suggestion)
		free(result->suggestion);
	free(result);
}

static int spell_next_word() {
	ispell_result *result = NULL;
	char *word;
	int citation = Config.getInt("ispell_ignore_citation", 0);
	int i;
	do {
		if(result)
			spellcheck_delete_result(result);
		if(!
		   (word =
			fl_textedit_get_nextword(textobj,
									 citation ? is_cited_line : NULL))) {
			fl_trigger_object(fd_spell_check->done);
			return -1;
		}

		if(!(result = spellcheck_word(word))) {
			free(word);
			fl_trigger_object(fd_spell_check->done);
			return -1;
		}
	} while(result->count == -1);

	fl_set_object_label(fd_spell_check->text, word);
	if(result->count >= 0) {
		fl_freeze_form(fd_spell_check->spell_check);
		fl_clear_browser(fd_spell_check->browser);
		for(i = 0; i < result->count; i++)
			fl_add_browser_line(fd_spell_check->browser,
								result->suggestion[i]);
		if(result->count) {
			fl_set_input(fd_spell_check->input, result->suggestion[0]);
			fl_select_browser_line(fd_spell_check->browser, 1);
		} else
			fl_set_input(fd_spell_check->input, word);
		fl_unfreeze_form(fd_spell_check->spell_check);
	}

	spellcheck_delete_result(result);
	free(word);

	return 0;
}

void spell_check_hide_form() {
	if(!fd_spell_check)
		return;

	fl_hide_form(fd_spell_check->spell_check);
	fl_free_form(fd_spell_check->spell_check);
	fl_free(fd_spell_check);
	fd_spell_check = NULL;
	textobj = NULL;
	fl_activate_form(msg_form);

	return;
}

/*
 * Don't kill main application in case the window cancel button was
 * pressed. Registered via fl_set_form_atclose().
 */
int cancel_box_cb(FL_FORM * obj, void *data) {
	fl_trigger_object(fd_spell_check->done);
	return FL_IGNORE;
}

void spell_check_replace_cb(FL_OBJECT * obj, long data) {
	fl_textedit_replace_sel(textobj,
							(char *) fl_get_input(fd_spell_check->input));
	spell_next_word();
}

void spell_check_browser_cb(FL_OBJECT * obj, long data) {
	int line;

	if((line = fl_get_browser(fd_spell_check->browser)) > 0)
		fl_set_input(fd_spell_check->input,
					 fl_get_browser_line(fd_spell_check->browser, line));
}

void spell_check_ignore_cb(FL_OBJECT * obj, long data) {
	spell_next_word();
}

void spellcheck_accept_cb(FL_OBJECT * obj, long data) {
	if(out) {
		fputs("@", out);
		fputs(fd_spell_check->text->label, out);
		fputs("\n", out);
		fflush(out);
	}

	spell_next_word();
}

void spellcheck_insert_cb(FL_OBJECT * obj, long data) {
	if(out) {
		fputs("*", out);
		fputs(fd_spell_check->text->label, out);
		fputs("\n", out);
		fflush(out);
	}

	spell_next_word();
}

void spell_check_options_cb(FL_OBJECT * obj, long data) {
	xfm_config(6);
}

void show_spell_checker(FL_OBJECT * tobj, FL_FORM * form) {
	char *lang = const_cast <char *>(Config.getInt("ispell_use_alt_lang", 0) ? Config.get("ispell_language", "").c_str() : ""); /* ispell is in action */
	if(fd_spell_check) {
		display_msg(MSG_WARN, "spelling check", "ispell is busy");
		return;
	}

	if(spell_checker_setup(strdup(lang)) == -1)
		return;

	textobj = tobj;
	msg_form = form;
	fl_deactivate_form(msg_form);
	fd_spell_check = create_form_spell_check();
	fl_set_form_atclose(fd_spell_check->spell_check, cancel_box_cb, NULL);

	fl_set_browser_dblclick_callback(fd_spell_check->browser,
									 spell_check_replace_cb, 0);
	fl_clear_browser(fd_spell_check->browser);

	fl_show_form(fd_spell_check->spell_check, FL_PLACE_MOUSE,
				 FL_FULLBORDER, "Spellchecker");
	if(spell_next_word() == 0)
		fl_do_only_forms();

	if(out) {
		fputs("#\n", out);
		fclose(out);
		out = NULL;
	}

	if(in) {
		fclose(in);
		in = NULL;
	}

	spell_check_hide_form();
	fl_activate_form(msg_form);
	return;
}

int is_cited_line(char *line) {
	char *mname, errbuf[64];
	char *prefix = const_cast<char *>(Config.get("prefix", ">").c_str());
	int regerr;
	if(!strncmp(line, prefix, strlen(prefix)))
		return 1;
	else {
		mname = line;
		if((*mname == ' ') || (*mname == '\t'))
			mname++;
		if((*mname == ':') || (*mname == '>') || (*mname == '|'))
			return 1;
#ifdef HAVE_REGCOMP
		else {
			regex_t preg;

			if((regerr = regcomp(&preg, "^[ \t]*[a-z0-9]*>",
								 REG_EXTENDED | REG_ICASE)) != 0) {
				regerror(regerr, &preg, errbuf, sizeof(errbuf));
				display_msg(MSG_WARN, "regular expression", "%s", errbuf);
				return 0;
			}

			if(regexec(&preg, mname, 0, 0, 0) == 0) {
				regfree(&preg);
				return 1;
			}
			regfree(&preg);
		}
#endif
	}
	return 0;
}
