#include <glib.h>
#include <string.h>
#include <gio/gw32registrykey.h>

static gboolean
g_utf16_has_suffix (const gunichar2 *str, const gunichar2 *suffix, gboolean case_sensitive)
{
  gsize str_len;
  gsize suffix_len;
  str_len = wcslen (str);
  suffix_len = wcslen (suffix);
  if (str_len < suffix_len)
    return FALSE;
  if (case_sensitive)
    return (wcscmp (&str[str_len - suffix_len], suffix) == 0);
  return (wcsicmp (&str[str_len - suffix_len], suffix) == 0);
}

static void
add_handler (GHashTable *urls, const gchar *value_name, const gchar *value_data)
{
  GHashTable *url_table;
  gboolean found;
  gboolean found_data;
  gchar *value_name_folded;
  gchar *value_data_folded;
  gchar *value_data_found;

  value_name_folded = g_utf8_casefold (value_name, -1);

  if (value_name_folded == NULL)
    return;

  value_data_folded = g_utf8_casefold (value_data, -1);

  if (value_data_folded == NULL)
    {
      g_free (value_name_folded);
      return;
    }

  found = g_hash_table_lookup_extended (urls, value_name_folded, NULL, (gpointer *) &url_table);

  if (!found)
    {
      url_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
      g_hash_table_insert (urls, value_name_folded, url_table);
    }
  else
    g_free (value_name_folded);

  found_data = g_hash_table_lookup_extended (url_table, value_data_folded, NULL, (gpointer *) &value_data_found);

  if (!found_data)
    g_hash_table_insert (url_table, value_data_folded, g_strdup (value_data));
  else
    g_free (value_data_folded);
}

static gboolean
check_handler_is_runnable (GHashTable *urls, const gchar *url, const gchar *handler_class)
{
  GW32RegistryKey  *shell_open_command;
  GW32RegistryValueType vtype;
  /* Stack allocation is enough, since key names can't be longer than 255 */
  gchar full_subkey_name[256];
  gchar *command;
  gboolean result;

  g_snprintf (full_subkey_name, 256, "HKEY_CLASSES_ROOT\\%s\\shell\\open\\command", handler_class);
  
  shell_open_command = g_w32_registry_key_open (full_subkey_name, NULL);

  if (shell_open_command == NULL)
    return FALSE;

  result = g_w32_registry_key_get_value (shell_open_command,
                                         "",
                                         &vtype,
                                         (gpointer *) &command,
                                         NULL,
                                         NULL);

  if (result)
    add_handler (urls, url, command);
  
  g_object_unref (shell_open_command);
  return result;
}

gboolean
handler_values_callback (GW32RegistryKey        *key,
                         const gchar            *value_name,
                         gsize                   value_name_len,
                         GW32RegistryValueType   value_type,
                         const gpointer          value_data,
                         gsize                   value_data_size,
                         gpointer                data,
                         GError                **error)
{
  GHashTable *urls = (GHashTable *) data;

  if (value_name[0] == '\0' || value_type != G_W32_REGISTRY_STR)
    return TRUE;

  check_handler_is_runnable (urls, value_name, value_data);

  return TRUE;
}

gboolean
handler_keys_callback (GW32RegistryKey  *key,
                       const gunichar2  *key_name,
                       gboolean         *skip,
                       gpointer          data,
                       GError          **error)
{
  
  if (!wcsstr (key_name, L"URLAssociations"))
    return TRUE;

  if (!g_utf16_has_suffix (key_name, L"Capabilities\\URLAssociations", FALSE))
    return TRUE;

  g_w32_registry_key_foreach_value (key, handler_values_callback, data, NULL);
  return TRUE;
}

gboolean
class_callback (GW32RegistryKey  *key,
                const gunichar2  *subkey_name,
                gsize             subkey_name_len,
                gpointer          data,
                GError          **error)
{
  GHashTable *urls;
  GW32RegistryKey  *subkey;
  gboolean is_url_protocol;
  GW32RegistryValueType vtype;
  const gchar *key_name_u8;

  urls = (GHashTable *) data;

  subkey = g_w32_registry_key_open_subkey_w (key, subkey_name, NULL);
  if (subkey == NULL)
    return TRUE;

  is_url_protocol = g_w32_registry_key_get_value_w (subkey,
                                                    L"URL Protocol",
                                                    &vtype,
                                                    NULL,
                                                    NULL,
                                                    NULL);

  if (is_url_protocol)
    {
      const gchar *backslash;
      key_name_u8 = g_w32_registry_key_get_path (subkey);
      backslash = strrchr (key_name_u8, '\\');
      if (backslash)
        check_handler_is_runnable (urls, &backslash[1], &backslash[1]);
    }

  g_object_unref (subkey);

  return TRUE;
}

int
main (int argc, char **argv)
{
  GW32RegistryKey *gkey;
  gboolean success;
  GHashTableIter url_iter;
  gchar *url;
  GHashTable *handlers;
  GHashTable *urls;

  urls = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_destroy);

  gkey = g_w32_registry_key_open_w (L"HKEY_LOCAL_MACHINE\\Software", NULL);
  if (gkey == NULL)
    return 1;

  success = g_w32_registry_key_walk_w (gkey, 4, handler_keys_callback, urls, NULL);
  g_object_unref (gkey);

  if (!success)
    return 2;

  g_object_unref (gkey);

  gkey = g_w32_registry_key_open_w (L"HKEY_CLASSES_ROOT", NULL);
  if (gkey == NULL)
    return 3;

  success = g_w32_registry_key_foreach_subkey_w (gkey, class_callback, urls, NULL);
  g_object_unref (gkey);
  
  if (!success)
    return 4;

  g_print ("%s", "Legend: (from http://msdn.microsoft.com/en-us/library/windows/desktop/cc144101%28v=vs.85%29.aspx)\n"
"%* - replace with all parameters\n"
"%~ - replace with all parameters starting with and following the second parameter\n"
"%0 or %1 the first file parameter. For example \"C:\\Users\\Eric\\Destop\\New Text Document.txt\". Generally this should be in quotes and the applications command line parsing should accept quotes to disambiguate files with spaces in the name and different command line parameters (this is a security best practice and I believe mentioned in MSDN).\n"
"%<n> (where N is 2 - 9), replace with the nth parameter\n"
"%s - show command\n"
"%h - hotkey value\n"
"%i - IDList stored in a shared memory handle is passed here.\n"
"%l - long file name form of the first parameter. Note win32 applications will be passed the long file name, win16 applications get the short file name. Specifying %L is preferred as it avoids the need to probe for the application type.\n"
"%d - desktop absolute parsing name of the first parameter (for items that don't have file system paths)\n"
"%v - for verbs that are none implies all, if there is no parameter passed this is the working directory\n"
"%w - the working directory\n");
  g_hash_table_iter_init (&url_iter, urls);

  while (g_hash_table_iter_next (&url_iter, (gpointer *) &url, (gpointer *) &handlers))
  {
    GHashTableIter handler_iter;
    gchar *handler_folded, *handler;

    g_hash_table_iter_init (&handler_iter, handlers);
    g_print ("%s is handled by:\n", url);

    while (g_hash_table_iter_next (&handler_iter, (gpointer *) &handler_folded, (gpointer *) &handler))
      g_print ("  %s\n", handler);
  }

  g_hash_table_destroy (urls);
  return 0;
}
