/*
 *  Copyright (C) 2004 Morten Fjord-Larsen
 *
 *  $Id: passwordlist.c,v 1.6 2004/06/17 14:31:32 fjord Exp $
 *
 *  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 <gnome.h>
#include <glade/glade.h>

#include "passwordentry.h"
#include "passwordfile.h"
#include "app.h"
#include "clipboard.h"
#include "rundialog.h"
#include "passworddialog.h"

extern GladeXML *xml;

static GtkListStore *password_list_store;
static GtkTreeModel *password_list_filter;
static GtkTreeModel *password_list_sorted;
static GtkTreeView *treeview;
static gboolean is_dirty = FALSE;

static gchar *password_list_master_password = NULL;
static const gchar *password_list_filename;
static const gchar *password_list_filter_text = NULL;

enum {
    COLUMN_TITLE,
    COLUMN_USERNAME,
    COLUMN_PASSWORD,
    COLUMN_URL,
    COLUMN_CREATION_TIME,
    COLUMN_MODIFICATION_TIME,
    COLUMN_EXPIRATION_TIME,
    COLUMN_DESCRIPTION,
    NCOLUMNS
};


/* Displays a popup menu. */
static void 
password_list_show_popup_menu(GtkWidget *password_list, GdkEventButton *event)
{
    GtkWidget *menu;
    int button, event_time;

    menu = glade_xml_get_widget(xml, "entry_menu");
    g_signal_connect(menu, "deactivate",
	    	     G_CALLBACK(gtk_widget_hide), NULL);

    if (event) {
	button = event->button;
	event_time = event->time;
    } else {
	button = 0;
	event_time = gtk_get_current_event_time ();
    }

    gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 
	    button, event_time);
}


/* Button press handler for the GtkTreeView */
static gboolean 
password_list_button_press_event_handler (GtkWidget *widget, 
					  GdkEventButton *event)
{
  /* Ignore double-clicks and triple-clicks */
  if (event->button == 3 && event->type == GDK_BUTTON_PRESS) {
      password_list_show_popup_menu(widget, event);
      return TRUE;
  }
  return FALSE;
}
	

/* popup_menu signal handler for the GtkTreeView */
static gboolean 
password_list_popup_menu_handler (GtkWidget *widget)
{
    password_list_show_popup_menu(widget, NULL);
    return TRUE;
}
	

/* Filter text handler */
void 
password_list_on_search_text_changed(GtkEntry *text_entry)
{
    password_list_filter_text = gtk_entry_get_text(text_entry);
    gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(password_list_filter));
}


/* Sets the dirty status of the password list. */
void 
password_list_set_dirty(gboolean dirty)
{
    is_dirty = dirty;
}


/* Determines if the password list is dirty. */
gboolean 
password_list_is_dirty() 
{
    return is_dirty;
}


/* Determines if a given row should be visible. */
static gboolean 
password_list_visible (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
{
    password_file_entry entry;
    if (password_list_filter_text == NULL || *password_list_filter_text == '\0')
	return TRUE;

    gtk_tree_model_get(model, iter,
		       COLUMN_TITLE,
		       &entry.title,
		       COLUMN_USERNAME,
		       &entry.username,
		       COLUMN_URL,
		       &entry.url,
		       -1);

    if (entry.title == NULL)
	return FALSE;

    if (strstr(entry.title, password_list_filter_text))
	return TRUE;

    if (strstr(entry.username, password_list_filter_text))
	return TRUE;

    if (strstr(entry.url, password_list_filter_text))
	return TRUE;

    return FALSE;
}


/* Creates a GtkTreeViewColumn for the password list widget */
static GtkTreeViewColumn *
password_list_create_column(const gchar *title, int n) 
{
    GtkTreeViewColumn *column;
    GtkCellRenderer   *renderer = gtk_cell_renderer_text_new();

    column = gtk_tree_view_column_new_with_attributes(
	    title,
	    renderer,
	    "text", n,
	    NULL);

    gtk_tree_view_column_set_sort_column_id(column, n);

    return column;
}


/* Gets a model iterator for the given path. */
static void 
password_list_get_iter(GtkTreeIter *iter, GtkTreePath *path)
{
    GtkTreeIter sortiter, filteriter;

    gtk_tree_model_get_iter(GTK_TREE_MODEL(password_list_sorted),
	    &sortiter,
	    path);

    gtk_tree_model_sort_convert_iter_to_child_iter(
	    GTK_TREE_MODEL_SORT(password_list_sorted),
	    &filteriter,
	    &sortiter);

    gtk_tree_model_filter_convert_iter_to_child_iter(
	    GTK_TREE_MODEL_FILTER(password_list_filter),
	    iter,
	    &filteriter);
}


/* Signal handler for "select_cursor_row" */
gboolean 
password_list_on_treeview_select_cursor_row (GtkTreeView *treeview,
                                             gboolean arg1,
					     gpointer user_data)
{
    app_menu_edit_items_sensitive(TRUE);
    return TRUE;
}


/* Called when a row is selected. */
void 
password_list_on_treeview_row_activated (GtkTreeView *treeview,
                                         GtkTreePath *arg1,
					 GtkTreeViewColumn *arg2,
					 gpointer user_data)
{
    GtkTreeIter iter;
    password_file_entry entry;

    password_list_get_iter(&iter, arg1);
    gtk_tree_model_get(GTK_TREE_MODEL(password_list_store),
	    	       &iter,
		       COLUMN_TITLE, &entry.title,
		       COLUMN_USERNAME, &entry.username,
		       COLUMN_PASSWORD, &entry.password,
		       COLUMN_URL, &entry.url,
		       COLUMN_CREATION_TIME, &entry.creation_time,
		       COLUMN_MODIFICATION_TIME, &entry.modification_time,
		       COLUMN_EXPIRATION_TIME, &entry.expiration_time,
		       COLUMN_DESCRIPTION, &entry.description,
		       -1);

    password_entry_edit(&entry);
}


/* Adds an entry to the password list */
void 
password_list_enter (const password_file_entry *entry, gboolean edit)
{
    GtkTreeIter iter;

    if (!edit) {
	gtk_list_store_append(password_list_store, &iter);
	app_status_say(_("Password entry '%s' inserted."), entry->title);
    } else {
	GtkTreePath *path;

	gtk_tree_view_get_cursor(treeview, &path, NULL);
	password_list_get_iter(&iter, path);
	gtk_tree_path_free(path);
	app_status_say(_("Password entry '%s' edited."), entry->title);
    }

    gtk_list_store_set(password_list_store, &iter, 
	    	       COLUMN_TITLE, entry->title,
		       COLUMN_USERNAME, entry->username,
		       COLUMN_PASSWORD, entry->password,
		       COLUMN_URL, entry->url,
		       COLUMN_CREATION_TIME, entry->creation_time,
		       COLUMN_MODIFICATION_TIME, entry->modification_time,
		       COLUMN_EXPIRATION_TIME, entry->expiration_time,
		       COLUMN_DESCRIPTION, entry->description,
		       -1);

    is_dirty = TRUE;
}


/* Edit->edit... activated */
void 
password_list_on_menu_edit () 
{
    GtkTreePath *path;
    gtk_tree_view_get_cursor(treeview, &path, NULL);
    password_list_on_treeview_row_activated(treeview, path, NULL, NULL);
    gtk_tree_path_free(path);
}


/* Fills the password_file_entry structure from the selected list item. */
static void 
password_list_get_entry(GtkTreeIter *iter, password_file_entry *entry)
{
    gtk_tree_model_get(
	    GTK_TREE_MODEL(password_list_store),
	    iter,
	    COLUMN_TITLE, &entry->title,
	    COLUMN_USERNAME, &entry->username,
	    COLUMN_PASSWORD, &entry->password,
	    COLUMN_URL, &entry->url,
	    COLUMN_CREATION_TIME, &entry->creation_time,
	    COLUMN_MODIFICATION_TIME, &entry->modification_time,
	    COLUMN_EXPIRATION_TIME, &entry->expiration_time,
	    COLUMN_DESCRIPTION, &entry->description,
	    -1);
}


/* Retrieves the selected entry, if one is selected. */
static gboolean 
password_list_get_selected_entry (password_file_entry *entry)
{
    GtkTreePath *path;
    GtkTreeIter iter;

    gtk_tree_view_get_cursor(treeview, &path, NULL);

    if (path == NULL) {
	app_status_say("No password entry selected!");
	return FALSE;
    }

    password_list_get_iter(&iter, path);
    gtk_tree_path_free(path);
    password_list_get_entry(&iter, entry);

    return TRUE;
}


/* Remove the selected entry (after prompting user). */
void 
password_list_on_menu_remove () 
{
    GtkTreePath *path;
    GtkTreeIter iter;
    int rc;

    gtk_tree_view_get_cursor(treeview, &path, NULL);

    if (path == NULL) {
	app_status_say(_("No password entry selected."));
	return;
    }

    rc = run_dialog(PACKAGE_DATA_DIR"/remove-dialog.glade", "remove_dialog");
    if (rc != GTK_RESPONSE_YES)
	return;

    password_list_get_iter(&iter, path);
    gtk_tree_path_free(path);
    gtk_list_store_remove(password_list_store, &iter);

    app_status_say(_("Password entry removed."));
    app_menu_edit_items_sensitive(FALSE);

    is_dirty = TRUE;
}


/* Copy password of selected entry to the clipboard. */
void 
password_list_copy_password ()
{
    password_file_entry entry;
    if (password_list_get_selected_entry(&entry)) {
	clipboard_copy(entry.password);
	app_status_say(_("Password for '%s' copied to clipboard."),entry.title);
	password_file_entry_destroy(&entry);
    }
}


/* Copy username of selected entry to the clipboard. */
void 
password_list_copy_username ()
{
    password_file_entry entry;
    if (password_list_get_selected_entry(&entry)) {
	clipboard_copy(entry.username);
	app_status_say(_("Username for '%s' copied to clipboard."),entry.title);
	password_file_entry_destroy(&entry);
    }
}


/* Copy username and password of selected entry to the clipboard. */
void 
password_list_copy_both ()
{
    password_file_entry entry;
    if (password_list_get_selected_entry(&entry)) {
	const char *seq[] = { entry.username, entry.password };
	clipboard_copy_sequence(seq, 2);
	app_status_say(_("Queued username and password for '%s' on clipboard."),entry.title);
	password_file_entry_destroy(&entry);
    }
}


/* Sets up the password list widget. */
void 
password_list_setup (GtkWidget *tv) 
{
    GtkTreeViewColumn *title;
    GtkTreeViewColumn  *username;
    GtkTreeViewColumn  *url;

    treeview = GTK_TREE_VIEW(tv);

    title = password_list_create_column(_("Title"), COLUMN_TITLE);
    gtk_tree_view_append_column(treeview, title);

    username = password_list_create_column(_("Username"), COLUMN_USERNAME);
    gtk_tree_view_append_column(treeview, username);

    url = password_list_create_column(_("URL"), COLUMN_URL);
    gtk_tree_view_append_column(treeview, url);

    password_list_store = gtk_list_store_new(NCOLUMNS, 
	    G_TYPE_STRING, 
	    G_TYPE_STRING, 
	    G_TYPE_STRING, 
	    G_TYPE_STRING,
	    G_TYPE_INT,
	    G_TYPE_INT,
	    G_TYPE_INT,
	    G_TYPE_STRING
	    );

    password_list_filter = gtk_tree_model_filter_new(
	    GTK_TREE_MODEL(password_list_store),
	    NULL);

    gtk_tree_model_filter_set_visible_func(
	    GTK_TREE_MODEL_FILTER(password_list_filter),
	    password_list_visible,
	    NULL, NULL);

    password_list_sorted = gtk_tree_model_sort_new_with_model(
	    password_list_filter);

    gtk_tree_view_set_model(treeview, password_list_sorted);
    gtk_tree_view_set_enable_search(treeview, TRUE);
    gtk_tree_view_set_search_column(treeview, COLUMN_TITLE);

    /* Popup menu signals */

    g_signal_connect(treeview, 
	    "button-press-event", 
	    G_CALLBACK(password_list_button_press_event_handler),
	    NULL);

    g_signal_connect(treeview,
	    "popup_menu",
	    G_CALLBACK(password_list_popup_menu_handler),
	    NULL);
}


/* Insert an entry into the password list. */
void 
password_list_insert_entry (password_file_entry *entry)
{
    password_list_enter(entry, FALSE);
}


/* Gets the current master password */
const gchar *
password_list_get_password ()
{
    return password_list_master_password;
}


/* Sets the current master password */
void 
password_list_set_password (const gchar *password)
{
    if (password_list_master_password != NULL)
	g_free(password_list_master_password);
    password_list_master_password = g_strdup(password);
}



static gboolean 
password_list_check_password (const gchar *password)
{
    encstream *es;
    GError *error = NULL;
    g_return_val_if_fail(password_list_filename != NULL, FALSE);

    es = password_file_openr(password_list_filename, password, &error);
    if (es != NULL) {
	password_file_entry entry;
	password_list_master_password = g_strdup(password);
	while (!password_file_eof(es)) {
	    int rc;
	    rc = password_file_read_entry(es, &entry, &error);
	    if (error != NULL) {
		GtkWidget *dialog;
		dialog = gtk_message_dialog_new (NULL,
			GTK_DIALOG_DESTROY_WITH_PARENT,
			GTK_MESSAGE_ERROR,
			GTK_BUTTONS_CLOSE,
			"Error loading password collection '%s': %s",
			password_list_filename, error->message);
		gtk_dialog_run(GTK_DIALOG (dialog));
		gtk_widget_destroy(dialog);
		g_error_free(error);
		break;
	    }
	    if (rc == EOF)
		break;
	    password_list_insert_entry(&entry);
	    password_file_entry_destroy(&entry);
	}
	is_dirty = FALSE;
	app_status_say(_("Password collection loaded."));
	return TRUE;
    }
    return FALSE;
}


/* Opens the url of the selected entry. */
void 
password_list_open_url () 
{
    password_file_entry entry;
    GError *error = NULL;
    if (password_list_get_selected_entry(&entry)) {
	gnome_url_show(entry.url, &error);
	password_file_entry_destroy(&entry);
    }
}


/* Loads a password file. */
void 
password_list_load_password_file (const gchar *filename)
{
    password_list_filename = filename;

    if (g_file_test(filename, G_FILE_TEST_EXISTS))
	password_dialog_display(password_list_check_password);
    else {
	password_create_dialog_display(password_list_set_password);
	is_dirty = TRUE;
    }
}


#if 0
typedef gboolean (*PasswordListAction)(password_file_entry *, gpointer);

gboolean password_list_foreach(PasswordListAction action, gpointer user_data)
{
    GtkTreeIter iter;
    gboolean has_more, succes = TRUE;
    password_file_entry entry;

    has_more = gtk_tree_model_get_iter_first(
	    GTK_TREE_MODEL(password_list_store),
	    &iter);

    while (has_more && succes) {

	password_list_get_entry(&iter, &entry);
	succes = action(&entry, user_data);
	password_file_entry_destroy(&entry);

	has_more = gtk_tree_model_iter_next(
		GTK_TREE_MODEL(password_list_store),
		&iter);
    }

    return succes;
}
#endif


/* Write the entire password list to disk. */
void password_list_write()
{
    GError *error = NULL;
    GtkTreeIter iter;
    gboolean has_more;
    password_file_entry entry;
    encstream *es;

    password_file_backup(password_list_filename, &error);
    if (error != NULL) {
	// FIXME: we should do something here...
	g_error_free(error);
	error = NULL;
    }

    es = password_file_openw(password_list_filename,
	    	                        password_list_master_password,
					&error);

    has_more = gtk_tree_model_get_iter_first(
	    GTK_TREE_MODEL(password_list_store),
	    &iter);

    while (has_more) {

	if (error != NULL) {
	    GtkWidget *dialog;
	    password_file_close(es, NULL);
	    dialog = gtk_message_dialog_new (NULL,
		    GTK_DIALOG_DESTROY_WITH_PARENT,
		    GTK_MESSAGE_ERROR,
		    GTK_BUTTONS_CLOSE,
		    "Error writing collection '%s': %s",
		    password_list_filename, error->message);
	    gtk_dialog_run(GTK_DIALOG (dialog));
	    gtk_widget_destroy(dialog);
	    g_error_free(error);
	    return;
	}

	password_list_get_entry(&iter, &entry);
	password_file_write_entry(es, &entry, &error);
	password_file_entry_destroy(&entry);

	has_more = gtk_tree_model_iter_next(
		GTK_TREE_MODEL(password_list_store),
		&iter);
    }

    password_file_close(es, NULL);

    app_status_say(_("Password collection saved."));

    is_dirty = FALSE;
}

