/*
 * util.c
 *
 * Copyright (C) 2002 Sun Microsystems, Inc.
 *           (C) 1999, 2000 Red Hat Inc.
 *           (C) 1998 James Henstridge
 *           (C) 1995-2002 Free Software Foundation
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Authors: Glynn Foster <glynn.foster@sun.com>
 *          Havoc Pennington <hp@redhat.com>
 *          James Henstridge <james@daa.com.au>
 *          Tom Tromey <tromey@redhat.com>
 */

#include <stdio.h>
#include <locale.h>
#include <errno.h>
#include <string.h>
#include "config.h"
#include "util.h"
#include "zenity.h"
#include <gconf/gconf-client.h>

#define URL_HANDLER_DIR      "/desktop/gnome/url-handlers/"
#define DEFAULT_HANDLER_PATH "/desktop/gnome/url-handlers/unknown/command"

#define ZENITY_OK_DEFAULT	0
#define ZENITY_CANCEL_DEFAULT	1
#define ZENITY_ESC_DEFAULT	1
#define ZENITY_ERROR_DEFAULT	-1
#define ZENITY_EXTRA_DEFAULT	127

GladeXML*
zenity_util_load_glade_file (const gchar *widget_root) 
{
  GladeXML *xml = NULL;

  if (g_file_test (ZENITY_GLADE_FILE_RELATIVEPATH, G_FILE_TEST_EXISTS)) {
    /* Try current dir, for debugging */
    xml = glade_xml_new (ZENITY_GLADE_FILE_RELATIVEPATH, widget_root, GETTEXT_PACKAGE);
  }
 
  if (xml == NULL)
    xml = glade_xml_new (ZENITY_GLADE_FILE_FULLPATH, widget_root, GETTEXT_PACKAGE);

  if (xml == NULL) {
    g_warning ("Could not load glade file : %s", ZENITY_GLADE_FILE_FULLPATH);
    return NULL;
  }

  return xml;
}

gchar*
zenity_util_strip_newline (gchar *string)
{
    gsize len;
    
    g_return_val_if_fail (string != NULL, NULL);
                                                                                                                                                                             
    len = strlen (string);
    while (len--) 
    {
      if (string[len] == '\n')
        string[len] = '\0';
      else
        break;
    }
            
    return string;
}

gboolean
zenity_util_fill_file_buffer (GtkTextBuffer *buffer, const gchar *filename) 
{
  GtkTextIter iter, end;
  FILE *f;
  gchar buf[2048];
  gint remaining = 0;

  if (filename == NULL)
    return FALSE;

  f = fopen (filename, "r");

  if (f == NULL) {
    g_warning ("Cannot open file '%s': %s", filename, g_strerror (errno));
    return FALSE;
  }

  gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);

  while (!feof (f)) {
    gint count;
    const char *leftover;
    int to_read = 2047  - remaining;

    count = fread (buf + remaining, 1, to_read, f);
    buf[count + remaining] = '\0';

    g_utf8_validate (buf, count + remaining, &leftover);

    g_assert (g_utf8_validate (buf, leftover - buf, NULL));
    gtk_text_buffer_insert (buffer, &iter, buf, leftover - buf);

    remaining = (buf + remaining + count) - leftover;
    g_memmove (buf, leftover, remaining);

    if (remaining > 6 || count < to_read)
      break;
  }

  if (remaining) {
    g_warning ("Invalid UTF-8 data encountered reading file '%s'", filename);
    return FALSE;
  }

  /* We had a newline in the buffer to begin with. (The buffer always contains
   * a newline, so we delete to the end of the buffer to clean up.
   */
  
  gtk_text_buffer_get_end_iter (buffer, &end);
  gtk_text_buffer_delete (buffer, &iter, &end);

  gtk_text_buffer_set_modified (buffer, FALSE);

  return TRUE;
}

static GList *
zenity_util_list_from_char_array (const char **s)
{
  GList *list = NULL;
  gint i;

  for (i = 0; s[i]; i++) {
    GdkPixbuf *pixbuf;

    pixbuf = gdk_pixbuf_new_from_file (s[i], NULL);
    if (pixbuf)
      list = g_list_prepend (list, pixbuf);
  }

  return list;
}

static void
zenity_util_free_list (GList *list)
{
  g_list_foreach (list, (GFunc) g_object_unref, NULL);
  g_list_free (list);
}

void
zenity_util_set_window_icon (GtkWidget *widget, const gchar *filename)
{
  const gchar *filenames[2] = { NULL};
  GList *list;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_WINDOW (widget));

  if (filename == NULL)
    return;

  filenames[0] = filename;
  list = zenity_util_list_from_char_array (filenames);
  gtk_window_set_icon_list (GTK_WINDOW (widget), list);
  zenity_util_free_list (list);
}

void 
zenity_util_set_window_icon_from_stock (GtkWidget *widget, const gchar *stock_id)
{
  GdkPixbuf *pixbuf;
	
  pixbuf = gtk_widget_render_icon (widget, stock_id, (GtkIconSize) -1, NULL);
  gtk_window_set_icon (GTK_WINDOW (widget), pixbuf);
  g_object_unref (pixbuf);
}

/* This is copied from libgnome/gnome-i18n.c since we try and avoid using
 * the evils of gnome_program_init (), which messes up the commandline
 */

static GHashTable *alias_table = NULL;

/*read an alias file for the locales*/
static void
zenity_read_aliases (char *file)
{
  FILE *fp;
  char buf[256];

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

  fp = fopen (file,"r");

  if (!fp)
    return;

  while (fgets (buf,256,fp)) {
    gchar *p;
    g_strstrip (buf);

    if (buf[0]=='#' || buf[0]=='\0')
      continue;

    p = (gchar *) strtok (buf, "\t ");

    if (!p)
      continue;

    p = (gchar *) strtok (NULL, "\t ");

    if(!p)
      continue;

    if (!g_hash_table_lookup (alias_table, buf))
      g_hash_table_insert (alias_table, g_strdup (buf), g_strdup (p));
  }

  fclose (fp);
}

/*return the un-aliased language as a newly allocated string*/
static char *
zenity_unalias_lang (char *lang)
{
  char *p;
  int i;

  if (!alias_table) { 
    zenity_read_aliases ("/usr/share/locale/locale.alias");
    zenity_read_aliases ("/usr/local/share/locale/locale.alias");
    zenity_read_aliases ("/usr/lib/X11/locale/locale.alias");
    zenity_read_aliases ("/usr/openwin/lib/locale/locale.alias");
  }
  
  i = 0;
  while ((p = g_hash_table_lookup (alias_table,lang)) && strcmp (p, lang)) {
    lang = p;
       
    if (i++ == 30) {
      static gboolean said_before = FALSE;

      if (!said_before)
        g_warning (_("Too many alias levels for a locale may indicate a loop"));

      said_before = TRUE;
      return lang;
    }
  }
  return lang;
}

/* Mask for components of locale spec. The ordering here is from
 * least significant to most significant
 */
enum
{
  COMPONENT_CODESET =   1 << 0,
  COMPONENT_TERRITORY = 1 << 1,
  COMPONENT_MODIFIER =  1 << 2
};

/* Break an X/Open style locale specification into components
 */
static guint
zenity_explode_locale (const gchar *locale,
                       gchar      **language, 
                       gchar      **territory, 
                       gchar      **codeset, 
                       gchar      **modifier)
{
  const gchar *uscore_pos;
  const gchar *at_pos; 
  const gchar *dot_pos; 
  guint mask = 0;

  uscore_pos = (const gchar *) strchr (locale, '_'); 
  dot_pos = (const gchar *) strchr (uscore_pos ? uscore_pos : locale, '.'); 
  at_pos = (const gchar *) strchr (dot_pos ? dot_pos : (uscore_pos ? uscore_pos : locale), '@'); 

  if (at_pos) { 
    mask |= COMPONENT_MODIFIER; 
    *modifier = g_strdup (at_pos);
  } 
  else 
    at_pos = locale + strlen (locale);

  if (dot_pos) { 
    mask |= COMPONENT_CODESET; 
    *codeset = g_new (gchar, 1 + at_pos - dot_pos); 
    strncpy (*codeset, dot_pos, at_pos - dot_pos);
    (*codeset) [at_pos - dot_pos] = '\0';
  } 
  else 
    dot_pos = at_pos;

  if (uscore_pos) {
    mask |= COMPONENT_TERRITORY; 
    *territory = g_new (gchar, 1 + dot_pos - uscore_pos); 
    strncpy (*territory, uscore_pos, dot_pos - uscore_pos); 
    (*territory)[dot_pos - uscore_pos] = '\0';
  } 
  else
    uscore_pos = dot_pos;

  *language = g_new (gchar, 1 + uscore_pos - locale); 
  strncpy (*language, locale, uscore_pos - locale); 
  (*language) [uscore_pos - locale] = '\0';

  return mask;
}

static GList *
zenity_compute_locale_variants (const gchar *locale)
{
  GList *retval = NULL; 
  gchar *language; 
  gchar *territory; 
  gchar *codeset; 
  gchar *modifier; 
  guint mask;
  guint i;


  g_return_val_if_fail (locale != NULL, NULL); 
      
  mask = zenity_explode_locale (locale, &language, &territory, &codeset, &modifier); 
        
  /* Iterate through all possible combinations, from least attractive
   * to most attractive.
   */
  
  for (i = 0; i <= mask; i++) 
    if ((i & ~mask) == 0) { 
      gchar *val = g_strconcat (language, (i & COMPONENT_TERRITORY) ? territory : "", 
                                          (i & COMPONENT_CODESET) ? codeset : "", 
                                          (i & COMPONENT_MODIFIER) ? modifier : "", NULL);
      retval = g_list_prepend (retval, val);
    }

    g_free (language); 
        
    if (mask & COMPONENT_CODESET) 
      g_free (codeset); 
    if (mask & COMPONENT_TERRITORY) 
      g_free (territory); 
    if (mask & COMPONENT_MODIFIER) 
      g_free (modifier);

    return retval;
}

static const gchar *
zenity_guess_category_value (const gchar *categoryname)
{
  const gchar *retval;

  /* The highest priority value is the `LANGUAGE' environment 
   * variable.  This is a GNU extension.
   */

  retval = g_getenv ("LANGUAGE");

  if (retval != NULL && retval[0] != '\0') 
    return retval;

  /* `LANGUAGE' is not set.  So we have to proceed with the POSIX 
   * methods of looking to `LC_ALL', `LC_xxx', and `LANG'.  On some 
   * systems this can be done by the `setlocale' function itself. 
   */

  /* Setting of LC_ALL overwrites all other.  */

  retval = g_getenv ("LC_ALL");

  if (retval != NULL && retval[0] != '\0') 
    return retval;

  /* Next comes the name of the desired category.  */ 
  retval = g_getenv (categoryname);

  if (retval != NULL && retval[0] != '\0') 
    return retval;

  /* Last possibility is the LANG environment variable.  */ 
  retval = g_getenv ("LANG"); 
  if (retval != NULL && retval[0] != '\0') 
    return retval;

  return NULL;
}


static GHashTable *category_table= NULL;

static const GList *
zenity_i18n_get_language_list (const gchar *category_name)
{
  GList *list; 
        
  if (!category_name) 
    category_name= "LC_ALL"; 
        
  if (category_table) {
    list= g_hash_table_lookup (category_table, (const gpointer) category_name); 
  } else { 
    category_table= g_hash_table_new (g_str_hash, g_str_equal); 
    list= NULL; 
  }

  if (!list) {
    gint c_locale_defined= FALSE; 
    const gchar *category_value; 
    gchar *category_memory, *orig_category_memory; 
                
    category_value = zenity_guess_category_value (category_name); 
               
    if (! category_value) 
      category_value = "C"; 

      orig_category_memory = category_memory = g_malloc (strlen (category_value) + 1);
      
      while (category_value[0] != '\0') {
	while (category_value[0] != '\0' && category_value[0] == ':') 
        ++category_value; 
                        
        if (category_value[0] != '\0') {
	  char *cp= category_memory; 
                                
          while (category_value[0] != '\0' && category_value[0] != ':') 
            *category_memory++= *category_value++; 
                                
          category_memory[0]= '\0'; 
          category_memory++; 
                                
          cp = zenity_unalias_lang (cp); 
                               
          if (strcmp (cp, "C") == 0)
            c_locale_defined= TRUE; 

          list= g_list_concat (list, zenity_compute_locale_variants (cp)); 
        } 
     } 
                
     g_free (orig_category_memory); 
                
     if (!c_locale_defined) 
       list= g_list_append (list, "C"); 
                
     g_hash_table_insert (category_table, (gpointer) category_name, list); 
  } 
       
  return list;
}

/* This is copied from libgnome/gnome-help.c since we try and avoid using
 * the evils of gnome_program_init (), which messes up the commandline
 */

static char *
zenity_locate_help_file (const char *path, const char *doc_name)
{
  int i;
  char *exts[] = { ".xml", ".docbook", ".sgml", ".html", "", NULL };
  const GList *lang_list = zenity_i18n_get_language_list ("LC_MESSAGES");

  for (;lang_list != NULL; lang_list = lang_list->next) {
    const char *lang = lang_list->data;

    /* This has to be a valid language AND a language with
     * no encoding postfix.  The language will come up without
     * encoding next
     */

    if (lang == NULL || (gchar *) strchr (lang, '.') != NULL)
      continue;

    for (i = 0; exts[i] != NULL; i++) {
       char *name;
       char *full;
      
       name = g_strconcat (doc_name, exts[i], NULL);
       full = g_build_filename (path, lang, name, NULL);

       if (g_file_test (full, G_FILE_TEST_EXISTS))
         return full;

       g_free (full);

    }
  }

  return NULL;
}

/* This is copied from libgnome/gnome-url.c since we try and avoid using
 * the evils of gnome_program_init (), which messes up the commandline
 */

gboolean
zenity_util_show_help (const gchar *path, const gchar *document, GError **error)
{
  GConfClient *client;
  gint i;
  gchar *pos, *template;
  int argc;
  char **argv;
  gboolean ret;
  char *url;
	
  g_return_val_if_fail (path != NULL, FALSE);
  g_return_val_if_fail (document != NULL, FALSE);
        
  url = g_strconcat ("ghelp:///", zenity_locate_help_file (path, document), NULL);
  pos = (gchar *) strchr (url, ':');

  client = gconf_client_get_default ();

  if (pos != NULL) {
    gchar *protocol, *path;

    g_return_val_if_fail (pos >= url, FALSE);

    protocol = g_new (gchar, pos - url + 1);
    strncpy (protocol, url, pos - url);
    protocol[pos - url] = '\0';
    g_ascii_strdown (protocol, -1);
    
    path = g_strconcat (URL_HANDLER_DIR, protocol, "/command", NULL);
    template = gconf_client_get_string (client, path, NULL);

    if (template == NULL) {
      gchar* template_temp;
			
      template_temp = gconf_client_get_string (client, DEFAULT_HANDLER_PATH, NULL);
						
      /* Retry to get the right url handler */
      template = gconf_client_get_string (client, path, NULL);

      if (template == NULL) 
        template = template_temp;
      else
        g_free (template_temp);

    }
		
    g_free (path);
    g_free (protocol);

  } else {
    /* no ':' ? this shouldn't happen. Use default handler */
    template = gconf_client_get_string (client, DEFAULT_HANDLER_PATH, NULL);
  }

  g_object_unref (G_OBJECT (client));

  if (!g_shell_parse_argv (template, &argc, &argv, error)) {
    g_free (template);
    return FALSE;
  }

  g_free (template);

  for (i = 0; i < argc; i++) {
    char *arg;

    if (strcmp (argv[i], "%s") != 0)
      continue;

    arg = argv[i];
    argv[i] = g_strdup (url);
    g_free (arg);
  }
	
  /* This can return some errors */
  ret = g_spawn_async (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, error); 
  g_strfreev (argv);

  return ret;
}

gint 
zenity_util_return_exit_code ( ZenityExitCode value ) 
{

  const gchar *env_var = NULL;
  gint retval;

  switch (value) {
  
  case ZENITY_OK:
    env_var = g_getenv("ZENITY_OK");
    if (! env_var) 
          env_var = g_getenv("DIALOG_OK");
    if (! env_var) 
          retval = ZENITY_OK_DEFAULT;
    break;
   
  case ZENITY_CANCEL:
    env_var = g_getenv("ZENITY_CANCEL");
    if (! env_var) 
          env_var = g_getenv("DIALOG_CANCEL");
    if (! env_var) 
          retval = ZENITY_CANCEL_DEFAULT;
    break;
    
  case ZENITY_ESC:
    env_var = g_getenv("ZENITY_ESC");
    if (! env_var) 
          env_var = g_getenv("DIALOG_ESC");
    if (! env_var) 
          retval = ZENITY_ESC_DEFAULT;
    break;
    
  case ZENITY_EXTRA:
    env_var = g_getenv("ZENITY_EXTRA");
    if (! env_var) 
          env_var = g_getenv("DIALOG_EXTRA");
    if (! env_var) 
          retval = ZENITY_EXTRA_DEFAULT;
    break;
    
  case ZENITY_ERROR:
    env_var = g_getenv("ZENITY_ERROR");
    if (! env_var) 
          env_var = g_getenv("DIALOG_ERROR");
    if (! env_var) 
          retval = ZENITY_ERROR_DEFAULT;
    break;
    
  default:
    retval = 1;
  }
  
  if (env_var) 
      retval = atoi (env_var);
  return retval; 
}