/***************************************************************************
 *            brasero-sum-dialog.c
 *
 *  ven sep  1 19:35:13 2006
 *  Copyright  2006  Rouquier Philippe
 *  bonfire-app@wanadoo.fr
 ***************************************************************************/

/*
 *  This program 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 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <string.h>
#include <fcntl.h>

#include <glib.h>
#include <glib/gi18n-lib.h>

#include <gtk/gtk.h>

#include <gtk/gtktreeview.h>
#include <gtk/gtktreemodel.h>
#include <gtk/gtkliststore.h>
#include <gtk/gtkcellrenderertext.h>
#include <gtk/gtkscrolledwindow.h>
#include <gtk/gtkbutton.h>
#include <gtk/gtkbox.h>
#include <gtk/gtkwindow.h>
#include <gtk/gtkdialog.h>
#include <gtk/gtkmessagedialog.h>

#include <libgnomevfs/gnome-vfs.h>
#include <nautilus-burn-drive.h>

#include "brasero-sum-dialog.h"
#include "brasero-tool-dialog.h"
#include "burn-sum.h"
#include "brasero-ncb.h"

static void brasero_sum_dialog_class_init (BraseroSumDialogClass *klass);
static void brasero_sum_dialog_init (BraseroSumDialog *sp);
static void brasero_sum_dialog_finalize (GObject *object);

struct _BraseroSumDialogPrivate {
	GSList *wrong_sums;
	GMainLoop *loop;
	gint channel_id;

	gint files_num;
	gint files_nb;
};

static GtkDialogClass *parent_class = NULL;

static gboolean brasero_sum_dialog_cancel (BraseroToolDialog *dialog);
static void brasero_sum_dialog_button_clicked (BraseroToolDialog *dialog);
static void brasero_sum_dialog_media_changed (BraseroToolDialog *dialog,
					       NautilusBurnMediaType media);

GType
brasero_sum_dialog_get_type ()
{
	static GType type = 0;

	if(type == 0) {
		static const GTypeInfo our_info = {
			sizeof (BraseroSumDialogClass),
			NULL,
			NULL,
			(GClassInitFunc)brasero_sum_dialog_class_init,
			NULL,
			NULL,
			sizeof (BraseroSumDialog),
			0,
			(GInstanceInitFunc)brasero_sum_dialog_init,
		};

		type = g_type_register_static (BRASERO_TYPE_TOOL_DIALOG, 
					       "BraseroSumDialog",
					       &our_info,
					       0);
	}

	return type;
}

static void
brasero_sum_dialog_class_init (BraseroSumDialogClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	BraseroToolDialogClass *tool_dialog_class = BRASERO_TOOL_DIALOG_CLASS (klass);

	parent_class = g_type_class_peek_parent (klass);
	object_class->finalize = brasero_sum_dialog_finalize;

	tool_dialog_class->button_clicked = brasero_sum_dialog_button_clicked;
	tool_dialog_class->media_changed = brasero_sum_dialog_media_changed;
	tool_dialog_class->cancel = brasero_sum_dialog_cancel;
}

static void
brasero_sum_dialog_init (BraseroSumDialog *obj)
{
	obj->priv = g_new0 (BraseroSumDialogPrivate, 1);

	brasero_tool_dialog_set_button (BRASERO_TOOL_DIALOG (obj),
					_("Check"),
					GTK_STOCK_FIND);
}

static void
brasero_sum_dialog_finalize (GObject *object)
{
	BraseroSumDialog *cobj;

	cobj = BRASERO_SUM_DIALOG (object);

	if (cobj->priv->loop)
		g_main_loop_quit (cobj->priv->loop);

	if (cobj->priv->wrong_sums) {
		g_slist_foreach (cobj->priv->wrong_sums, (GFunc) g_free, NULL);
		g_slist_free (cobj->priv->wrong_sums);
		cobj->priv->wrong_sums = NULL;
	}

	if (cobj->priv->channel_id) {
		g_source_remove (cobj->priv->channel_id);
		cobj->priv->channel_id = 0;
	}

	g_free (cobj->priv);
	G_OBJECT_CLASS (parent_class)->finalize (object);
}

GtkWidget *
brasero_sum_dialog_new ()
{
	BraseroSumDialog *obj;
	
	obj = BRASERO_SUM_DIALOG (g_object_new (BRASERO_TYPE_SUM_DIALOG, NULL));
	
	return GTK_WIDGET (obj);
}

static gboolean
brasero_sum_dialog_cancel (BraseroToolDialog *dialog)
{
	BraseroSumDialog *self;

	self = BRASERO_SUM_DIALOG (dialog);

	if (self->priv->loop
	&&  g_main_loop_is_running (self->priv->loop)) {
		g_main_loop_quit (self->priv->loop);
	}

	if (self->priv->channel_id) {
		g_source_remove (self->priv->channel_id);
		self->priv->channel_id = 0;
	}

	return TRUE;
}

static void
brasero_sum_dialog_media_changed (BraseroToolDialog *dialog,
				  NautilusBurnMediaType media)
{
	
}

static void
brasero_sum_dialog_message (BraseroSumDialog *self,
			    const gchar *title,
			    const gchar *primary_message,
			    const gchar *secondary_message,
			    GtkMessageType type)
{
	GtkWidget *message;

	message = gtk_message_dialog_new (GTK_WINDOW (self),
					  GTK_DIALOG_MODAL |
					  GTK_DIALOG_DESTROY_WITH_PARENT,
					  type,
					  GTK_BUTTONS_CLOSE,
					  primary_message);

	gtk_window_set_title (GTK_WINDOW (message), title);

	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message),
						  secondary_message);
	gtk_dialog_run (GTK_DIALOG (message));
	gtk_widget_destroy (message);
}

static void
brasero_sum_dialog_no_md5_file_warning (BraseroSumDialog *self)
{
	brasero_sum_dialog_message (self,
				    _("Missing md5 file"),
				    _("The file integrity check cannot be performed:"),
				    _("the md5 checksum file is missing."),
				    GTK_MESSAGE_ERROR);
}

static void
brasero_sum_dialog_no_mount_warning (BraseroSumDialog *self)
{
	brasero_sum_dialog_message (self,
				    _("Error while mounting disc"),
				    _("The file integrity check cannot be performed:"),
				    _("the disc mount point cannot be retrieved."),
				    GTK_MESSAGE_ERROR);
}

static void
brasero_sum_dialog_drive_mount_error (BraseroSumDialog *self,
				      const gchar *error,
				      const gchar *detailed_error)
{
	brasero_sum_dialog_message (self,
				    _("Error while mounting disc"),
				    _("The file integrity check cannot be performed:"),
				    detailed_error,
				    GTK_MESSAGE_ERROR);
}

static void
brasero_sum_dialog_fork_error (BraseroSumDialog *self,
			       GError *error)
{
	brasero_sum_dialog_message (self,
				    _("Error while starting md5sum"),
				    _("The file integrity check cannot be performed:"),
				    error->message,
				    GTK_MESSAGE_ERROR);
}

static void
brasero_sum_dialog_success (BraseroSumDialog *self)
{
	brasero_sum_dialog_message (self,
				    _("File integrity check success"),
				    _("The file integrity was performed successfully:"),
				    _("there is no corrupted file on the disc."),
				    GTK_MESSAGE_INFO);
}

enum {
	BRASERO_SUM_DIALOG_PATH,
	BRASERO_SUM_DIALOG_NB_COL
};

static void
brasero_sum_dialog_corruption_warning (BraseroSumDialog *self)
{
	GSList *iter;
	GtkWidget *scroll;
	GtkWidget *tree;
	GtkWidget *message;
	GtkTreeModel *model;
	GtkCellRenderer *renderer;
	GtkTreeViewColumn *column;

	message = gtk_message_dialog_new_with_markup (GTK_WINDOW (self),
						      GTK_DIALOG_MODAL |
						      GTK_DIALOG_DESTROY_WITH_PARENT,
						      GTK_MESSAGE_ERROR,
						      GTK_BUTTONS_CLOSE,
						      _("<b><big>The following files appear to be corrupted:</big></b>"));

	gtk_window_set_title (GTK_WINDOW (message),  _("File integrity check error"));
	gtk_window_set_resizable (GTK_WINDOW (message), TRUE);
	gtk_widget_set_size_request (GTK_WIDGET (message), 440, 300);

	/* build a list */
	model = GTK_TREE_MODEL (gtk_list_store_new (BRASERO_SUM_DIALOG_NB_COL, G_TYPE_STRING));
	for (iter = self->priv->wrong_sums; iter; iter = iter->next) {
		gchar *path;
		GtkTreeIter tree_iter;

		path = iter->data;
		gtk_list_store_append (GTK_LIST_STORE (model), &tree_iter);
		gtk_list_store_set (GTK_LIST_STORE (model), &tree_iter,
				    BRASERO_SUM_DIALOG_PATH, path,
				    -1);

		g_free (path);
	}
	g_slist_free (self->priv->wrong_sums);
	self->priv->wrong_sums = NULL;

	tree = gtk_tree_view_new_with_model (model);
	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (tree), TRUE);

	column = gtk_tree_view_column_new ();
	renderer = gtk_cell_renderer_text_new ();
	gtk_tree_view_column_pack_start (column, renderer, TRUE);
	gtk_tree_view_column_add_attribute (column, renderer,
					    "text", BRASERO_SUM_DIALOG_PATH);
	gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
	gtk_tree_view_column_set_title (column, _("Corrupted files"));

	scroll = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll),
					     GTK_SHADOW_ETCHED_IN);

	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll),
					GTK_POLICY_AUTOMATIC,
					GTK_POLICY_AUTOMATIC);
	gtk_container_add (GTK_CONTAINER (scroll), tree);
	gtk_box_pack_start (GTK_BOX (GTK_DIALOG (message)->vbox),
			    scroll, 
			    TRUE,
			    TRUE,
			    0);

	gtk_widget_show_all (scroll);

	gtk_dialog_run (GTK_DIALOG (message));
	gtk_widget_destroy (message);
}

static void
brasero_sum_dialog_check_status (BraseroSumDialog *self,
				 const gchar *line)
{
	gdouble progress;
	gchar *name_end;
	gchar *status;
	gchar *action;
	gchar *name;

	name_end = strrchr (line, ':');

	name = g_strndup (line, name_end - line);
	action = g_strdup_printf (_("Checking %s"), name);
	brasero_tool_dialog_set_action (BRASERO_TOOL_DIALOG (self),
					BRASERO_BURN_ACTION_ANALYSING,
					action);
	g_free (action);

	self->priv->files_nb ++;
	progress = (gdouble) self->priv->files_nb /
		   (gdouble) self->priv->files_num;

	brasero_tool_dialog_set_progress (BRASERO_TOOL_DIALOG (self),
					  progress,
					  -1.0,
					  -1,
					  -1,
					  -1);
					  
	/* check the status for the file */
	status = name_end + 2;
	if (status && strcmp (status, "OK\n")) {
		/* something went wrong */
		self->priv->wrong_sums = g_slist_prepend (self->priv->wrong_sums,
							  name);
	}
	else
		g_free (name);
}

static gboolean
brasero_sum_dialog_read_channel (GIOChannel *channel,
				 GIOCondition condition,
				 gpointer user_data)
{
	BraseroSumDialog *self;
	GIOStatus status;

	self = BRASERO_SUM_DIALOG (user_data);

	if (condition & G_IO_IN) {
		gchar *line = NULL;

		status = g_io_channel_read_line (channel,
						 &line,
						 NULL,
						 NULL,
						 NULL);
		if (status == G_IO_STATUS_AGAIN) {
			/* line is NULL since there wasn't any character ending
			 * the line and so it wasn't read at all.*/
			return TRUE;
		}
		else if (status == G_IO_STATUS_NORMAL) {
			brasero_sum_dialog_check_status (self, line);
			return TRUE;
		}
		else if (status == G_IO_STATUS_EOF)
			goto exit;
	}
	else if (condition & G_IO_HUP)
		goto exit;

	return TRUE;
	
exit:

	if (self->priv->loop
	&&  g_main_loop_is_running (self->priv->loop))
		g_main_loop_quit (self->priv->loop);

	self->priv->channel_id = 0;
	return FALSE;
}

static gboolean
brasero_sum_dialog_get_line_num (BraseroSumDialog *self,
				 const gchar *path)
{
	gint c;
	FILE *file;
	gint num = 0;

	file = fopen (path, "r");
	if (!file)
		return FALSE;

	while ((c = getc (file)) != EOF) {
		if (c == '\n')
			num ++;
	}
	fclose (file);

	self->priv->files_num = num;
	return TRUE;
}

static gboolean
brasero_sum_dialog_drive_mounted (BraseroSumDialog *self,
				  GnomeVFSDrive *drive)
{
	GList *iter, *volumes;
	GError *error = NULL;
	GIOChannel *channel;
	gchar *mount_point;
	gchar *local_path;
	gboolean success;
	gchar *file;
	GPid pid;
	gint fd;

	gchar *argv [] = { 	"md5sum",
				"-c",
				BRASERO_CHECKSUM_FILE,
				NULL };
	/* we need to do that to avoid having md5sum translated */
	gchar *envp [] = {	"LANG=C",
				"LANGUAGE=C"
				"LC_ALL=C",
				NULL};

	/* get the uri for the mount point */
	volumes = gnome_vfs_drive_get_mounted_volumes (drive);
	for (iter = volumes; iter; iter = iter->next) {
		GnomeVFSVolume *volume;

		volume = iter->data;
		mount_point = gnome_vfs_volume_get_activation_uri (volume);
	}

	if (!mount_point || strncmp (mount_point, "file://", 7)) {
		/* mount point won't be usable */
		if (mount_point)
			g_free (mount_point);

		brasero_sum_dialog_no_mount_warning (self);
		return FALSE;
	}

	local_path = gnome_vfs_get_local_path_from_uri (mount_point);
	g_free (mount_point);

	/* test if the md5 file exists at the root of the disc */
	file = g_build_path (G_DIR_SEPARATOR_S,
			     local_path,
			     BRASERO_CHECKSUM_FILE,
			     NULL);

	if (!g_file_test (file, G_FILE_TEST_IS_REGULAR)) {
		g_free (file);
		g_free (local_path);

		brasero_sum_dialog_no_md5_file_warning (self);
		return FALSE;
	}

	/* now we need to know the number of lines in the file to be able to 
	 * report the progress. Count the number of "\n" characters. */
	if (!brasero_sum_dialog_get_line_num (self, file)) {
		g_free (file);
		g_free (local_path);

		brasero_sum_dialog_no_md5_file_warning (self);
		return FALSE;
	}
	g_free (file);

	success = g_spawn_async_with_pipes (local_path,
					    argv,
					    envp,
					    G_SPAWN_SEARCH_PATH,
					    NULL,
					    NULL,
					    &pid,
					    NULL,
					    &fd,
					    NULL,
					    &error);
	g_free (local_path);
	
	if (!success) {
		/* something went wrong during the execution */
		brasero_sum_dialog_fork_error (self, error);
		return FALSE;
	}

	/* setup channels */
	fcntl (fd, F_SETFL, O_NONBLOCK);
	channel = g_io_channel_unix_new (fd);
	g_io_channel_set_flags (channel,
				g_io_channel_get_flags (channel) | G_IO_FLAG_NONBLOCK,
				NULL);
	g_io_channel_set_encoding (channel, NULL, NULL);
	self->priv->channel_id = g_io_add_watch (channel,
						 (G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL),
						 (GIOFunc) brasero_sum_dialog_read_channel,
						 self);
	g_io_channel_unref (channel);
	
	return TRUE;
}

typedef struct _DiscMountData {
	BraseroSumDialog *sum;
	gboolean called;
	gboolean result;
} DiscMountData;

static void
brasero_sum_dialog_drive_mount_result (gboolean succeeded,
				       gchar *error,
				       gchar *detailed_error,
				       gpointer user_data)
{
	DiscMountData *data = user_data;

	if (!succeeded 
	&& (error || detailed_error))
		brasero_sum_dialog_drive_mount_error (data->sum,
						      error,
						      detailed_error);

	data->result = succeeded;
	data->called = TRUE;
}

static void
brasero_sum_dialog_drive_found (BraseroSumDialog *self,
				GnomeVFSDrive *drive)
{
	if (!gnome_vfs_drive_is_mounted (drive)) {
		DiscMountData data = { FALSE, FALSE };

		/* try to mount the disc */
		data.sum = self;
		gnome_vfs_drive_mount (drive,
				       brasero_sum_dialog_drive_mount_result,
				       &data);

		while (!data.called) gtk_main_iteration ();

		if (!data.result)
			return;
	}
	
	if (!brasero_sum_dialog_drive_mounted (self, drive))
		return;

	/* go into a loop until everything is finished */
	self->priv->loop = g_main_loop_new (NULL, FALSE);
	g_main_loop_run (self->priv->loop);

	brasero_tool_dialog_set_action (BRASERO_TOOL_DIALOG (self),
					BRASERO_BURN_ACTION_NONE,
					NULL);

	g_main_loop_unref (self->priv->loop);
	self->priv->loop = NULL;

	if (self->priv->wrong_sums) {
		brasero_sum_dialog_corruption_warning (self);

		g_slist_foreach (self->priv->wrong_sums, (GFunc) g_free, NULL);
		g_slist_free (self->priv->wrong_sums);
		self->priv->wrong_sums = NULL;
	}
	else
		brasero_sum_dialog_success (self);
}

static void
brasero_sum_dialog_button_clicked (BraseroToolDialog *dialog)
{
	GnomeVFSVolumeMonitor *monitor;
	NautilusBurnDrive *drive;
	BraseroSumDialog *self;
	GList *drives;
	GList *iter;

	self = BRASERO_SUM_DIALOG (dialog);

	drive = brasero_tool_dialog_get_drive (dialog);
	if (!drive) {
		gtk_widget_destroy (GTK_WIDGET (self));
		return;
	}

	monitor = gnome_vfs_get_volume_monitor ();
	drives = gnome_vfs_volume_monitor_get_connected_drives (monitor);
	for (iter = drives; iter; iter = iter->next) {
		GnomeVFSDeviceType device_type;
		GnomeVFSDrive *vfs_drive;
		gchar *device_path;

		vfs_drive = iter->data;

		device_type = gnome_vfs_drive_get_device_type (vfs_drive);
		if (device_type != GNOME_VFS_DEVICE_TYPE_AUDIO_CD
		&&  device_type != GNOME_VFS_DEVICE_TYPE_VIDEO_DVD
		&&  device_type != GNOME_VFS_DEVICE_TYPE_CDROM)
			continue;

		device_path = gnome_vfs_drive_get_device_path (vfs_drive);
		if (!strcmp (device_path, NCB_DRIVE_GET_DEVICE (drive))) {
			brasero_sum_dialog_drive_found (self, vfs_drive);
			g_free (device_path);
			break;
		}

		g_free (device_path);
	}

	g_list_foreach (drives, (GFunc) gnome_vfs_drive_unref, NULL);
	g_list_free (drives);

	gtk_widget_destroy (GTK_WIDGET (self));
	return;
}
