/* LIBGIMP - The GIMP Library
 * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
 *
 * gimputils.c
 * Copyright (C) 2003  Sven Neumann <sven@gimp.org>
 *
 * This library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>
#include <stdio.h>

#include <glib-object.h>

#include "gimpbasetypes.h"
#include "gimputils.h"

#include "libgimp/libgimp-intl.h"


/**
 * SECTION: gimputils
 * @title: gimputils
 * @short_description: Utilities of general interest
 *
 * Utilities of general interest
 **/


/**
 * gimp_utf8_strtrim:
 * @str: an UTF-8 encoded string (or %NULL)
 * @max_chars: the maximum number of characters before the string get
 * trimmed
 *
 * Creates a (possibly trimmed) copy of @str. The string is cut if it
 * exceeds @max_chars characters or on the first newline. The fact
 * that the string was trimmed is indicated by appending an ellipsis.
 *
 * Returns: A (possibly trimmed) copy of @str which should be freed
 * using g_free() when it is not needed any longer.
 **/
gchar *
gimp_utf8_strtrim (const gchar *str,
                   gint         max_chars)
{
  /* FIXME: should we make this translatable? */
  const gchar ellipsis[] = "...";
  const gint  e_len      = strlen (ellipsis);

  if (str)
    {
      const gchar *p;
      const gchar *newline = NULL;
      gint         chars   = 0;
      gunichar     unichar;

      for (p = str; *p; p = g_utf8_next_char (p))
        {
          if (++chars > max_chars)
            break;

          unichar = g_utf8_get_char (p);

          switch (g_unichar_break_type (unichar))
            {
            case G_UNICODE_BREAK_MANDATORY:
            case G_UNICODE_BREAK_LINE_FEED:
              newline = p;
              break;
            default:
              continue;
            }

          break;
        }

      if (*p)
        {
          gsize  len     = p - str;
          gchar *trimmed = g_new (gchar, len + e_len + 2);

          memcpy (trimmed, str, len);
          if (newline)
            trimmed[len++] = ' ';

          g_strlcpy (trimmed + len, ellipsis, e_len + 1);

          return trimmed;
        }

      return g_strdup (str);
    }

  return NULL;
}

/**
 * gimp_any_to_utf8:
 * @str:            The string to be converted to UTF-8.
 * @len:            The length of the string, or -1 if the string
 *                  is nul-terminated.
 * @warning_format: The message format for the warning message if conversion
 *                  to UTF-8 fails. See the <function>printf()</function>
 *                  documentation.
 * @...:            The parameters to insert into the format string.
 *
 * This function takes any string (UTF-8 or not) and always returns a valid
 * UTF-8 string.
 *
 * If @str is valid UTF-8, a copy of the string is returned.
 *
 * If UTF-8 validation fails, g_locale_to_utf8() is tried and if it
 * succeeds the resulting string is returned.
 *
 * Otherwise, the portion of @str that is UTF-8, concatenated
 * with "(invalid UTF-8 string)" is returned. If not even the start
 * of @str is valid UTF-8, only "(invalid UTF-8 string)" is returned.
 *
 * Return value: The UTF-8 string as described above.
 **/
gchar *
gimp_any_to_utf8 (const gchar  *str,
                  gssize        len,
                  const gchar  *warning_format,
                  ...)
{
  const gchar *start_invalid;
  gchar       *utf8;

  g_return_val_if_fail (str != NULL, NULL);

  if (g_utf8_validate (str, len, &start_invalid))
    {
      if (len < 0)
        utf8 = g_strdup (str);
      else
        utf8 = g_strndup (str, len);
    }
  else
    {
      utf8 = g_locale_to_utf8 (str, len, NULL, NULL, NULL);
    }

  if (! utf8)
    {
      if (warning_format)
        {
          va_list warning_args;

          va_start (warning_args, warning_format);

          g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE,
                  warning_format, warning_args);

          va_end (warning_args);
        }

      if (start_invalid > str)
        {
          gchar *tmp;

          tmp = g_strndup (str, start_invalid - str);
          utf8 = g_strconcat (tmp, " ", _("(invalid UTF-8 string)"), NULL);
          g_free (tmp);
        }
      else
        {
          utf8 = g_strdup (_("(invalid UTF-8 string)"));
        }
    }

  return utf8;
}

/**
 * gimp_filename_to_utf8:
 * @filename: The filename to be converted to UTF-8.
 *
 * Convert a filename in the filesystem's encoding to UTF-8
 * temporarily.  The return value is a pointer to a string that is
 * guaranteed to be valid only during the current iteration of the
 * main loop or until the next call to gimp_filename_to_utf8().
 *
 * The only purpose of this function is to provide an easy way to pass
 * a filename in the filesystem encoding to a function that expects an
 * UTF-8 encoded filename.
 *
 * Return value: A temporarily valid UTF-8 representation of @filename.
 *               This string must not be changed or freed.
 **/
const gchar *
gimp_filename_to_utf8 (const gchar *filename)
{
  /* Simpleminded implementation, but at least allocates just one copy
   * of each translation. Could check if already UTF-8, and if so
   * return filename as is. Could perhaps (re)use a suitably large
   * cyclic buffer, but then would have to verify that all calls
   * really need the return value just for a "short" time.
   */

  static GHashTable *ht = NULL;
  gchar             *filename_utf8;

  if (! filename)
    return NULL;

  if (! ht)
    ht = g_hash_table_new (g_str_hash, g_str_equal);

  filename_utf8 = g_hash_table_lookup (ht, filename);

  if (! filename_utf8)
    {
      filename_utf8 = g_filename_display_name (filename);
      g_hash_table_insert (ht, g_strdup (filename), filename_utf8);
    }

  return filename_utf8;
}


/**
 * gimp_strip_uline:
 * @str: underline infested string (or %NULL)
 *
 * This function returns a copy of @str stripped of underline
 * characters. This comes in handy when needing to strip mnemonics
 * from menu paths etc.
 *
 * In some languages, mnemonics are handled by adding the mnemonic
 * character in brackets (like "File (_F)"). This function recognizes
 * this construct and removes the whole bracket construction to get
 * rid of the mnemonic (see bug 157561).
 *
 * Return value: A (possibly stripped) copy of @str which should be
 *               freed using g_free() when it is not needed any longer.
 **/
gchar *
gimp_strip_uline (const gchar *str)
{
  gchar    *escaped;
  gchar    *p;
  gboolean  past_bracket = FALSE;

  if (! str)
    return NULL;

  p = escaped = g_strdup (str);

  while (*str)
    {
      if (*str == '_')
        {
          /*  "__" means a literal "_" in the menu path  */
          if (str[1] == '_')
            {
             *p++ = *str++;
             str++;
             continue;
            }

          /*  find the "(_X)" construct and remove it entirely  */
          if (past_bracket && str[1] && *(g_utf8_next_char (str + 1)) == ')')
            {
              str = g_utf8_next_char (str + 1) + 1;
              p--;
            }
          else
            {
              str++;
            }
        }
      else
        {
          past_bracket = (*str == '(');

          *p++ = *str++;
        }
    }

  *p = '\0';

  return escaped;
}

/**
 * gimp_escape_uline:
 * @str: Underline infested string (or %NULL)
 *
 * This function returns a copy of @str with all underline converted
 * to two adjacent underlines. This comes in handy when needing to display
 * strings with underlines (like filenames) in a place that would convert
 * them to mnemonics.
 *
 * Return value: A (possibly escaped) copy of @str which should be
 * freed using g_free() when it is not needed any longer.
 *
 * Since: GIMP 2.2
 **/
gchar *
gimp_escape_uline (const gchar *str)
{
  gchar *escaped;
  gchar *p;
  gint   n_ulines = 0;

  if (! str)
    return NULL;

  for (p = (gchar *) str; *p; p++)
    if (*p == '_')
      n_ulines++;

  p = escaped = g_malloc (strlen (str) + n_ulines + 1);

  while (*str)
    {
      if (*str == '_')
        *p++ = '_';

      *p++ = *str++;
    }

  *p = '\0';

  return escaped;
}

/**
 * gimp_canonicalize_identifier:
 * @identifier: The identifier string to canonicalize.
 *
 * Turns any input string into a canonicalized string.
 *
 * Canonical identifiers are e.g. expected by the PDB for procedure
 * and parameter names. Every character of the input string that is
 * not either '-', 'a-z', 'A-Z' or '0-9' will be replaced by a '-'.
 *
 * Return value: The canonicalized identifier. This is a newly
 *               allocated string that should be freed with g_free()
 *               when no longer needed.
 *
 * Since: GIMP 2.4
 **/
gchar *
gimp_canonicalize_identifier (const gchar *identifier)
{
  gchar *canonicalized = NULL;

  if (identifier)
    {
      gchar *p;

      canonicalized = g_strdup (identifier);

      for (p = canonicalized; *p != 0; p++)
        {
          gchar c = *p;

          if (c != '-' &&
              (c < '0' || c > '9') &&
              (c < 'A' || c > 'Z') &&
              (c < 'a' || c > 'z'))
            *p = '-';
        }
    }

  return canonicalized;
}

/**
 * gimp_enum_get_desc:
 * @enum_class: a #GEnumClass
 * @value:      a value from @enum_class
 *
 * Retrieves #GimpEnumDesc associated with the given value, or %NULL.
 *
 * Return value: the value's #GimpEnumDesc.
 *
 * Since: GIMP 2.2
 **/
GimpEnumDesc *
gimp_enum_get_desc (GEnumClass *enum_class,
                    gint        value)
{
  const GimpEnumDesc *value_desc;

  g_return_val_if_fail (G_IS_ENUM_CLASS (enum_class), NULL);

  value_desc =
    gimp_enum_get_value_descriptions (G_TYPE_FROM_CLASS (enum_class));

  if (value_desc)
    {
      while (value_desc->value_desc)
        {
          if (value_desc->value == value)
            return (GimpEnumDesc *) value_desc;

          value_desc++;
        }
    }

  return NULL;
}

/**
 * gimp_enum_get_value:
 * @enum_type:  the #GType of a registered enum
 * @value:      an integer value
 * @value_name: return location for the value's name (or %NULL)
 * @value_nick: return location for the value's nick (or %NULL)
 * @value_desc: return location for the value's translated description (or %NULL)
 * @value_help: return location for the value's translated help (or %NULL)
 *
 * Checks if @value is valid for the enum registered as @enum_type.
 * If the value exists in that enum, its name, nick and its translated
 * description and help are returned (if @value_name, @value_nick,
 * @value_desc and @value_help are not %NULL).
 *
 * Return value: %TRUE if @value is valid for the @enum_type,
 *               %FALSE otherwise
 *
 * Since: GIMP 2.2
 **/
gboolean
gimp_enum_get_value (GType         enum_type,
                     gint          value,
                     const gchar **value_name,
                     const gchar **value_nick,
                     const gchar **value_desc,
                     const gchar **value_help)
{
  GEnumClass *enum_class;
  GEnumValue *enum_value;
  gboolean    success = FALSE;

  g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), FALSE);

  enum_class = g_type_class_ref (enum_type);
  enum_value = g_enum_get_value (enum_class, value);

  if (enum_value)
    {
      if (value_name)
        *value_name = enum_value->value_name;

      if (value_nick)
        *value_nick = enum_value->value_nick;

      if (value_desc || value_help)
        {
          GimpEnumDesc *enum_desc;

          enum_desc = gimp_enum_get_desc (enum_class, value);

          if (value_desc)
            {
              if (enum_desc && enum_desc->value_desc)
                {
                  const gchar *context;

                  context = gimp_type_get_translation_context (enum_type);

                  if (context)  /*  the new way, using NC_()    */
                    *value_desc = g_dpgettext2 (gimp_type_get_translation_domain (enum_type),
                                                context,
                                                enum_desc->value_desc);
                  else          /*  for backward compatibility  */
                    *value_desc = g_strip_context (enum_desc->value_desc,
                                                   dgettext (gimp_type_get_translation_domain (enum_type),
                                                             enum_desc->value_desc));
                }
              else
                {
                  *value_desc = NULL;
                }
            }

          if (value_help)
            {
              *value_help = ((enum_desc && enum_desc->value_help) ?
                             dgettext (gimp_type_get_translation_domain (enum_type),
                                       enum_desc->value_help) :
                             NULL);
            }
        }

      success = TRUE;
    }

  g_type_class_unref (enum_class);

  return success;
}

/**
 * gimp_enum_value_get_desc:
 * @enum_class: a #GEnumClass
 * @enum_value: a #GEnumValue from @enum_class
 *
 * Retrieves the translated description for a given @enum_value.
 *
 * Return value: the translated description of the enum value
 *
 * Since: GIMP 2.2
 **/
const gchar *
gimp_enum_value_get_desc (GEnumClass *enum_class,
                          GEnumValue *enum_value)
{
  GType         type = G_TYPE_FROM_CLASS (enum_class);
  GimpEnumDesc *enum_desc;

  enum_desc = gimp_enum_get_desc (enum_class, enum_value->value);

  if (enum_desc && enum_desc->value_desc)
    {
      const gchar *context;

      context = gimp_type_get_translation_context (type);

      if (context)  /*  the new way, using NC_()    */
        return g_dpgettext2 (gimp_type_get_translation_domain (type),
                             context,
                             enum_desc->value_desc);
      else          /*  for backward compatibility  */
        return g_strip_context (enum_desc->value_desc,
                                dgettext (gimp_type_get_translation_domain (type),
                                          enum_desc->value_desc));
    }

  return enum_value->value_name;
}

/**
 * gimp_enum_value_get_help:
 * @enum_class: a #GEnumClass
 * @enum_value: a #GEnumValue from @enum_class
 *
 * Retrieves the translated help for a given @enum_value.
 *
 * Return value: the translated help of the enum value
 *
 * Since: GIMP 2.2
 **/
const gchar *
gimp_enum_value_get_help (GEnumClass *enum_class,
                          GEnumValue *enum_value)
{
  GType         type = G_TYPE_FROM_CLASS (enum_class);
  GimpEnumDesc *enum_desc;

  enum_desc = gimp_enum_get_desc (enum_class, enum_value->value);

  if (enum_desc && enum_desc->value_help)
    return dgettext (gimp_type_get_translation_domain (type),
                     enum_desc->value_help);

  return NULL;
}

/**
 * gimp_flags_get_first_desc:
 * @flags_class: a #GFlagsClass
 * @value:       a value from @flags_class
 *
 * Retrieves the first #GimpFlagsDesc that matches the given value, or %NULL.
 *
 * Return value: the value's #GimpFlagsDesc.
 *
 * Since: GIMP 2.2
 **/
GimpFlagsDesc *
gimp_flags_get_first_desc (GFlagsClass *flags_class,
                           guint        value)
{
  const GimpFlagsDesc *value_desc;

  g_return_val_if_fail (G_IS_FLAGS_CLASS (flags_class), NULL);

  value_desc =
    gimp_flags_get_value_descriptions (G_TYPE_FROM_CLASS (flags_class));

  if (value_desc)
    {
      while (value_desc->value_desc)
        {
          if ((value_desc->value & value) == value_desc->value)
            return (GimpFlagsDesc *) value_desc;

          value_desc++;
        }
    }

  return NULL;
}

/**
 * gimp_flags_get_first_value:
 * @flags_type: the #GType of registered flags
 * @value:      an integer value
 * @value_name: return location for the value's name (or %NULL)
 * @value_nick: return location for the value's nick (or %NULL)
 * @value_desc: return location for the value's translated description (or %NULL)
 * @value_help: return location for the value's translated help (or %NULL)
 *
 * Checks if @value is valid for the flags registered as @flags_type.
 * If the value exists in that flags, its name, nick and its
 * translated description and help are returned (if @value_name,
 * @value_nick, @value_desc and @value_help are not %NULL).
 *
 * Return value: %TRUE if @value is valid for the @flags_type,
 *               %FALSE otherwise
 *
 * Since: GIMP 2.2
 **/
gboolean
gimp_flags_get_first_value (GType         flags_type,
                            guint         value,
                            const gchar **value_name,
                            const gchar **value_nick,
                            const gchar **value_desc,
                            const gchar **value_help)
{
  GFlagsClass *flags_class;
  GFlagsValue *flags_value;

  g_return_val_if_fail (G_TYPE_IS_FLAGS (flags_type), FALSE);

  flags_class = g_type_class_peek (flags_type);
  flags_value = g_flags_get_first_value (flags_class, value);

  if (flags_value)
    {
      if (value_name)
        *value_name = flags_value->value_name;

      if (value_nick)
        *value_nick = flags_value->value_nick;

      if (value_desc || value_help)
        {
          GimpFlagsDesc *flags_desc;

          flags_desc = gimp_flags_get_first_desc (flags_class, value);

          if (value_desc)
            *value_desc = ((flags_desc && flags_desc->value_desc) ?
                           dgettext (gimp_type_get_translation_domain (flags_type),
                                     flags_desc->value_desc) :
                           NULL);

          if (value_help)
            *value_help = ((flags_desc && flags_desc->value_desc) ?
                           dgettext (gimp_type_get_translation_domain (flags_type),
                                     flags_desc->value_help) :
                           NULL);
        }

      return TRUE;
    }

  return FALSE;
}

/**
 * gimp_flags_value_get_desc:
 * @flags_class: a #GFlagsClass
 * @flags_value: a #GFlagsValue from @flags_class
 *
 * Retrieves the translated description for a given @flags_value.
 *
 * Return value: the translated description of the flags value
 *
 * Since: GIMP 2.2
 **/
const gchar *
gimp_flags_value_get_desc (GFlagsClass *flags_class,
                           GFlagsValue *flags_value)
{
  GType         type = G_TYPE_FROM_CLASS (flags_class);
  GimpFlagsDesc *flags_desc;

  flags_desc = gimp_flags_get_first_desc (flags_class, flags_value->value);

  if (flags_desc->value_desc)
    return dgettext (gimp_type_get_translation_domain (type),
                     flags_desc->value_desc);

  return flags_value->value_name;
}

/**
 * gimp_flags_value_get_help:
 * @flags_class: a #GFlagsClass
 * @flags_value: a #GFlagsValue from @flags_class
 *
 * Retrieves the translated help for a given @flags_value.
 *
 * Return value: the translated help of the flags value
 *
 * Since: GIMP 2.2
 **/
const gchar *
gimp_flags_value_get_help (GFlagsClass *flags_class,
                           GFlagsValue *flags_value)
{
  GType         type = G_TYPE_FROM_CLASS (flags_class);
  GimpFlagsDesc *flags_desc;

  flags_desc = gimp_flags_get_first_desc (flags_class, flags_value->value);

  if (flags_desc->value_help)
    return dgettext (gimp_type_get_translation_domain (type),
                     flags_desc->value_help);

  return NULL;
}
