/* -*- mode: C; c-basic-offset: 4 -*-
 * themus - utilities for GNOME themes
 * Copyright (C) 2003  Andrew Sobala <aes@gnome.org>
 *
 * This library 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.1 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU 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
*/

#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <libintl.h>

#include <glib/gi18n.h>
#include <libgnomevfs/gnome-vfs.h>
#include <libgnomevfs/gnome-vfs-cancellable-ops.h>
#include <libgnomevfs/gnome-vfs-module.h>

#include <gnome-theme-info.h>

#define THEME_METHOD_DIRECTORY DIRECTORY_DIR "/theme-method.directory"

/* this is from gnome-vfs-monitor-private.h */
void gnome_vfs_monitor_callback (GnomeVFSMethodHandle *method_handle,
                                 GnomeVFSURI *info_uri,
                                 GnomeVFSMonitorEventType event_type);

static void invoke_monitors(void);

/* cc cut-paste */
static gboolean
transfer_done_targz_idle_cb (gpointer data)
{
	int status;
	gchar *command;
	gchar *path = data;

	/* this should be something more clever and nonblocking */
	command = g_strdup_printf ("sh -c 'cd \"%s/.themes\"; gzip -d -c < \"%s\" | tar xf -'",
				   g_get_home_dir (), path);
	if (g_spawn_command_line_sync (command, NULL, NULL, &status, NULL) && status == 0)
		gnome_vfs_unlink (path);
	g_free (command);
	g_free (path);

	return FALSE;
}

static gboolean
transfer_done_tarbz2_idle_cb (gpointer data)
{
	int status;
	gchar *command;
	gchar *path = data;

	/* this should be something more clever and nonblocking */
	command = g_strdup_printf ("sh -c 'cd \"%s/.themes\"; bzip2 -d -c < \"%s\" | tar xf -'",
				   g_get_home_dir (), path);
	if (g_spawn_command_line_sync (command, NULL, NULL, &status, NULL) && status == 0)
		gnome_vfs_unlink (path);
	g_free (command);
	g_free (path);

	return FALSE;
}
/* end cut-paste*/

static gchar *
get_path_from_uri (GnomeVFSURI const *uri)
{
    gchar *path;
    gint len;

    path = gnome_vfs_unescape_string (uri->text,
				      G_DIR_SEPARATOR_S);

    if (path == NULL) {
	return NULL;
    }

    if (path[0] != G_DIR_SEPARATOR) {
	g_free (path);
	return NULL;
    }

    len = strlen(path);
    if (path[len-1] == G_DIR_SEPARATOR) path[len-1] = '\0';

    return path;
}

static GnomeVFSURI *
create_local_uri(const GnomeVFSURI *orig_uri)
{
    gchar *themedir, *themedir_escaped, *basename;
    GnomeVFSURI *themedir_uri, *new_uri;

    /* make sure ~/.themes exists ... */
    themedir = g_strconcat(g_get_home_dir(), G_DIR_SEPARATOR_S, ".themes",NULL);
    if (mkdir(themedir, 0755) && errno != EEXIST) {
	g_free(themedir);
	return NULL;
    }
    /* get URI */
    themedir_escaped = gnome_vfs_get_uri_from_local_path(themedir);
    g_free(themedir);
    themedir_uri = gnome_vfs_uri_new(themedir_escaped);
    g_free(themedir_escaped);

    basename = gnome_vfs_uri_extract_short_name(orig_uri);
    new_uri = gnome_vfs_uri_append_file_name(themedir_uri, basename);
    g_free(basename);
    gnome_vfs_uri_unref(themedir_uri);
    return new_uri;
}

static gint
theme_meta_info_sort_func (GnomeThemeMetaInfo *a, GnomeThemeMetaInfo *b)
{
	return strcmp (a->readable_name, b->readable_name);
}

static GnomeThemeMetaInfo*
theme_meta_info_find (GnomeVFSURI *uri)
{
	GList *theme;
	gchar *path;

	path = get_path_from_uri (uri);
	for (theme = gnome_theme_meta_info_find_all (); theme != NULL;
								theme=theme->next)
		if (!strcmp ( g_strconcat ("/",
			((GnomeThemeMetaInfo*)(theme->data))->readable_name, NULL),
			path))
			{
				g_free (path);
				return ((GnomeThemeMetaInfo*)(theme->data));
			}

	g_free (path);
	return NULL;
}

static GnomeVFSResult
fill_info_struct (GnomeVFSFileInfo *file_info, GnomeVFSFileInfoOptions options,
GnomeThemeMetaInfo *theme)
{
	GnomeVFSURI *theme_uri;
	GnomeVFSResult result;

	theme_uri = gnome_vfs_uri_new (theme->path);
	result = gnome_vfs_get_file_info_uri (theme_uri, file_info, options);
	gnome_vfs_uri_unref (theme_uri);

	if (result == GNOME_VFS_OK)
	{
		g_free (file_info->name);
		file_info->name = g_strdup (theme->readable_name);

		file_info->valid_fields &= ~GNOME_VFS_FILE_INFO_FIELDS_SYMLINK_NAME;
		g_free (file_info->symlink_name);
		file_info->symlink_name = NULL;
		GNOME_VFS_FILE_INFO_SET_SYMLINK (file_info, FALSE);
		file_info->type = GNOME_VFS_FILE_TYPE_REGULAR;

		g_free (file_info->mime_type);
		file_info->mime_type = g_strdup ("application/x-gnome-theme-installed");
		file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
	}

	return result;
}

typedef struct _ThemeHandle ThemeHandle;
struct _ThemeHandle {
    GnomeVFSMethodHandle *handle;

    /* if we're listing themes.... */
    GList *theme;
    GnomeVFSFileInfoOptions options;
    gboolean seen_dotdirectory;

    /* if we're doing a file.... */
    gchar *uri; /* the real URI */
};

/* -- VFS method -- */

static GnomeVFSResult
do_open_directory(GnomeVFSMethod *method,
		  GnomeVFSMethodHandle **method_handle,
		  GnomeVFSURI *uri,
		  GnomeVFSFileInfoOptions options,
		  GnomeVFSContext *context)
{
	gchar* path;
	ThemeHandle *handle;
	path = get_path_from_uri (uri);
	if (!strcmp (path, ""))
	{
		handle = g_new0(ThemeHandle, 1);
		handle->handle = (GnomeVFSMethodHandle *) method_handle;
		handle->theme = gnome_theme_meta_info_find_all ();
		handle->theme = g_list_sort (handle->theme,
					(GCompareFunc)theme_meta_info_sort_func);
		handle->options = options;
		handle->seen_dotdirectory = FALSE;
		*method_handle = (GnomeVFSMethodHandle *)handle;
		g_free (path);
		return GNOME_VFS_OK;
	}
	else {
		g_free (path);
		return GNOME_VFS_ERROR_NOT_FOUND;
	}
}

static GnomeVFSResult
do_close_directory(GnomeVFSMethod *method,
		   GnomeVFSMethodHandle *method_handle,
		   GnomeVFSContext *context)
{
	g_free ((ThemeHandle*)method_handle);
	return GNOME_VFS_OK;
}

static GnomeVFSResult
do_read_directory(GnomeVFSMethod *method,
		  GnomeVFSMethodHandle *method_handle,
		  GnomeVFSFileInfo *file_info,
		  GnomeVFSContext *context)
{
	ThemeHandle *handle = (ThemeHandle *) method_handle;
	GList *theme = handle->theme;

	if (!handle->seen_dotdirectory)
	{
		g_free (file_info->name);
		file_info->name = g_strdup (".directory");
		file_info->type = GNOME_VFS_FILE_TYPE_REGULAR;
		file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE;
		file_info->mime_type = g_strdup("application/x-gnome-app-info");
		file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
		file_info->permissions = GNOME_VFS_PERM_USER_ALL |
					 GNOME_VFS_PERM_GROUP_READ |
					 GNOME_VFS_PERM_OTHER_READ;
		file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS;
		handle->seen_dotdirectory = TRUE;
		return GNOME_VFS_OK;
	}

	if (theme == NULL)
	{
		return GNOME_VFS_ERROR_EOF;
	}

	fill_info_struct (file_info, handle->options,
			  (GnomeThemeMetaInfo *) theme->data);

	handle->theme = handle->theme->next;
	return GNOME_VFS_OK;
}

/* -------- handling of file objects -------- */

static GnomeVFSResult
do_open(GnomeVFSMethod *method,
	GnomeVFSMethodHandle **method_handle,
	GnomeVFSURI *uri,
	GnomeVFSOpenMode mode,
	GnomeVFSContext *context)
{
	gchar *path;
	GnomeVFSResult result;
	GnomeThemeMetaInfo *theme;
	GnomeVFSURI *theme_uri;
	ThemeHandle *handle;

	path = get_path_from_uri(uri);

	if (!path) {
		return GNOME_VFS_ERROR_INVALID_URI;
	}

	if (path[0] == '\0')
	{
		g_free (path);
		return GNOME_VFS_ERROR_IS_DIRECTORY;
	}

	/* FIXME: ro check */

	/* handle the .directory file */
	if (!strcmp(path, "/.directory")) {
		uri = gnome_vfs_uri_new(THEME_METHOD_DIRECTORY);
		result = gnome_vfs_open_uri_cancellable(
			(GnomeVFSHandle **)method_handle, uri, mode, context);

		handle = g_new0(ThemeHandle, 1);
		handle->handle = *method_handle;
		*method_handle = (GnomeVFSMethodHandle *) handle;

		g_free (path);
		gnome_vfs_uri_unref(uri);
		return result;
	}

	g_free (path);

	theme = theme_meta_info_find (uri);
	if (theme)
	{
		theme_uri = gnome_vfs_uri_new (theme->path);
		result = gnome_vfs_open_uri_cancellable(
			(GnomeVFSHandle **)method_handle, theme_uri, mode, context);

		handle = g_new0(ThemeHandle, 1);
		handle->handle = *method_handle;
		handle->uri = gnome_vfs_uri_to_string (theme_uri,
						GNOME_VFS_URI_HIDE_TOPLEVEL_METHOD);
		*method_handle = (GnomeVFSMethodHandle *) handle;

		gnome_vfs_uri_unref (theme_uri);
		return result;
	}
	else
		return GNOME_VFS_ERROR_NOT_FOUND;
}

static GnomeVFSResult
do_create(GnomeVFSMethod *method,
	  GnomeVFSMethodHandle **method_handle,
	  GnomeVFSURI *uri,
	  GnomeVFSOpenMode mode,
	  gboolean exclusive,
	  guint perm,
	  GnomeVFSContext *context)
{
    GnomeVFSResult result;
    GnomeVFSURI *new_uri;
    ThemeHandle *handle;

    new_uri = create_local_uri(uri);
    if (!new_uri)
	return gnome_vfs_result_from_errno();

    result = gnome_vfs_create_uri_cancellable((GnomeVFSHandle **)method_handle,
					      new_uri, mode, exclusive, perm,
					      context);

    handle = g_new0 (ThemeHandle, 1);
    handle->uri = gnome_vfs_uri_to_string (new_uri,
					GNOME_VFS_URI_HIDE_TOPLEVEL_METHOD);
    handle->handle = *method_handle;
    *method_handle = (GnomeVFSMethodHandle*) handle;

    gnome_vfs_uri_unref(new_uri);

    return result;
}


static GnomeVFSResult
do_close(GnomeVFSMethod *method,
	 GnomeVFSMethodHandle *method_handle,
	 GnomeVFSContext *context)
{
	GnomeVFSResult result;
	gchar* path = NULL;
	gint len;

	path = ((ThemeHandle*) method_handle)->uri;
	result = gnome_vfs_close_cancellable ((GnomeVFSHandle*)(((ThemeHandle*)(method_handle))->handle), context);
	g_free (method_handle);

	if (result != GNOME_VFS_OK)
		return result;

	if (path) {
		len = strlen (path);
		if (path && len > 7 && !strcmp (path + len - 7, ".tar.gz"))
			transfer_done_targz_idle_cb (path);
		if (path && len > 8 && !strcmp (path + len - 8, ".tar.bz2"))
			transfer_done_tarbz2_idle_cb (path);

		invoke_monitors ();
	}

	return result;

}

static GnomeVFSResult
do_read(GnomeVFSMethod *method,
	GnomeVFSMethodHandle *method_handle,
	gpointer buffer,
	GnomeVFSFileSize bytes,
	GnomeVFSFileSize *bytes_read,
	GnomeVFSContext *context)
{
	return gnome_vfs_read_cancellable(
			(GnomeVFSHandle*)(((ThemeHandle*)(method_handle))->handle),
			buffer, bytes, bytes_read, context);
}

static GnomeVFSResult
do_write(GnomeVFSMethod *method,
	 GnomeVFSMethodHandle *method_handle,
	 gconstpointer buffer,
	 GnomeVFSFileSize bytes,
	 GnomeVFSFileSize *bytes_written,
	 GnomeVFSContext *context)
{
	return gnome_vfs_write_cancellable(
			(GnomeVFSHandle*)(((ThemeHandle*)(method_handle))->handle),
			buffer, bytes, bytes_written, context);
}

static GnomeVFSResult
do_seek(GnomeVFSMethod *method,
	GnomeVFSMethodHandle *method_handle,
	GnomeVFSSeekPosition whence,
	GnomeVFSFileOffset offset,
	GnomeVFSContext *context)
{
	return gnome_vfs_seek_cancellable(
			(GnomeVFSHandle*)(((ThemeHandle*)(method_handle))->handle),
			whence, offset, context);
}

static GnomeVFSResult
do_tell(GnomeVFSMethod *method,
	GnomeVFSMethodHandle *method_handle,
	GnomeVFSFileSize *offset_return)
{
	return gnome_vfs_tell(
			(GnomeVFSHandle*)(((ThemeHandle*)(method_handle))->handle),
			offset_return);
}

static GnomeVFSResult
do_get_file_info_from_handle(GnomeVFSMethod *method,
			     GnomeVFSMethodHandle *method_handle,
			     GnomeVFSFileInfo *file_info,
			     GnomeVFSFileInfoOptions options,
			     GnomeVFSContext *context)
{
	return gnome_vfs_get_file_info_from_handle_cancellable (
			(GnomeVFSHandle*)(((ThemeHandle*)(method_handle))->handle),
			file_info, options, context);
}


/* -------- file metadata -------- */

static GnomeVFSResult
do_get_file_info(GnomeVFSMethod *method,
		 GnomeVFSURI *uri,
		 GnomeVFSFileInfo *file_info,
		 GnomeVFSFileInfoOptions options,
		 GnomeVFSContext *context)
{
	gchar *path = NULL;
	GnomeThemeMetaInfo *theme;

	path = get_path_from_uri(uri);
	if (!path)
		return GNOME_VFS_ERROR_INVALID_URI;

	/* root directory */
	if (!strcmp(path, "")) {
		g_free(file_info->name);
		file_info->name = g_strdup(_("Themes"));

		file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY;
		file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE;

		g_free(file_info->mime_type);
		file_info->mime_type = g_strdup("x-directory/normal");
		file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;

		file_info->permissions = GNOME_VFS_PERM_USER_READ |
					 GNOME_VFS_PERM_GROUP_READ |
					 GNOME_VFS_PERM_OTHER_READ;
		file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS;

		g_free (path);
		return GNOME_VFS_OK;
	}
	else {
		g_free (path);

		theme = theme_meta_info_find (uri);
		if (theme)
			return fill_info_struct (file_info, options, theme);
	}

	return GNOME_VFS_ERROR_INTERNAL;
}

static gboolean
do_is_local(GnomeVFSMethod *method,
            const GnomeVFSURI *uri)
{
	return TRUE;
}

static GnomeVFSResult
do_unlink(GnomeVFSMethod *method,
	  GnomeVFSURI *uri,
	  GnomeVFSContext *context)
{
	/* we can only safely delete the metatheme; subthemes may be in use by other
	metathemes */

	if (!strcmp (gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE),
			"themes:///.vfs-write.tmp"))
	{
		return gnome_vfs_unlink (g_strconcat (g_get_home_dir(),
				G_DIR_SEPARATOR_S, ".themes", G_DIR_SEPARATOR_S,
				".vfs-write.tmp", NULL));
		/* yuck */
	}

	if (!strcmp (gnome_vfs_uri_get_scheme (uri), "themes"))
	{
		GnomeThemeMetaInfo *theme;
		GnomeVFSResult result;

		theme = theme_meta_info_find (uri);
		if (theme) {
			result = gnome_vfs_unlink (theme->path);
			invoke_monitors ();
			return result;
		}
		else
			return GNOME_VFS_ERROR_INTERNAL;
	} else
		return GNOME_VFS_OK; /* FIXME */
}


/* -------- Directory monitor -------- */

/* list of monitors attached to themes:/// */
G_LOCK_DEFINE_STATIC(monitor_list);
static GList *monitor_list = NULL;

static void
invoke_monitors()
{
    GList *tmp;

    G_LOCK(monitor_list);
    for (tmp = monitor_list; tmp != NULL; tmp = tmp->next) {
	GnomeVFSURI *uri = tmp->data;

	gnome_vfs_monitor_callback((GnomeVFSMethodHandle *)uri, uri,
				   GNOME_VFS_MONITOR_EVENT_CHANGED);
    }
    G_UNLOCK(monitor_list);
}

static void
theme_changed_callback(GnomeThemeType       type,
                       gpointer             theme,
                       GnomeThemeChangeType change_type,
                       GnomeThemeElement    element,
                       gpointer             data)
{
  invoke_monitors();
}

static GnomeVFSResult
do_monitor_add(GnomeVFSMethod *method,
	       GnomeVFSMethodHandle **method_handle,
	       GnomeVFSURI *uri,
	       GnomeVFSMonitorType monitor_type)
{
    char *path = NULL;
    GnomeVFSURI *new_uri;
    GnomeVFSResult result;

    path = get_path_from_uri(uri);
    if (!path)  /* invalid path */
	result = GNOME_VFS_ERROR_INVALID_URI;
    else
    {

	    if (path[0] == '\0' && monitor_type == GNOME_VFS_MONITOR_DIRECTORY) {
		/* it is a directory monitor on themes:/// */
		new_uri = gnome_vfs_uri_dup(uri);
		*method_handle = (GnomeVFSMethodHandle *)new_uri;
		G_LOCK(monitor_list);
		monitor_list = g_list_prepend(monitor_list, new_uri);
		G_UNLOCK(monitor_list);
		result = GNOME_VFS_OK;
	    }
	    else if (path[0] == '\0' && monitor_type != GNOME_VFS_MONITOR_DIRECTORY)
    		result = GNOME_VFS_ERROR_NOT_SUPPORTED;
	    else {
    		/* it is a theme monitor */
		result = GNOME_VFS_ERROR_NOT_SUPPORTED; /* though it should be */
	    }
    }

    g_free(path);
    return result;
}

static GnomeVFSResult
do_monitor_cancel(GnomeVFSMethod *method,
		  GnomeVFSMethodHandle *method_handle)
{
    GnomeVFSURI *uri;

    uri = (GnomeVFSURI *)method_handle;
    G_LOCK(monitor_list);
    monitor_list = g_list_remove(monitor_list, uri);
    G_UNLOCK(monitor_list);
    gnome_vfs_uri_unref(uri);
    return GNOME_VFS_OK;
}

/* -- method externs -- */

static GnomeVFSMethod method = {
    sizeof(GnomeVFSMethod),

    do_open,
    do_create,
    do_close,
    do_read,
    do_write,
    do_seek,
    do_tell,

    NULL,

    do_open_directory,
    do_close_directory,
    do_read_directory,
    do_get_file_info,
    do_get_file_info_from_handle,
    do_is_local,

    NULL,
    NULL,
    NULL,

    do_unlink,

    NULL,
    NULL,
    NULL,
    NULL,
    NULL,

    do_monitor_add,
    do_monitor_cancel,

    NULL
};

GnomeVFSMethod*
vfs_module_init (const char *method_name, const char *args)
{
	gnome_theme_init (NULL);
	if (!strcmp (method_name, "themes"))
	{
		gnome_theme_info_register_theme_change (theme_changed_callback, NULL);
		return &method;
	}
	else
		return NULL;
}

void
vfs_module_shutdown (GnomeVFSMethod* method)
{

}
