/*
 * Copyright (C) 2004, 2005 Jean-Yves Lefort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of Jean-Yves Lefort nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include <string.h>
#include <glib.h>
#include <glib/gi18n-lib.h>
#include <gmodule.h>
#include "translate.h"

#include "translate-rfc3066-private.h"

const unsigned int translate_major_version = TRANSLATE_MAJOR_VERSION;
const unsigned int translate_minor_version = TRANSLATE_MINOR_VERSION;
const unsigned int translate_micro_version = TRANSLATE_MICRO_VERSION;

gboolean translate_initialized = FALSE;

static GSList *services = NULL;
G_LOCK_DEFINE_STATIC(services);

static GHashTable *languages = NULL;
G_LOCK_DEFINE_STATIC(languages);

static char *proxy_uri = NULL;
G_LOCK_DEFINE_STATIC(proxy_uri);

static void translate_load_modules (const char *directory);
static gboolean translate_load_module (const char *filename, GError **err);

GQuark
translate_error_quark (void)
{
  return g_quark_from_static_string("translate-error");
}

GQuark
translate_init_error_quark (void)
{
  return g_quark_from_static_string("translate-init-error");
}

/**
 * translate_init:
 * @err: a location to report errors, or %NULL. Any of the errors in
 * #TranslateInitError or other domains may occur.
 *
 * Initializes libtranslate. This function can safely be called
 * multiple times (however, it is <emphasis>not</emphasis>
 * thread-safe, read below). When it is called more than once, it
 * returns the status (and error if any) of the initial invocation.
 *
 * <important><para>
 * Unlike other libtranslate functions, translate_init() is
 * <emphasis>not</emphasis> thread-safe, and must
 * <emphasis>not</emphasis> be called while any mutex is held. The
 * reason is that translate_init() may call g_thread_init(). Refer to
 * the g_thread_init() documentation for more details.
 * </para></important>
 *
 * Return value: %TRUE if the initialization was successful, %FALSE
 * otherwise (in such case @err is set to the error that has
 * occurred).
 **/
gboolean
translate_init (GError **err)
{
  if (! translate_initialized)
    {
      static GError *init_err = NULL;

      if (! init_err)
	{
#ifdef ENABLE_NLS
	  bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
	  bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
#endif /* ENABLE_NLS */

	  g_type_init();
	  
	  if (! g_thread_supported())
	    g_thread_init(NULL);

	  if (g_thread_supported())
	    {
	      char *user_modules;
	      
	      translate_initialized = TRUE;
	      
	      languages = g_hash_table_new(translate_ascii_strcase_hash,
					   translate_ascii_strcase_equal);
	      translate_rfc3066_init(languages);
	      
	      translate_load_modules(MODULESDIR);
	      
	      user_modules = g_build_filename(g_get_home_dir(), ".libtranslate", "modules", NULL);
	      translate_load_modules(user_modules);
	      g_free(user_modules);
	    }
	  else
	    init_err = g_error_new(TRANSLATE_INIT_ERROR,
				   TRANSLATE_INIT_ERROR_MULTI_THREADING_NOT_SUPPORTED,
				   _("multi-threading is not supported"));
	}

      if (init_err && err)
	*err = g_error_copy(init_err);
    }

  return translate_initialized;
}

static void
translate_load_modules (const char *directory)
{
  GError *err = NULL;
  GDir *dir;
  const char *filename;
  
  g_return_if_fail(directory != NULL);

  if (! g_file_test(directory, G_FILE_TEST_IS_DIR))
    return;

  dir = g_dir_open(directory, 0, &err);
  if (! dir)
    {
      g_warning(_("unable to scan modules directory \"%s\": %s"), directory, err->message);
      g_error_free(err);
      return;
    }

  while ((filename = g_dir_read_name(dir)))
    {
      char *pathname;
      char *extension;

      pathname = g_build_filename(directory, filename, NULL);
      if (g_file_test(pathname, G_FILE_TEST_IS_REGULAR)
	  && (extension = strrchr(filename, '.'))
	  && ! strcmp(++extension, G_MODULE_SUFFIX))
	{
	  if (! translate_load_module(pathname, &err))
	    {
	      g_warning(_("unable to load module \"%s\": %s"), pathname, err->message);
	      g_clear_error(&err);
	    }
	}
      g_free(pathname);
    }

  g_dir_close(dir);
}

static gboolean
translate_load_module (const char *filename, GError **err)
{
  GModule *module;
  gpointer func;

  g_return_val_if_fail(filename != NULL, FALSE);

  module = g_module_open(filename, 0);
  if (! module)
    {
      g_set_error(err,
		  TRANSLATE_ERROR,
		  TRANSLATE_ERROR_FAILED,
		  "%s", g_module_error());
      return FALSE;
    }

  if (g_module_symbol(module, "translate_module_init", &func))
    {
      if (((TranslateModuleInitFunc) func)(err))
	return TRUE;
    }
  else
    g_set_error(err,
		TRANSLATE_ERROR,
		TRANSLATE_ERROR_FAILED,
		_("unable to find translate_module_init() function"));

  /* failed, close the module */
  g_module_close(module);
  return FALSE;
}

/**
 * translate_add_service:
 * @service: a service.
 *
 * Adds @service to the libtranslate service list. If a service with
 * the same name is already in the list, nothing is done and %FALSE is
 * returned.
 *
 * Return value: %TRUE if the service was added, %FALSE if it was
 * already in the list.
 **/
gboolean
translate_add_service (TranslateService *service)
{
  const char *name;
  gboolean added = TRUE;
  GSList *l;

  g_return_val_if_fail(TRANSLATE_IS_SERVICE(service), FALSE);

  name = translate_service_get_name(service);
  g_return_val_if_fail(name != NULL, FALSE);

  G_LOCK(services);
  for (l = services; l != NULL && added; l = l->next)
    if (! strcmp(translate_service_get_name(l->data), name))
      added = FALSE;

  if (added)
    services = g_slist_append(services, g_object_ref(service));
  G_UNLOCK(services);

  return added;
}

/**
 * translate_get_service:
 * @name: a service name, encoded in ASCII.
 *
 * Looks up a service in the libtranslate service list.
 *
 * Return value: a new reference to the service with name @name, or
 * %NULL if not found.
 **/
TranslateService *
translate_get_service (const char *name)
{
  TranslateService *service = NULL;
  GSList *l;

  g_return_val_if_fail(name != NULL, NULL);

  G_LOCK(services);
  for (l = services; l != NULL && ! service; l = l->next)
    if (! strcmp(translate_service_get_name(l->data), name))
      service = g_object_ref(l->data);
  G_UNLOCK(services);

  return service;
}

/**
 * translate_get_services:
 *
 * Gets a copy of the libtranslate service list. When no longer
 * needed, the list should be freed with:
 *
 * <programlisting>
 * g_slist_foreach(list, (GFunc) g_object_unref, NULL);
 * g_slist_free(list);
 * </programlisting>
 *
 * Return value: a copy of the libtranslate service list.
 **/
GSList *
translate_get_services (void)
{
  GSList *copy;

  G_LOCK(services);
  copy = g_slist_copy(services);
  g_slist_foreach(copy, (GFunc) g_object_ref, NULL);
  G_UNLOCK(services);

  return copy;
}

/**
 * translate_add_language:
 * @tag: a RFC 3066 language tag.
 * @name: the language human-readable name.
 *
 * Adds a language tag to name mapping to the libtranslate language
 * database. If a language with the same tag already exists in the
 * database, nothing is done and %FALSE is returned.
 *
 * <note><para>
 * Some RFC 3066 tag to name mappings are <link
 * linkend="rfc3066-builtin">built into libtranslate</link> and do not
 * need to be added.
 * </para></note>
 *
 * Return value: %TRUE if the language was added, %FALSE if it was
 * already in the database.
 **/
gboolean
translate_add_language (const char *tag, const char *name)
{
  gboolean added;

  g_return_val_if_fail(tag != NULL, FALSE);
  g_return_val_if_fail(name != NULL, FALSE);

  G_LOCK(languages);
  if (g_hash_table_lookup(languages, tag))
    added = FALSE;
  else
    {
      g_hash_table_insert(languages, g_strdup(tag), g_strdup(name));
      added = TRUE;
    }
  G_UNLOCK(languages);

  return added;
}

/**
 * translate_get_language_name:
 * @tag: a RFC 3066 language tag.
 *
 * Looks up a language tag in the libtranslate language database, and
 * returns its human-readable name.
 *
 * Return value: the human-readable name of the language with tag
 * @tag, or @tag itself if not found. If the returned string is not
 * @tag, it is owned by libtranslate and must not be modified or
 * freed.
 **/
const char *
translate_get_language_name (const char *tag)
{
  const char *name;

  g_return_val_if_fail(tag != NULL, NULL);

  G_LOCK(languages);
  /*
   * We do not return a copy of the name (less overhead and
   * thread-safe, because once a language is added in the hash, it
   * cannot be removed).
   */
  name = g_hash_table_lookup(languages, tag);
  G_UNLOCK(languages);

  return name ? name : tag;
}

/**
 * translate_set_proxy:
 * @uri: the URI of a proxy server, or %NULL to unset.
 *
 * Sets the URI of a proxy server to use for network transfers.
 **/
void
translate_set_proxy (const char *uri)
{
  G_LOCK(proxy_uri);
  g_free(proxy_uri);
  proxy_uri = g_strdup(uri);
  G_UNLOCK(proxy_uri);
}

/**
 * translate_get_proxy:
 *
 * Gets the URI thas has been previously set with
 * translate_set_proxy().
 *
 * If you are implementing a service, you <emphasis>must</emphasis>
 * use this URI for proxying network transfers.
 *
 * Return value: the libtranslate proxy URI, or %NULL if no proxy is
 * set. The returned string should be freed when no longer needed.
 **/
char *
translate_get_proxy (void)
{
  char *copy;

  G_LOCK(proxy_uri);
  copy = g_strdup(proxy_uri);
  G_UNLOCK(proxy_uri);

  return copy;
}
