/* Copyright (C) 2005-2006 Fabio Marzocca <thesaltydog@gmail.com>
 * Copyright (C) 2012-2021 MATE Developers
 *
 * This file is part of MATE Utils.
 *
 * MATE Utils 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.
 *
 * MATE Utils 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 MATE Utils.  If not, see <https://www.gnu.org/licenses/>.
 */

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

#include <glib.h>
#include <glib/gstdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <glib/gi18n.h>
#include <glib/gprintf.h>
#include <gtk/gtk.h>
#include <gio/gio.h>
#include <glibtop/mountlist.h>
#include <glibtop/fsusage.h>

#include "baobab.h"
#include "baobab-treeview.h"
#include "baobab-utils.h"
#include "callbacks.h"

void
baobab_get_filesystem (BaobabFS *fs)
{
	size_t i;
	glibtop_mountlist mountlist;
	glibtop_mountentry *mountentries;

	memset (fs, 0, sizeof *fs);

	mountentries = glibtop_get_mountlist (&mountlist, FALSE);

	for (i = 0; i < mountlist.number; ++i) {
		GFile *file;
		glibtop_fsusage fsusage;

		file = g_file_new_for_path (mountentries[i].mountdir);

		if (!baobab_is_excluded_location (file)) {

			glibtop_get_fsusage (&fsusage, mountentries[i].mountdir);

			fs->total += fsusage.blocks * fsusage.block_size;
			fs->avail += fsusage.bfree * fsusage.block_size;
			fs->used += (fsusage.blocks - fsusage.bfree) * fsusage.block_size;
		}

		g_object_unref (file);
	}

	g_free (mountentries);
}

static void
filechooser_cb (GtkWidget *chooser,
                gint response,
                gpointer data)
{
	if (response == GTK_RESPONSE_OK) {
		gchar *filename;
		GFile	*file;

		filename = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (chooser));
		gtk_widget_hide (chooser);

		file = g_file_new_for_uri (filename);
		baobab_scan_location (file);
		g_free (filename);
		g_object_unref (file);
	}
	else {
		gtk_widget_hide (chooser);
	}
}

/*
 * GtkFileChooser to select a directory to scan
 */
gchar *
dir_select (gboolean SEARCH, GtkWidget *parent)
{
	static GtkWidget *file_chooser = NULL;
	GtkWidget *toggle;

	if (file_chooser == NULL) {
		file_chooser = gtk_file_chooser_dialog_new (_("Select Folder"),
					      GTK_WINDOW (parent),
					      GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
					      "gtk-cancel",
					      GTK_RESPONSE_CANCEL,
					      "gtk-open",
					      GTK_RESPONSE_OK, NULL);

		gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (file_chooser), FALSE);
		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (file_chooser),
					     g_get_home_dir ());
		/* add extra widget */
		toggle = gtk_check_button_new_with_mnemonic (_("_Show hidden folders"));
		gtk_widget_show (toggle);
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE);
		g_signal_connect ((gpointer) toggle, "toggled",
				  G_CALLBACK (on_toggled), file_chooser);
		gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (file_chooser),
						   toggle);

		g_signal_connect (file_chooser, "response",
				  G_CALLBACK (filechooser_cb), NULL);
		g_signal_connect (file_chooser, "destroy",
				  G_CALLBACK (gtk_widget_destroyed), &file_chooser);

		gtk_window_set_modal (GTK_WINDOW (file_chooser), TRUE);
		gtk_window_set_position (GTK_WINDOW (file_chooser), GTK_WIN_POS_CENTER_ON_PARENT);
	}

	gtk_widget_show (GTK_WIDGET (file_chooser));

	return NULL;
}

void
on_toggled (GtkToggleButton *togglebutton, gpointer dialog)
{
	gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (dialog),
					  !gtk_file_chooser_get_show_hidden
					  (GTK_FILE_CHOOSER (dialog)));
}

gboolean
show_bars (GtkTreeModel *mdl,
	   GtkTreePath *path,
	   GtkTreeIter *iter,
	   gpointer data)
{
	GtkTreeIter parent;
	gdouble perc;
	gint readelements, size_col;
	guint64 refsize, size;
	char *sizecstr = NULL;

	if (baobab.show_allocated)
		size_col = (gint) COL_H_ALLOCSIZE;
	else
		size_col = (gint) COL_H_SIZE;

	if (gtk_tree_model_iter_parent (mdl, &parent, iter)) {
		gtk_tree_model_get (mdl, iter, COL_H_ELEMENTS,
				    &readelements, -1);

		if (readelements == -1) {
			gtk_tree_store_set (GTK_TREE_STORE (mdl), iter,
					    COL_DIR_SIZE, "--",
					    COL_ELEMENTS, "--", -1);
			return FALSE;
		}

 		gtk_tree_model_get (mdl, &parent, COL_H_ELEMENTS,
 				    &readelements, -1);

                gtk_tree_model_get (mdl, iter, size_col, &size, -1);

			sizecstr = g_format_size (size);

 		if (readelements == -1) {
			gtk_tree_store_set (GTK_TREE_STORE (mdl), iter,
				            COL_DIR_SIZE, sizecstr, -1);

			g_free (sizecstr);
			return FALSE;
		}

		gtk_tree_model_get (mdl, &parent, size_col, &refsize, -1);
		perc = (refsize != 0) ? ((gdouble) size * 100) / (gdouble) refsize : 0.0;

		gtk_tree_store_set (GTK_TREE_STORE (mdl), iter,
				    COL_DIR_SIZE, sizecstr,
				    COL_H_PERC, perc, -1);

		g_free (sizecstr);
	} else {
		gtk_tree_model_get (mdl, iter, COL_H_ELEMENTS,
				    &readelements, -1);

		if (readelements != -1) {
			gtk_tree_model_get (mdl, iter, size_col, &size,
					    -1);

				sizecstr = g_format_size (size);

			gtk_tree_store_set (GTK_TREE_STORE (mdl), iter,
					    COL_H_PERC, 100.0,
					    COL_DIR_SIZE, sizecstr, -1);

			g_free (sizecstr);
		}
		else {
			gtk_tree_store_set (GTK_TREE_STORE (mdl), iter,
		  		 	    COL_DIR_SIZE, "--",
					    COL_ELEMENTS, "--", -1);
		}
	}

	return FALSE;
}

void
message (const gchar *primary_msg,
	 const gchar *secondary_msg,
	 GtkMessageType type,
	 GtkWidget *parent)
{
	GtkWidget *dialog;
	dialog = gtk_message_dialog_new (GTK_WINDOW (parent),
					 GTK_DIALOG_DESTROY_WITH_PARENT,
					 type,
					 GTK_BUTTONS_OK, "%s", primary_msg);
	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
	                                          "%s", secondary_msg);
	gtk_dialog_run (GTK_DIALOG (dialog));
	gtk_widget_destroy (dialog);
}

gint
messageyesno (const gchar *primary_msg,
	      const gchar *secondary_msg,
	      GtkMessageType type,
	      gchar *ok_button,
	      GtkWidget *parent)
{
	GtkWidget *dialog;
	GtkWidget *button;
	gint response;

	dialog = gtk_message_dialog_new (GTK_WINDOW (parent),
					 GTK_DIALOG_DESTROY_WITH_PARENT,
					 type,
					 GTK_BUTTONS_CANCEL,
					 "%s", primary_msg);
	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
	                                          "%s", secondary_msg);

	button = gtk_button_new_with_mnemonic (ok_button);
	gtk_widget_set_can_default (button, TRUE);
	gtk_widget_show (button);

	gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, GTK_RESPONSE_OK);
	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
	response = gtk_dialog_run (GTK_DIALOG (dialog));
	gtk_widget_destroy (dialog);

	return response;
}

gboolean
baobab_check_dir (GFile	*file)
{
	GFileInfo *info;
	GError *error = NULL;
	gboolean ret = TRUE;

	if (baobab_is_excluded_location (file)) {
		message("", _("Cannot check an excluded folder!"), GTK_MESSAGE_INFO, baobab.window);
		return FALSE;
		}

	info = g_file_query_info (file,
				  G_FILE_ATTRIBUTE_STANDARD_TYPE,
				  G_FILE_QUERY_INFO_NONE,
				  NULL,
				  &error);

	if (!info) {
		message("", error->message, GTK_MESSAGE_INFO, baobab.window);
		g_error_free (error);

		return FALSE;
	}

	if ((g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY) ||
		is_virtual_filesystem(file)) {

		char *error_msg = NULL;
		gchar *name = NULL;

	    	name = g_file_get_parse_name (file);
		error_msg = g_strdup_printf (_("\"%s\" is not a valid folder"),
					     name);

		message (error_msg, _("Could not analyze disk usage."),
		         GTK_MESSAGE_ERROR, baobab.window);

		g_free (error_msg);
		g_free (name);
		ret = FALSE;
	}

	g_object_unref(info);

	return ret;
}

static void
add_popupmenu_item (GtkMenu *pmenu, const gchar *label, const gchar *icon_name, GCallback item_cb)
{
	GtkWidget *item;
	GtkWidget *image;

	item = gtk_image_menu_item_new_with_mnemonic (label);
	image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
	gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);

	g_signal_connect (item, "activate", item_cb, NULL);

	gtk_container_add (GTK_CONTAINER (pmenu), item);
}

void
popupmenu_list (GtkTreePath *path, GdkEventButton *event, gboolean can_trash)
{
	GtkWidget *pmenu;

	pmenu = gtk_menu_new ();

	add_popupmenu_item (GTK_MENU (pmenu),
			    _("_Open Folder"),
			    "document-open",
			    G_CALLBACK (open_file_cb));

	if (baobab.is_local && can_trash) {
		add_popupmenu_item (GTK_MENU (pmenu),
				    _("Mo_ve to Trash"),
				    "edit-delete",
				    G_CALLBACK (trash_dir_cb));
	}

	gtk_widget_show_all (pmenu);
	gtk_menu_popup_at_pointer (GTK_MENU (pmenu),
	                           (const GdkEvent*) event);
}

void
open_file_with_application (GFile *file)
{
	GAppInfo *application;
	gchar *primary;
	GFileInfo *info;
	const char *content;

	info = g_file_query_info (file,
				  G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
				  G_FILE_QUERY_INFO_NONE,
				  NULL,
				  NULL);
	if (!info) return;

	content = g_file_info_get_content_type (info);
	application = g_app_info_get_default_for_type (content, TRUE);

	if (!application) {
			primary = g_strdup_printf (_("Could not open folder \"%s\""),
			                           g_file_get_basename (file));
			message (primary,
			         _("There is no installed viewer capable "
				   "of displaying the folder."),
				 GTK_MESSAGE_ERROR,
				 baobab.window);
			g_free (primary);
	}
	else {
		GList *uris = NULL;
		gchar *uri;

		uri = g_file_get_uri (file);
		uris = g_list_append (uris, uri);
		g_app_info_launch_uris (application, uris, NULL, NULL);

		g_list_free (uris);
		g_free (uri);
	}

	if (application)
		g_object_unref (application);

	g_object_unref (info);
}

gboolean
can_trash_file (GFile *file)
{
	GFileInfo *info;
	gboolean can_trash = FALSE;

	info = g_file_query_info (file,
				  G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH,
				  G_FILE_QUERY_INFO_NONE,
				  NULL,
				  NULL);

	if (info) {
		if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH)) {
			can_trash = g_file_info_get_attribute_boolean (info,
								       G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH);
		}

		g_object_unref (info);
	}

	return can_trash;
}

gboolean
trash_file (GFile *file)
{
	GError *error = NULL;

	if (!g_file_trash (file, NULL, &error)) {
		GFileInfo *info;
		char *str = NULL;
		char *mess;

		info = g_file_query_info (file,
					  G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
					  G_FILE_QUERY_INFO_NONE,
					  NULL,
					  NULL);

		if (info) {
			const char *displayname = g_file_info_get_display_name (info);
			if (displayname)
				str = g_strdup_printf (_("Could not move \"%s\" to the Trash"),
						      displayname);

			g_object_unref (info);
		}

		/* fallback */
		if (str == NULL)
			str = g_strdup (_("Could not move file to the Trash"));

		mess = g_strdup_printf (_("Details: %s"), error->message);
		message (str, mess, GTK_MESSAGE_ERROR, baobab.window);
		g_free (str);
		g_free (mess);
		g_error_free (error);

		return FALSE;
	}

	return TRUE;
}

gboolean
baobab_help_display (GtkWindow   *parent,
		     const gchar *file_name,
		     const gchar *link_id)
{
	GError *error = NULL;
	char *uri;
	gboolean ret;

	uri = (link_id) ?
		g_strdup_printf ("help:%s/%s", file_name, link_id) :
		g_strdup_printf ("help:%s", file_name);

	ret = gtk_show_uri_on_window (parent,
	                              uri,
	                              gtk_get_current_event_time (),
	                              &error);
	g_free (uri);

	if (error != NULL) {
		GtkWidget *dialog;

		dialog = gtk_message_dialog_new (parent,
						 GTK_DIALOG_DESTROY_WITH_PARENT,
						 GTK_MESSAGE_ERROR,
						 GTK_BUTTONS_CLOSE,
						 _("There was an error displaying help."));

		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
							  "%s", error->message);

		g_signal_connect (G_OBJECT (dialog), "response",
				  G_CALLBACK (gtk_widget_destroy), NULL);

		gtk_widget_show (dialog);

		g_error_free (error);
	}

	return ret;
}

gboolean
is_virtual_filesystem (GFile *file)
{
	gboolean ret = FALSE;
	char *path;

	path = g_file_get_path (file);

	/* FIXME: we need a better way to check virtual FS */
	if (path != NULL) {
		if ((strcmp (path, "/proc") == 0) ||
		    (strcmp (path, "/sys") == 0))
	 		ret = TRUE;
	}

	g_free (path);

	return ret;
}
