
/******************************************************************************
* MODULE     : ispell.cpp
* DESCRIPTION: interface with the ispell spell checker
* COPYRIGHT  : (C) 1999  Joris van der Hoeven
*******************************************************************************
* This software falls under the GNU general public license version 3 or later.
* It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
* in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
******************************************************************************/

#include "Ispell/ispell.hpp"
#include "file.hpp"
#include "resource.hpp"
#include "tm_link.hpp"
#include "convert.hpp"
#include "locale.hpp"

string ispell_encode (string lan, string s);
string ispell_decode (string lan, string s);

/******************************************************************************
* The connection resource
******************************************************************************/

RESOURCE(ispeller);
struct ispeller_rep: rep<ispeller> {
  string  lan; // name of the session
  tm_link ln;  // the pipe
  bool unavailable; 

public:
  ispeller_rep (string lan);
  string start ();
  string retrieve ();
  void   send (string cmd);
private:
  bool connect_spellchecker (string cmd);
};
RESOURCE_CODE(ispeller);

/******************************************************************************
* Routines for ispellers
******************************************************************************/

ispeller_rep::ispeller_rep (string lan2): rep<ispeller> (lan2), lan (lan2) {}

// connect to spell checker with the desired dictionnary
string
ispeller_rep::start () {
    if (!is_nil (ln)) { 
       if (ln->alive) return "ok";
       if (unavailable) return "Error: not available";
    }
    string cmd, err;
    string name = "";
    string locale = language_to_locale (lan);
    bool testdic = false;
    if (exists_in_path ("hunspell")) {
        cmd= "hunspell";
        name = cmd;
        cmd = cmd * " -a -i utf-8";
        if (locale != "") cmd = cmd * " -d " * locale;
        testdic = connect_spellchecker(cmd);
    }
    if ((name == "") || (!testdic))
      if (exists_in_path ("aspell")) {
        cmd= "aspell";
        name = cmd;
        cmd = cmd * " -a --encoding=utf-8";
        if (locale != "") cmd = cmd * " -l " * locale;
        testdic = connect_spellchecker(cmd);
      }
#ifdef OS_MINGW
    //look in "program files" for system-wide install (but not found in PATH)
    url u;
    bool testcmd;
    if ((name == "") || (!testdic)){
      u= url_system ("$PROGRAMFILES\\Hunspell\\bin\\hunspell.exe");
      testcmd = exists (u);
      if (!testcmd) {
          u= url_system ("$PROGRAMFILES(x86)\\Hunspell\\bin\\hunspell.exe");
          testcmd = exists (u);
          }
      if (testcmd) {
          cmd = as_string(u);
          name = "Hunspell";
          cmd = "\"" * cmd * "\" -a -i utf-8";
          if (locale != "") cmd = cmd * " -d " * locale;
          testdic = connect_spellchecker(cmd);
      }
    }
    if ((name == "") || (!testdic)) {
      u = url_system ("$PROGRAMFILES\\Aspell\\bin\\aspell.exe");
      testcmd = exists (u);
      if (!testcmd) {
        u= url_system ("$PROGRAMFILES(x86)\\Aspell\\bin\\aspell.exe");
        testcmd = exists (u);
        }
      if (testcmd)  {
        cmd = as_string(u);
        name = "Aspell";
        cmd = "\"" * cmd * "\" -a --encoding=utf-8";
        if (locale != "") cmd =  cmd * " -l " * locale;
        testdic = connect_spellchecker(cmd);
      }
    }
#endif
    if (name == "") {
        err = "Error: spellchecker not found in PATH (neither Aspell nor Hunspell) ";
        std_error << err << "\nCannot spellcheck\n";
        unavailable = true;
        return err;
        }
    if (!testdic) {
        err = "Error: no dictionary installed for " * lan * " (" * locale * ")";
        std_error << err << "\nThe corresponding text is not checked\n";
        unavailable = true;
        return err;
        }
    debug_spell << "running " << name << " with " << locale << " dictionary for " << lan << "\n";
    unavailable = false;
    return "ok";

}

bool
ispeller_rep::connect_spellchecker (string cmd) {
// establishes connection (absence of error means the required dictionnary is available)
// returns true when the spellchecker is up and running
  ln= make_pipe_link (cmd);
  string message= ln->start ();
  if (DEBUG_IO) debug_spell << "Received " << message << "\n";
  if (starts (message, "Error: ")) {
    if (ln->alive) ln->stop ();
    return false;
  }
  message= retrieve ();
  if (DEBUG_IO) debug_spell << "Received " << message << "\n";
  if (starts (message, "@(#)")) return true;
  else {
    if (ln->alive) ln->stop ();
    return false;
    }
}

string
ispeller_rep::retrieve () {
  string ret;
#ifdef OS_MINGW
  while ((ret != "\r\n") && (!ends (ret, "\r\n\r\n")) &&
	 ((!ends (ret, "\r\n")) || (!starts (ret, "@(#)"))))
#else
  while ((ret != "\n") && (!ends (ret, "\n\n")) &&
	 ((!ends (ret, "\n")) || (!starts (ret, "@(#)"))))
#endif
    {
      ln->listen (10000);
      string mess = ln->read (LINK_ERR);
      string extra= ln->read (LINK_OUT);
      if (mess  != "") io_error << "Spellchecker error: " << mess << "\n";
      if (extra == "") {
	ln->stop ();
	return "Error: spellchecker does not respond";
      }
      ret << extra;
    }
  return ispell_decode (lan, ret);
}

void
ispeller_rep::send (string cmd) {
  ln->write (ispell_encode (lan, cmd) * "\n", LINK_IN);
}

/******************************************************************************
* Internationalization
******************************************************************************/

string
ispell_encode (string lan, string s) {
  (void) lan;
  return cork_to_utf8 (s);
}

string
ispell_decode (string lan, string s) {
  (void) lan;
  return utf8_to_cork (s);
}

/******************************************************************************
* Subroutines
******************************************************************************/

static tree
parse_ispell (string s) {
#ifdef OS_MINGW
  while (ends (s, "\r\n")) s= s (0, N(s)-2);
#else
  while (ends (s, "\n")) s= s (0, N(s)-1);
#endif
  bool flag= true;
  int i, j;
  tree t (TUPLE);
  for (i=0, j=0; j<N(s); j++)
    if (s[j]==':') flag= false;
    else if (((s[j]==' ') && (flag || (j==i) || (s[j-1]==':'))) || (s[j]==','))
      {
	if (j>i) t << s (i, j);
	i= j+1;
      }
  t << s (i, j);

  if (N(t) == 0) return tree (TUPLE, "0");
  if ((t[0] == "+") || (t[0] == "*") || (t[0] == "-")) return "ok";
  if ((N(t)>=4) && ((t[0] == "&") || (t[0]=="?"))) {
    tree u (TUPLE, t[2]);
    u << A (t (4, N (t)));
    return u;
  }
  return tree (TUPLE, "0");
}

static void
ispell_send (string lan, string s) {
  ispeller sc= ispeller (lan);
  if ((!is_nil (sc)) && sc->ln->alive) sc->send (s);
}

static string
ispell_eval (string lan, string s) {
  ispeller sc= ispeller (lan);
  if ((!is_nil (sc)) && sc->ln->alive) {
    sc->send (s);
    return sc->retrieve ();
  }
  return "";
}

/******************************************************************************
* Spell checking interface
******************************************************************************/

string
ispell_start (string lan) {
  if (DEBUG_IO) debug_spell << "Start " << lan << "\n";
  ispeller sc= ispeller (lan);
  if (is_nil (sc)) sc= tm_new<ispeller_rep> (lan);
  return sc->start ();
}

tree
ispell_check (string lan, string s) {
  if (DEBUG_IO) debug_spell << "Check " << s << "\n";
  ispeller sc= ispeller (lan);
  if (is_nil (sc) || (!sc->ln->alive)) {
    string message= ispell_start (lan);
    if (starts (message, "Error: ")) return message;
  }
  if (sc->unavailable) return "Error: unavailable";
  string ret_s= ispell_eval (lan, "^" * s);
  if (starts (ret_s, "Error: ")) return ret_s;
  return parse_ispell (ret_s);
}

void
ispell_accept (string lan, string s) {
  if (DEBUG_IO) debug_spell << "Accept " << s << "\n";
  ispell_send (lan, "@" * s);
}

void
ispell_insert (string lan, string s) {
  if (DEBUG_IO) debug_spell << "Insert " << s << "\n";
  ispell_send (lan, "*" * s);
}

void
ispell_done (string lan) {
  if (DEBUG_IO) debug_spell << "End " << lan << "\n";
  ispell_send (lan, "#");
}
