/*
 * help.c       -- Help command
 *
 * Copyright (C) 2006-2010 Mikael Berthe <mikael@lilotux.net>
 * Copyright (C) 2009      Myhailo Danylenko <isbear@ukrpost.net>
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

/*
 * How it works
 *
 * Main calls help_init, that installs option guards. These guards do
 * nothing, but set help_dirs_stalled flag. When user issues help command,
 * it checks, if help_dirs_stalled flag is set, and if it is, it calls
 * init_help_dirs before performing help search.
 *
 * Options:
 *   lang       List of semicolon-separated language codes. If unset, will
 *              be detected from locale, with fallback to english.
 *   help_dirs  List of semicolon-seaparated directories, where search for
 *              help (in language subdirectories) will be performed.
 *              Defaults to DATA_DIR/mcabber/help.
 *   help_to_current  Print help to current buddy's buffer.
 *
 * XXX:
 *   Remove command list from hlp.txt and print detected list of all help
 *   topics?
 */

#include <glib.h>
#include <string.h>
#include <locale.h>
#include <sys/types.h>
#include <dirent.h>

#include "logprint.h"
#include "screen.h"
#include "hbuf.h"
#include "settings.h"
#include "utils.h"

static GSList   *help_dirs         = NULL;
static gboolean  help_dirs_stalled = TRUE;

void free_help_dirs(void)
{
  GSList *hel;

  for (hel = help_dirs; hel; hel = hel->next)
    g_free(hel->data);

  g_slist_free(help_dirs);

  help_dirs = NULL;
}

void dir_push_languages(const char *langs, const char *dir)
{
  const char *lstart = langs;
  const char *lend;
  char       *path   = expand_filename(dir);

  for (lend = strchr(lstart, ';'); lend; lend = strchr(lstart, ';')) {
    char *lang = g_strndup(lstart, lend - lstart);
    char *dir  = g_strdup_printf("%s/%s", path, lang);

    help_dirs = g_slist_append(help_dirs, dir);

    g_free(lang);
    lstart = lend + 1;
  }

  { // finishing element
    char *dir = g_strdup_printf("%s/%s", path, lstart);

    help_dirs = g_slist_append(help_dirs, dir);
  }

  g_free(path);
}

void init_help_dirs(void)
{
  const char *paths;
  const char *langs;
  char        lang[6];

  if (help_dirs)
    free_help_dirs();

  // initialize variables
  paths = settings_opt_get("help_dirs");
  if (!paths || !*paths)
#ifdef DATA_DIR
    paths = DATA_DIR "/mcabber/help";
#else
    paths = "/usr/local/share/mcabber/help;/usr/share/mcabber/help";
#endif

  langs = settings_opt_get("lang");

  if (!langs || !*langs) {
    char *locale = setlocale(LC_MESSAGES, NULL);

    // XXX crude method to distinguish between xx_XX xx xx@xxx
    // and C POSIX NULL etc.
    if (locale && isalpha(locale[0]) && isalpha(locale[1])
        && !isalpha(locale[2])) {
      lang[0] = locale[0];
      lang[1] = locale[1];

      if (lang[0] == 'e' && lang[1] == 'n')
        lang[2] = '\0';
      else {
        lang[2] = ';';
        lang[3] = 'e';
        lang[4] = 'n';
        lang[5] = '\0';
      }

      langs = lang;
    } else
      langs = "en";
  }

  { // parse
    const char *pstart = paths;
    const char *pend;

    for (pend = strchr(pstart, ';'); pend; pend = strchr(pstart, ';')) {
      char *path = g_strndup(pstart, pend - pstart);

      dir_push_languages(langs, path);

      g_free(path);
      pstart = pend + 1;
    }

    // last element
    dir_push_languages(langs, pstart);
  }

  help_dirs_stalled = FALSE;
}

static gboolean do_help_in_dir(const char *arg, const char *path, const char *jid)
{
  char       *fname;
  GIOChannel *channel;
  GString    *line;
  int         lines   = 0;

  if (arg && *arg)
    fname = g_strdup_printf("%s/hlp_%s.txt", path, arg);
  else
    fname = g_strdup_printf("%s/hlp.txt", path);

  channel = g_io_channel_new_file(fname, "r", NULL);

  if (!channel)
    return FALSE;

  line = g_string_new(NULL);

  while (TRUE) {
    gsize     endpos;
    GIOStatus ret;

    ret = g_io_channel_read_line_string(channel, line, &endpos, NULL);
    if (ret != G_IO_STATUS_NORMAL) // XXX G_IO_STATUS_AGAIN?
      break;

    line->str[endpos] = '\0';

    if (jid)
      scr_WriteIncomingMessage(jid, line->str, 0,
                               HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
    else
      scr_LogPrint(LPRINT_NORMAL, "%s", line->str);

    ++lines;
  }

  g_io_channel_unref(channel);

  g_string_free(line, TRUE);

  if (!lines)
    return FALSE;

  if (!jid) {
    scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
    scr_setattentionflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE,
                                   ROSTER_UI_PRIO_STATUS_WIN_MESSAGE, prio_max);
  }

  return TRUE;
}

void help_process(char *arg)
{
  gchar      *string;
  const char *jid    = NULL;
  gboolean    done   = FALSE;

  if (help_dirs_stalled)
    init_help_dirs();

  { // check input
    char *c;

    for (c = arg; *c; ++c)
      if (!isalnum(*c) && *c != '-' && *c != '_') {
        scr_LogPrint(LPRINT_NORMAL, "Wrong help expression, "
                     "it can contain only alphbetic, numeric"
                     " characters and symbols '-' and '_'.");
        return;
      }

    string = g_strdup(arg);
    mc_strtolower(string);
  }

  if (settings_opt_get_int("help_to_current") && CURRENT_JID)
    jid = CURRENT_JID;

  { // search
    GSList *hel;

    for (hel = help_dirs; hel && !done; hel = hel->next) {
      char *dir = (char *)hel->data;
      done = do_help_in_dir(string, dir, jid);
    }
  }

  if (!done && string && *string) { // match and print any similar topics
    GSList *hel;
    GSList *matches = NULL;

    for (hel = help_dirs; hel; hel = hel->next) {
      const char *path = (const char *)hel->data;
      DIR        *dd   = opendir(path);

      if (dd) {
        struct dirent *file;

        for (file = readdir(dd); file; file = readdir(dd)) {
          const char *name = file->d_name;

          if (name && name[0] == 'h' && name[1] == 'l' &&
                      name[2] == 'p' && name[3] == '_') {
            const char *nstart = name + 4;
            const char *nend   = strrchr(nstart, '.');

            if (nend) {
              gsize len = nend - nstart;

              if (g_strstr_len(nstart, len, string)) {
                gchar *match = g_strndup(nstart, len);

                if (!g_slist_find_custom(matches, match,
                                         (GCompareFunc)strcmp))
                  matches = g_slist_append(matches, match);
                else
                  g_free(match);

                done = TRUE;
              }
            }
          }
        }

        closedir(dd);
      }
    }

    if (done) {
      GString *message = g_string_new("No exact match found. "
                                      "Keywords, that contain this word:");
      GSList  *wel;

      for (wel = matches; wel; wel = wel->next) {
        gchar *word = (gchar *)wel->data;

        g_string_append_printf(message, " %s,", word);

        g_free(wel->data);
      }

      message->str[message->len - 1] = '.';

      g_slist_free(matches);

      {
        char *msg = g_string_free(message, FALSE);

        if (jid)
          scr_WriteIncomingMessage(jid, msg, 0,
                                   HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
        else
          scr_LogPrint(LPRINT_NORMAL, "%s", msg);

        g_free(msg);
      }
    }
  }

  if (!done) {
    if (jid) // XXX
      scr_WriteIncomingMessage(jid, "No help found.", 0,
                               HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
    else
      scr_LogPrint(LPRINT_NORMAL, "No help found.");
  }

  g_free(string);
}

static gchar *help_guard(const gchar *key, const gchar *new_value)
{
  help_dirs_stalled = TRUE;
  return g_strdup(new_value);
}

void help_init(void)
{
  settings_set_guard("lang", help_guard);
  settings_set_guard("help_dirs", help_guard);
}

/* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2:  For Vim users... */
