/* $Id: e2_permissions_dialog.c 868 2008-05-06 04:42:09Z tpgww $

Copyright (C) 2003-2008 tooar <tooar@gmx.net>
Portions copyright (C) 1999 Michael Clark.

This file is part of emelFM2.
emelFM2 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 3, or (at your option)
any later version.

emelFM2 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 emelFM2; see the file GPL. If not, contact the Free Software
Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "emelfm2.h"
#include <pwd.h>
#include <grp.h>
#include "e2_dialog.h"
#include "e2_permissions_dialog.h"
#include "e2_ownership_dialog.h"

typedef struct _E2_PermsDldRuntime
{
	GtkWidget *chmod_buttons[12];
	GtkWidget *set_perms_button;
	GtkWidget *add_perms_button;
	GtkWidget *remove_perms_button;
	GtkWidget *recurse_button;
	GtkWidget *recurse_dirs_button;
	GtkWidget *recurse_other_button;
	gboolean permission;
	E2_RecurseType recurse;
	mode_t mode;
	gchar *stringmode;
	DialogButtons result;
} E2_PermsDldRuntime;

enum { SETUID, SETGID, STICKY, USR_READ, USR_WRITE, USR_EXEC, GRP_READ,
       GRP_WRITE, GRP_EXEC, OTH_READ, OTH_WRITE, OTH_EXEC };

static mode_t mask[12] = {S_ISUID, S_ISGID, S_ISVTX, S_IRUSR, S_IWUSR, S_IXUSR,
                          S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH};
//strings used for command string, not translated
static gchar *add_strings[12] =
	{"u+s", "g+s", "o+t", "u+r", "u+w", "u+x", "g+r", "g+w", "g+x", "o+r", "o+w", "o+x"};
static gchar *remove_strings[12] =
	{"u-s", "g-s", "o-t", "u-r", "u-w", "u-x", "g-r", "g-w", "g-x", "o-r", "o-w", "o-x"};

/**
@brief construct and save string-form mode string from current dialog settings
The result is stored as a newly-allocated string at rt->mode. Maybe NULL on error.
@param rt pointer to dialog data struct

@return
*/
static void _e2_permissions_dialog_get_mode_string (E2_PermsDldRuntime *rt)
{
	gint i;
	GString *mode_string = g_string_sized_new (16);
	if (GTK_TOGGLE_BUTTON (rt->set_perms_button)->active)
	{
		mode_t mode = 0;
		for (i = 0; i < 12; i++)
		{
			if (GTK_TOGGLE_BUTTON (rt->chmod_buttons[i])->active)
				mode |= mask[i];
		}
		g_string_printf (mode_string, "%o", (guint) mode);
	}
	else if (GTK_TOGGLE_BUTTON (rt->add_perms_button)->active)
	{
		for (i = 0; i < 12; i++)
		{
			if (GTK_TOGGLE_BUTTON (rt->chmod_buttons[i])->active)
			{
				g_string_append (mode_string, add_strings[i]);
				g_string_append_c (mode_string, ',');
			}
		}
		i = mode_string->len;
		if (i > 0)
		  g_string_truncate (mode_string, i-1); // get rid of trailing comma
	}
	else if (GTK_TOGGLE_BUTTON (rt->remove_perms_button)->active)
	{
		for (i = 0; i < 12; i++)
		{
			if (GTK_TOGGLE_BUTTON (rt->chmod_buttons[i])->active)
			{
				g_string_append (mode_string, remove_strings[i]);
				g_string_append_c (mode_string, ',');
			}
		}
		i = mode_string->len;
		if (i > 0)
			g_string_truncate (mode_string, i-1); // get rid of trailing comma
	}
	else
	{
		printd (DEBUG, "Strange error in permissions dialog.");
		g_string_free (mode_string, TRUE);
		rt->stringmode = NULL;
	}
	rt->stringmode = mode_string->str;
	g_string_free (mode_string, FALSE);
}
/**
@brief add a check button for a current permission

@param table the table to which the button will be added
@param left left position for attaching to @a table
@param right right position for attaching to @a table
@param top top position for attaching to @a table
@param bottom bottom position for attaching to @a table
@param label label to add to the button, or NULL
@param state state of the button
@param i buttons-array index of added button
@param rt pointer to dialog data struct

@return
*/
static void _e2_permissions_dialog_add_chmod_button (
				GtkWidget *table,
				gint left, gint right, gint top, gint bottom,
				gint i,
				gchar *label,
				gint state,
				E2_PermsDldRuntime *rt)
{
	if (label == NULL)
	{    //align the button reasonably
		GtkWidget *aligner =  gtk_alignment_new (0.1 , 0.5, 0, 0 );
		GtkWidget *check_button = gtk_check_button_new ();
		gtk_container_add (GTK_CONTAINER (aligner), check_button);
		gtk_table_attach (GTK_TABLE (table), aligner,
			left, right, top, bottom,
			GTK_FILL,  //GtkAttachOptions xoptions
			GTK_FILL, //GtkAttachOptions yoptions
			0, //xpadding
			0 //ypadding
			);
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), state);
		gtk_widget_show (check_button);
		rt->chmod_buttons[i] = check_button;
	}
	else  //there is a label
		rt->chmod_buttons[i] = e2_button_add_toggle_to_table (table,
			label, state, NULL, NULL, left, right, top, bottom);
	//prevent changes if relevant
	if (!rt->permission)
		gtk_widget_set_sensitive (rt->chmod_buttons[i], FALSE);
}
/**
@brief "toggled" signal callback for add_perms_button and remove_perms_button
This sets all permission-button states to FALSE
@param widget the activated button widget
@param rt pointer to dialog data struct

@return
*/
static void _e2_permissions_dialog_clear_chmod_buttons_cb (GtkWidget *widget,
	E2_PermsDldRuntime *rt)
{
	gint i;
	for (i = 0; i < 12; i++)
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->chmod_buttons[i]), FALSE);
}
/**
@brief "toggled" signal callback for set_perms_button
This sets all permission-button states to conform to mode FIXME
@param widget the activated button widget
@param rt pointer to dialog data struct

@return
*/
static void _e2_permissions_dialog_reset_chmod_buttons_cb (GtkWidget *widget,
	E2_PermsDldRuntime *rt)
{
	mode_t st_mode = rt->mode;
	gboolean flag;
	gint i;
	for (i = 0; i < 12; i++)
	{
		flag = st_mode & mask[i];
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->chmod_buttons[i]), flag);
	}
}
/**
@brief "toggled" signal callback for recurse_button

@param widget the activated button widget
@param rt pointer to dialog data struct

@return
*/
static void _e2_permissions_dialog_toggle_recurse_button_cb (GtkWidget *widget,
	E2_PermsDldRuntime *rt)
{
	if (!rt->permission)
		return;
	gboolean flag = (GTK_TOGGLE_BUTTON (widget)->active);
	gtk_widget_set_sensitive (rt->recurse_dirs_button, flag);
	gtk_widget_set_sensitive (rt->recurse_other_button, flag);
}
/**
@brief "toggled" signal callback for recurse_dirs and recurs_others buttons

@param widget the activated button widget
@param rt pointer to dialog data struct

@return
*/
static void _e2_permissions_dialog_toggle_recurse_type_cb (GtkWidget *widget,
	E2_PermsDldRuntime *rt)
{
	if (!rt->permission)
		return;
	if (GTK_TOGGLE_BUTTON (widget)->active)
		return;	//don't care about choice to turn recursion on
	if (widget == rt->recurse_dirs_button)
	{
		if (!(GTK_TOGGLE_BUTTON (rt->recurse_other_button)->active))
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->recurse_other_button),TRUE);
	}
	else //widget == rt->recurse_other_button
	{
		if (!(GTK_TOGGLE_BUTTON (rt->recurse_dirs_button)->active))
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rt->recurse_dirs_button),TRUE);
	}
}

/**
@brief dialog response callback

@param dialog the permissions-dialog
@param response the response for the clicked button
@param result pointer to store for the dialog return value

@return
*/
static void _e2_permissions_dialog_response_cb (GtkDialog *dialog,
	gint response, E2_PermsDldRuntime *rt)
{
	switch (response)
	{
		case GTK_RESPONSE_OK:
		case E2_RESPONSE_APPLYTOALL:
			rt->recurse = E2_RECURSE_NONE;	//probably redundant
			if (rt->permission)	//some widgets only exist if permission is TRUE;
			{
				_e2_permissions_dialog_get_mode_string (rt);	//set rt->stringmode
				if (rt->recurse_button != NULL
				  //the recurse button has been added to the dialog
					&& GTK_TOGGLE_BUTTON (rt->recurse_button)->active)
				{
					if (GTK_TOGGLE_BUTTON (rt->recurse_dirs_button)->active)
						rt->recurse = E2_RECURSE_DIRS;
					if (GTK_TOGGLE_BUTTON (rt->recurse_other_button)->active)
						rt->recurse |= E2_RECURSE_OTHER;
					if (rt->recurse == (E2_RECURSE_DIRS | E2_RECURSE_OTHER))
						rt->recurse = E2_RECURSE_ALL;
				}
			}
			break;
		default:
		break;
	}
	gtk_widget_destroy (GTK_WIDGET (dialog));
	e2_dialog_response_decode_cb (dialog, response, &rt->result);
}
/**
@brief create and run a dialog for changing item permissions

@param localpath path of item to be processed
@param mode_ret store for mode string to be used in the backend chmod function
@param recurse_ret store for returning whether to recurse the changes
@param permission_ret store for returning whether a change is authorised
@param multi TRUE if this dialog is part of a series for multiple items

@return enumerator corresponing to the clicked dialog button
*/
DialogButtons e2_permissions_dialog_run (
				VPATH *localpath,
				gchar **mode_ret,
				E2_RecurseType *recurse_ret,
				gboolean *permission_ret,
				gboolean multi )
{
	GtkWidget *permissions_dialog;
	GtkWidget *dialog_vbox, *sub_vbox;
	GtkWidget *hbox;
	GtkWidget *frame;
	GtkWidget *action_area;
	GtkWidget *table;

//	gchar perm_string[32];
	struct stat statbuf;
	struct passwd *pw_buf;
	struct group *grp_buf;
	E2_PermsDldRuntime rt;

	if (e2_fs_lstat (localpath, &statbuf E2_ERR_NONE()))
		return CANCEL;

	rt.permission = e2_fs_check_write_permission (localpath E2_ERR_NONE());
	rt.recurse = E2_RECURSE_NONE;
	rt.stringmode = NULL;

	GString *label_text = g_string_sized_new (NAME_MAX+20);
	gboolean thisis_dir = e2_fs_is_dir3 (localpath E2_ERR_NONE());
	permissions_dialog = e2_dialog_create (NULL, NULL, _("permissions"),
		_e2_permissions_dialog_response_cb, &rt);
	dialog_vbox = GTK_DIALOG (permissions_dialog)->vbox;
	action_area = GTK_DIALOG (permissions_dialog)->action_area;
	gtk_container_set_border_width (GTK_CONTAINER (dialog_vbox), E2_PADDING);
	gtk_dialog_set_has_separator (GTK_DIALOG (permissions_dialog), FALSE);  //all info in frames, no need

	//  table = e2_widget_add_table (dialog_vbox, 3, 3, TRUE, TRUE, 0);  //3 rows, 3 cols, homogen, fill, no pad
	//  gtk_table_set_row_spacing (GTK_TABLE(table), 0, E2_PADDING);
	gchar *label = (thisis_dir) ? _("Directory name") : _("Filename") ;
	gchar *name = g_filename_display_basename (VPSTR (localpath));
	g_string_printf (label_text, "%s: <b>%s</b>", label, name);
	g_free (name);

	hbox = gtk_hbox_new (FALSE, 0);
	gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, TRUE, TRUE, E2_PADDING); //top, bottom padding
	e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING); //L, R padding
	gtk_widget_show (hbox);

	label = _("User");
	if ((pw_buf = getpwuid (statbuf.st_uid)) != NULL)
		name = e2_utf8_from_locale (pw_buf->pw_name);
	else
		name = NULL;
	if (name != NULL)
	{
		g_string_printf (label_text, "%s: %s", label, name);
		g_free (name);
	}
	else
		g_string_printf (label_text, "%s: %d", label, (guint) statbuf.st_uid);

	hbox = gtk_hbox_new (FALSE, 0);
	e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING);
	gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, TRUE, TRUE, 0);
	gtk_widget_show (hbox);

	label = _("Group");
	if ((grp_buf = getgrgid (statbuf.st_gid)) != NULL)
		name = e2_utf8_from_locale (grp_buf->gr_name);
	else
		name = NULL;
	if (name != NULL)
	{
		g_string_printf (label_text, "%s: %s", label, name);
		g_free (name);
	}
	else
		g_string_printf (label_text, "%s: %d", label, (guint) statbuf.st_gid);

	hbox = gtk_hbox_new (FALSE, 0);
	e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING);
	gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, TRUE, TRUE, 0);
	gtk_widget_show (hbox);

	gchar *s = e2_fs_get_perm_string (statbuf.st_mode);
	g_string_assign (label_text, g_utf8_next_char (s));	//skip the "type code" in 1st byte of string
	g_free (s);
	g_string_insert_c (label_text, e2_utils_get_byte_position (label_text->str, 6), ' ');
	g_string_insert_c (label_text, e2_utils_get_byte_position (label_text->str, 3), ' ');
	g_string_insert_c (label_text, 0, ' ');
	label_text = g_string_prepend (label_text, _("currently"));

	hbox = gtk_hbox_new (FALSE, 0);
	e2_widget_add_mid_label (hbox, label_text->str, 0, TRUE, E2_PADDING);
	gtk_box_pack_start (GTK_BOX(dialog_vbox), hbox, TRUE, TRUE, 0);
	gtk_widget_show (hbox);

	frame = gtk_frame_new (_("Permissions"));
	gtk_box_pack_start (GTK_BOX (dialog_vbox), frame, TRUE, TRUE, E2_PADDING);
	gtk_widget_show (frame);

	sub_vbox = gtk_vbox_new (FALSE, 0);
	gtk_container_add (GTK_CONTAINER (frame), sub_vbox);
	gtk_widget_show (sub_vbox);

	table = e2_widget_add_table (sub_vbox, 4, 5, TRUE, TRUE, 0);  //4 rows, 5 cols, homogen, fill, no pad
	e2_widget_add_mid_label_to_table (table, _("Read"), 0,		1,2,0,1);  //gint left, gint right, gint top, gint bottom)
	e2_widget_add_mid_label_to_table (table, _("Write"), 0,		2,3,0,1);
	e2_widget_add_mid_label_to_table (table, _("Exec"), 0.05,	3,4,0,1);
	e2_widget_add_mid_label_to_table (table, _("Special"), 0.2,	4,5,0,1);

	e2_widget_add_mid_label_to_table (table, _("User"), 0.1,	0,1,1,2);
	_e2_permissions_dialog_add_chmod_button (table, 1,2,1,2, USR_READ, NULL,
		statbuf.st_mode & S_IRUSR, &rt);
	_e2_permissions_dialog_add_chmod_button (table, 2,3,1,2, USR_WRITE, NULL,
		statbuf.st_mode & S_IWUSR, &rt);
	_e2_permissions_dialog_add_chmod_button (table, 3,4,1,2, USR_EXEC, NULL,
		statbuf.st_mode & S_IXUSR,	&rt);
	_e2_permissions_dialog_add_chmod_button (table, 4,5,1,2, SETUID, _("Set UID"),
		statbuf.st_mode & S_ISUID, &rt);

	e2_widget_add_mid_label_to_table (table, _("Group"), 0.1,	0,1,2,3);
	_e2_permissions_dialog_add_chmod_button (table, 1,2,2,3, GRP_READ, NULL,
		statbuf.st_mode & S_IRGRP, &rt);
	_e2_permissions_dialog_add_chmod_button (table, 2,3,2,3, GRP_WRITE, NULL,
		statbuf.st_mode & S_IWGRP, &rt);
	_e2_permissions_dialog_add_chmod_button (table, 3,4,2,3, GRP_EXEC, NULL,
		statbuf.st_mode & S_IXGRP, &rt);
	_e2_permissions_dialog_add_chmod_button (table, 4,5,2,3, SETGID, _("Set GID"),
		statbuf.st_mode & S_ISGID, &rt);

	e2_widget_add_mid_label_to_table (table, _("Other"), 0.1,	0,1,3,4);
	_e2_permissions_dialog_add_chmod_button (table, 1,2,3,4, OTH_READ, NULL,
		statbuf.st_mode & S_IROTH, &rt);
	_e2_permissions_dialog_add_chmod_button (table, 2,3,3,4, OTH_WRITE, NULL,
		statbuf.st_mode & S_IWOTH, &rt);
	_e2_permissions_dialog_add_chmod_button (table, 3,4,3,4, OTH_EXEC, NULL,
		statbuf.st_mode & S_IXOTH, &rt);
	_e2_permissions_dialog_add_chmod_button (table, 4,5,3,4, STICKY, _("Sticky"),
		statbuf.st_mode & S_ISVTX, &rt);

	if (!rt.permission || !thisis_dir)
	{
		//prevent testing of missing widgets in the response cb
		rt.recurse_button = NULL;
		rt.recurse_dirs_button = NULL;
		rt.recurse_other_button = NULL;
	}

	if (rt.permission)
	{
		frame = gtk_frame_new (_("Action:"));
		gtk_box_pack_start (GTK_BOX (dialog_vbox), frame, TRUE, TRUE, E2_PADDING);
		gtk_widget_show (frame);

		sub_vbox = gtk_vbox_new (FALSE, 0);
		gtk_container_add (GTK_CONTAINER (frame), sub_vbox);
		gtk_widget_show (sub_vbox);

		table = e2_widget_add_table (sub_vbox, 1, 5, TRUE, TRUE, 0);  //1 rows, 5 cols, homogen, fill, no pad
		rt.mode = statbuf.st_mode;
		rt.set_perms_button = e2_button_add_radio_to_table (table, _("_Set"),
			NULL, TRUE, _e2_permissions_dialog_reset_chmod_buttons_cb,
			&rt,
			1, 2, 0, 1 ); //gint left, gint right, gint top, gint bottom
		rt.add_perms_button = e2_button_add_radio_to_table (table, _("_Add"),
			gtk_radio_button_get_group (GTK_RADIO_BUTTON (rt.set_perms_button)),
			FALSE, _e2_permissions_dialog_clear_chmod_buttons_cb, &rt,
			2, 3, 0, 1);
		rt.remove_perms_button = e2_button_add_radio_to_table (table, _("_Remove"),
			gtk_radio_button_get_group (GTK_RADIO_BUTTON (rt.add_perms_button)),
			FALSE, _e2_permissions_dialog_clear_chmod_buttons_cb, &rt,
			3, 4, 0, 1);

		if (thisis_dir)  //no longer do this for 'multi'
		{
			table = e2_widget_add_table (sub_vbox, 1, 5, TRUE, TRUE, 0);  //1 row, 5 cols, homogen, fill, no pad
			rt.recurse_button = e2_button_add_toggle_to_table (table, _("R_ecurse:"),
				FALSE, _e2_permissions_dialog_toggle_recurse_button_cb, &rt,
				1,2,0,1 );
#ifdef USE_GTK2_12TIPS
			gtk_widget_set_tooltip_text (
#else
			e2_widget_set_tooltip (NULL,
#endif
				rt.recurse_button,
				_("If activated, changes will be applied to selected items and"
					" also their descendents, if such items match your choice of"
					" \"directories\" and/or \"others\" (anything not a directory)"));
			rt.recurse_dirs_button = e2_button_add_toggle_to_table (table, _("d_irectories"),
			   TRUE, _e2_permissions_dialog_toggle_recurse_type_cb, &rt, 2,3,0,1) ;
			gtk_widget_set_sensitive (rt.recurse_dirs_button, FALSE);
			rt.recurse_other_button = e2_button_add_toggle_to_table (table, _("o_thers"),
			   TRUE, _e2_permissions_dialog_toggle_recurse_type_cb, &rt, 3,4,0,1) ;
			gtk_widget_set_sensitive (rt.recurse_other_button, FALSE);
		}
	}
	else //no right to change object permissions
		//show message
		e2_ownership_dialog_warn (dialog_vbox);

	g_string_free (label_text, TRUE);

	// add buttons in the order that they will appear
	GtkWidget *btn;
	if (multi)
	{
		e2_dialog_set_negative_response (permissions_dialog, E2_RESPONSE_NOTOALL);
		e2_dialog_add_defined_button (permissions_dialog, &E2_BUTTON_NOTOALL);
		btn = e2_dialog_add_button_custom (permissions_dialog, FALSE,
			&E2_BUTTON_APPLYTOALL, NULL, NULL, NULL);
		if (!rt.permission)
			gtk_widget_set_sensitive (btn, FALSE);
	}
	if (rt.permission || multi)
	{
		btn = e2_dialog_add_defined_button (permissions_dialog, &E2_BUTTON_CANCEL);
		if (!rt.permission)
			gtk_widget_set_sensitive (btn, FALSE);
	}

	e2_dialog_add_button_custom (permissions_dialog, TRUE, &E2_BUTTON_OK,
		NULL, NULL, NULL);

	e2_dialog_setup (permissions_dialog, app.main_window);
/*  non-modal not supported
	e2_dialog_run (permissions_dialog, NULL,
	E2_DIALOG_NONMODAL | E2_DIALOG_THREADED | E2_DIALOG_FREE);
*/
	gdk_threads_enter ();
	e2_dialog_run (permissions_dialog, NULL, 0);
	gtk_main ();
	gdk_threads_leave ();

	*permission_ret = rt.permission;
	*recurse_ret = rt.recurse;
	if (rt.result == OK || rt.result == YES_TO_ALL)
		*mode_ret = rt.stringmode;	//possibly NULL
	else if (rt.stringmode != NULL)
	{
		g_free (rt.stringmode);
		*mode_ret = NULL;
	}
	return rt.result;
}
