/* NessusClient
 * Copyright (C) 1998, 2005 Renaud Deraison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * 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 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * In addition, as a special exception, Renaud Deraison
 * gives permission to link the code of this program with any
 * version of the OpenSSL library which is distributed under a
 * license identical to that listed in the included COPYING.OpenSSL
 * file, and distribute linked combinations including the two.
 * You must obey the GNU General Public License in all respects
 * for all of the code used other than OpenSSL.  If you modify
 * this file, you may extend this exception to your version of the
 * file, but you are not obligated to do so.  If you do not wish to
 * do so, delete this exception statement from your version.
 */

#include <time.h>
#include <includes.h>
#include "comm.h"
#include "nessus_i18n.h"

#ifdef USE_GTK
#include <gtk/gtk.h>
#include "../nessus_plugin.h"
#include "../plugin_infos.h"
#include "../families.h"
#include "../error_dialog.h"
#include "../xpm/warning_small.xpm"
#include "filter.h"
#include "context.h"
#include "prefs_help.h"
#include "prefs_plugins_tree.h"



/* The columns of the tree store we use */
enum {
  /* The name of the family/plugin */
  COL_NAME,

  /* struct arglist * of the plugin. For a family the value is NULL.
   */
  COL_PLUGIN,

  /* Number of columns */
  NUM_COLS
};


/* Define a new signal, "statistics-changed", to be emitted when the
 * plugin tree changes in a way that may have changed the statistics
 * (number of plugins, number of enabled plugins, number of filtered
 * plugins) tree view.
 *
 * the signal handlers have the following signature:
 *
 * void statistics_changed_handler(GtkWidget* tree_view, gpointer user_data)
 */

/* the id of the signal */
static gint statistics_changed_signal = 0;

/* register the "statistics-changed" signal with glib.  This function
 * must be called before the signal is used for the first time in any
 * way.  It may be called multiple times as it only registers the signal
 * on the first call.
 */
static void
create_statistics_changed_signal()
{
  if (statistics_changed_signal == 0)
  {
    statistics_changed_signal = g_signal_new("statistics-changed",
	GTK_TYPE_TREE_VIEW, G_SIGNAL_RUN_LAST, 0, NULL, NULL,
	g_cclosure_marshal_VOID__VOID, GTK_TYPE_NONE, 0);
  }
}

/* Emit the "statistics-changed" signal with the tree_view as the
 * instance */
static void
emit_statistics_changed(tree_view)
  GtkWidget *tree_view;
{
  g_signal_emit(tree_view, statistics_changed_signal, 0);
}  

/* Handler for the tree model's "row-changed" signal.  Emit the
 * "statistics-changed" signal on the tree model */
static void
emit_statistics_when_row_changed(treemodel, arg1, arg2, user_data)
  GtkTreeModel *treemodel;
  GtkTreePath *arg1;
  GtkTreeIter *arg2;
  gpointer user_data;
{
  emit_statistics_changed(GTK_WIDGET(user_data));
}

/* Handler for the tree model's "row-deleted" signal.  Emit the
 * "statistics-changed" signal on the tree model */
static void
emit_statistics_when_row_deleted(treemodel, arg1, user_data)
  GtkTreeModel *treemodel;
  GtkTreePath *arg1;
  gpointer user_data;
{
  emit_statistics_changed(GTK_WIDGET(user_data));
}


/* Return whether the family given by the model and iter is enabled.
 *
 * The iter argument must be the iter pointing to the family row.
 * A family is enabled when at least one of it's plugins is enabled as
 * determined by plug_get_launch.
 */
static gboolean
is_family_enabled(model, iter)
  GtkTreeModel* model;
  GtkTreeIter * iter;
{
  GtkTreeIter child_iter;

  if (gtk_tree_model_iter_children(model, &child_iter, iter))
  {
    do
    {
      struct nessus_plugin *plugin;
      gtk_tree_model_get(model, &child_iter, COL_PLUGIN, &plugin, -1);
      if ( plugin->enabled )
	return TRUE;
    }
    while (gtk_tree_model_iter_next(model, &child_iter));
  }
  return FALSE;
}


/* Determine whether a warning should be shown for the given row
 *
 * Some plugins are dangerous and a warning sign is shown for the them
 * in the tree view.  This function determines whether a warning is
 * necessary for a particular row of the tree model.  The row can be
 * specified either with a path or an iter.  If an iter is given,
 * i.e. the iter parameter is not NULL, the iter is used to retrieve the
 * plugin information and the path argument is ignored.  If iter is
 * NULL, the path argument must be the GtkTreePath for the row and it is
 * used to determine the iter needed to access the row data.
 *
 * The return value is true if the category of the plugin is one of
 * "denial", "kill_host", "flood" or "destructive_attack" and false
 * otherwise.
 */

static int
should_show_warning(model, path, iter)
  GtkTreeModel *model;
  GtkTreePath *path;
  GtkTreeIter *iter;
{
  GtkTreeIter real_iter;
  struct nessus_plugin *plugin;
  char *cat;
  int warning = FALSE;

  if (!iter)
  {
    gtk_tree_model_get_iter(model, &real_iter, path);
    iter = &real_iter;
  }

  gtk_tree_model_get(model, iter, COL_PLUGIN, &plugin, -1);
  if (plugin)
  {
    cat = plugin->category;
    warning = cat ? (!strcmp(cat, "denial") ||
                     !strcmp(cat, "kill_host") ||
                     !strcmp(cat, "flood") ||
                     !strcmp(cat, "destructive_attack")) : 0;
  }

  return warning;
}


/* Trigger a row update in any view of the given model
 *
 * The iter should be an iter for the row in question.  This function is
 * needed because some information shown in the plugin tree is not
 * directly stored in the tree store.  When that information is changed
 * the update is not automatically noted by the tree views because the
 * model doesn't send any signals.
 */
static void
trigger_row_update(model, iter)
  GtkTreeModel *model;
  GtkTreeIter *iter;
{
  GtkTreePath *path = gtk_tree_model_get_path(model, iter);

  gtk_tree_model_row_changed(model, path, iter);

  gtk_tree_path_free(path);
}



/* Toggle the active/launch flag of a plugin
 *
 * This function is called when the checkbox in the tree view widget has
 * been toggled by the user.  There are two cases: The row on which it
 * happens is a family, in which case all plugins of the family are
 * activated/deactivated depending on the state of the family checkbox,
 * or the row is a single plugin in which case on that plugins flag is
 * changed.
 *
 * This function expects the tree view in the data parameter.  The
 * path_str must describe the path in the model of that tree view.
 *
 * The changes are always made on the actual plugins, that is the
 * arglists, with the function plug_set_launch.  To update the view, the
 * appropriate signal is emitted for the rows on the model that are
 * modified.  If a plugin is modified, a signal is also emitted for it's
 * parent family.
 */
static void
active_toggled(cell, path_str, data)
  GtkCellRendererToggle *cell;
  gchar                 *path_str;
  gpointer               data;
{
  GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(data));
  GtkTreeIter iter;
  struct nessus_plugin *plugin;

  gtk_tree_model_get_iter_from_string(model, &iter, path_str);

  /* if the item is a family instead of a plugin, toggle the state of
   * all the children as well.  Since only families have children, we
   * can determine whether the iter points to a family by checking
   * whether we can get an iterator for its children.
   */
  if (gtk_tree_model_iter_has_child(model, &iter))
  {
    GtkTreeIter child_iter;

    gboolean launch = !is_family_enabled(model, &iter);

    gtk_tree_model_iter_children(model, &child_iter, &iter);
    do
    {
      gtk_tree_model_get(model, &child_iter, COL_PLUGIN, &plugin, -1);
      plugin->enabled = launch;
      trigger_row_update(model, &child_iter);
    }
    while (gtk_tree_model_iter_next(model, &child_iter));
  }
  else
  {
    GtkTreeIter parent_iter;

    gtk_tree_model_get(model, &iter, COL_PLUGIN, &plugin, -1);
    plugin->enabled = ! plugin->enabled;
    trigger_row_update(model, &iter);

    /* update the parent too because the active flag of the family
     * depends on the active flags of all of its children */
    gtk_tree_model_iter_parent(model, &parent_iter, &iter);
    trigger_row_update(model, &parent_iter);
  }
}

/* Called when the user double clicks on a row of the tree view.  Pop up
 * the plugin info dialog if the row is a plugin, not a family.
 */
static void
row_activated(treeview, path, column, user_data)
  GtkTreeView *treeview;
  GtkTreePath *path;
  GtkTreeViewColumn *column;
  gpointer user_data;
{
  GtkTreeModel * model = gtk_tree_view_get_model(treeview);
  int depth = gtk_tree_path_get_depth(path);

  if (depth > 1)
  {
    GtkTreeIter iter;
    struct nessus_plugin *plugin;
    gtk_tree_model_get_iter(model, &iter, path);
    gtk_tree_model_get(model, &iter, COL_PLUGIN, &plugin, -1);

    plugin_info_window_setup(plugin, plugin->name);
  }
}


/* Make the check boxes and warning "buttons" work on the first mouse click
 *
 * Normally, the tree view only selects a line on the first click with
 * the mouse button.  Only clicks on a selected row will toggle a check
 * box.  To work around that, we handle button clicks ourselves in some
 * cases.  Any click on in the column with the check box will call
 * gtk_tree_view_set_cursor for the row/column which will lead to the
 * corresponding check box being toggled.  Also, a click on a warning
 * sign will pop up the warning dialog.
 *
 * When the event was handled, i.e. one of the two cases above occurred,
 * the function returns TRUE to indicate that the event was handled.
 * Otherwise FALSE is returned to indicate that the event wasn't handled
 * yet so that we get the default behavior of the tree view widget in
 * all other cases.
 */
static gboolean
button_press_event(tree, event, user_data)
  GtkTreeView *tree;
  GdkEventButton *event;
  gpointer user_data;
{
  GtkTreePath * path;
  GtkTreeViewColumn *column;
  int colnum;
  gboolean event_was_handled = FALSE;

  if (!gtk_tree_view_get_path_at_pos(tree, event->x, event->y, &path, &column,
	  NULL, NULL))
    return FALSE;
  colnum = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "colnum"));

  if (colnum == 1)
  {
    if (should_show_warning(gtk_tree_view_get_model(tree), path, NULL))
    {
      show_info("%s", HLP_WARNING);
    }
    event_was_handled = TRUE;
  }
  else if (colnum == 2)
  {
    gtk_tree_view_set_cursor(tree, path, column, TRUE);
    event_was_handled = TRUE;
  }

  gtk_tree_path_free(path);

  return event_was_handled;
}


/* Set the "active" property of the toggle cell renderer
 */
static void
active_data_func(tree_column, cell, model, iter, data)
  GtkTreeViewColumn *tree_column;
  GtkCellRenderer *cell;
  GtkTreeModel *model;
  GtkTreeIter *iter;
  gpointer data;
{
  struct nessus_plugin *plugin;
  gboolean is_active = FALSE;

  if (gtk_tree_model_iter_has_child(model, iter))
  {
    is_active = is_family_enabled(model, iter) == 0 ? 0:1;
  }
  else
  {
    gtk_tree_model_get(model, iter, COL_PLUGIN, &plugin, -1);
    is_active = plugin->enabled == 0 ? 0:1;
  }

  g_object_set(G_OBJECT(cell), "active", is_active, NULL);
}


/* Set the "pixbuf" property of the warning cell renderer
 */
static void
warning_data_func(tree_column, cell, model, iter, data)
  GtkTreeViewColumn *tree_column;
  GtkCellRenderer *cell;
  GtkTreeModel *model;
  GtkTreeIter *iter;
  gpointer data;
{
  static GdkPixbuf* warning_pixbuf = NULL;
  GdkPixbuf *value = NULL;

  if (should_show_warning(model, NULL, iter))
  {
    if (!warning_pixbuf)
      warning_pixbuf = gdk_pixbuf_new_from_xpm_data(
	(const char**)warning_small_xpm);
    value = warning_pixbuf;
  }

  g_object_set(G_OBJECT(cell), "pixbuf", value, NULL);
}



/* Create a new plugin tree and model */
GtkWidget *
prefs_create_plugins_tree(context, ctrls)
  struct context *context;
  struct arglist *ctrls;
{
  GtkWidget *tree;
  GtkTreeSelection *selection;
  GtkTreeViewColumn *column;
  GtkCellRenderer *renderer;

  /* make sure the "statistics-changed" signal exists */
  create_statistics_changed_signal();

  /* Create the tree and its columns*/
  tree = gtk_tree_view_new();
  arg_add_value(ctrls, "PLUGINS_TREE_VIEW", ARG_PTR, -1, tree);

  renderer = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer,
      "text", COL_NAME, NULL);
  gtk_tree_view_column_set_resizable(column, TRUE);
  gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
  g_object_set_data(G_OBJECT(column), "colnum", GINT_TO_POINTER(0));

  renderer = gtk_cell_renderer_pixbuf_new();
  column = gtk_tree_view_column_new_with_attributes(_("Warning"), renderer,
      NULL);
  gtk_tree_view_column_set_cell_data_func(column, renderer, warning_data_func,
      NULL, NULL);
  gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
  g_object_set_data(G_OBJECT(column), "colnum", GINT_TO_POINTER(1));

  renderer = gtk_cell_renderer_toggle_new();
  g_signal_connect(renderer, "toggled", G_CALLBACK(active_toggled), tree);
  column = gtk_tree_view_column_new_with_attributes(_("Active"), renderer,
      NULL);
  gtk_tree_view_column_set_cell_data_func(column, renderer, active_data_func,
      NULL, NULL);
  gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
  g_object_set_data(G_OBJECT(column), "colnum", GINT_TO_POINTER(2));

  /* selection settings */
  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
  gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);

  g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(row_activated),
      NULL);
  g_signal_connect(G_OBJECT(tree), "button-press-event",
      G_CALLBACK(button_press_event), NULL);

  return tree;
}


/* Fill the context's tree model with the plugins, applying the filter
 * from ctrls if one is set.
 */
static void
fill_plugin_tree_store(context, ctrls)
  struct context *context;
  struct arglist *ctrls;
{
  struct nessus_plugin *plugs = context->plugins;
  GtkTreeModel *store = GTK_TREE_MODEL(context->plugin_tree_store);
  struct plugin_filter *filter = arg_get_value(ctrls, "FILTER");
  GHashTable* family_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
      NULL, (GDestroyNotify)gtk_tree_path_free);
  
  /* add the plugins and families.  The family nodes are created when
   * the first plugin of that family is encountered */
  while(plugs != NULL )
  {
    GtkTreeIter family_iter, insert_iter;
    char *plug_family;
    GtkTreePath * family_path;
    /* char *summary = arg_get_value(plugs->value, "SUMMARY"); */

    if(filter && filter_plugin(filter, plugs))
    {
      plugs = plugs->next;
      continue;
    }

    /* Determine the iter of the family node for the current plugin.  If
     * the node doesn't exist yet, create it. */
    plug_family = plugs->family;
    family_path = g_hash_table_lookup(family_hash, plug_family);

    if (family_path == NULL )
    {
      gtk_tree_store_append(GTK_TREE_STORE(store), &family_iter, NULL);
      family_path = gtk_tree_model_get_path(store, &family_iter);
      g_hash_table_insert(family_hash, plug_family, family_path);
      gtk_tree_store_set(GTK_TREE_STORE(store), &family_iter,
	  COL_NAME, plug_family, COL_PLUGIN, NULL, -1);
    }
    else
    {
      gtk_tree_model_get_iter(store, &family_iter, family_path);
    }

    /* Now family_iter should be set to the iter of the family the
     * current plugin belongs to.  Append the plugin to that family. */
    gtk_tree_store_append(GTK_TREE_STORE(store), &insert_iter, &family_iter);
    gtk_tree_store_set(GTK_TREE_STORE(store), &insert_iter,
	COL_NAME, plugs->name, COL_PLUGIN, plugs, -1);

    plugs = plugs->next;
  }

  g_hash_table_destroy(family_hash);
}


/* Update the plugin tree with a different context.  The plugin-tree
 * maintains a tree store and a sorted model in the context.  Make the
 * tree store of the new context the model shown in the plugin tree view
 * (taken from ctrls).  If the store doesn't exist yet or if the
 * force_rebuild parameter is TRUE, this function creates a new store.
 *
 * Also, connect to some of the model's signals so that we can emit the
 * statistics_changed signal.
 */
void
prefs_plugin_tree_context_changed(context, ctrls, force_rebuild)
  struct context *context;
  struct arglist *ctrls;
  gboolean force_rebuild;
{
  GtkTreeView *tree_view = arg_get_value(ctrls, "PLUGINS_TREE_VIEW");
  GtkTreeModel *model = context->plugin_tree_model;
  GtkTreeModel *old_model = gtk_tree_view_get_model(tree_view);

  /* if the caller wants, force a rebuild of the store by deleting the
   * old one */
  if (force_rebuild)
    context_reset_plugin_tree(context);

  /* if the context doesn't have a tree store yet, create it together
   * with the sort model and fill it */
  if (context->plugin_tree_store == NULL)
  {
    context->plugin_tree_store = gtk_tree_store_new(NUM_COLS, G_TYPE_STRING,
	G_TYPE_POINTER);

    model = gtk_tree_model_sort_new_with_model(
      GTK_TREE_MODEL(context->plugin_tree_store));
    gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
	0, GTK_SORT_ASCENDING);
    context->plugin_tree_model = model;

    fill_plugin_tree_store(context, ctrls);
    g_object_set_data(G_OBJECT(context->plugin_tree_store), "plugins",
	context->plugins);
  }

  /* Set the new model, reconnecting the signals that we need to emit
   * the statistics_changed signal */
  if (old_model != NULL)
  {
    g_signal_handlers_disconnect_by_func(old_model,
	emit_statistics_when_row_deleted, tree_view);
    g_signal_handlers_disconnect_by_func(old_model,
	emit_statistics_when_row_changed, tree_view);
  }

  gtk_tree_view_set_model(tree_view, model);

  if (model != NULL)
  {
    g_signal_connect(model, "row-changed",
	emit_statistics_when_row_changed, tree_view);
    g_signal_connect(model, "row-deleted",
	emit_statistics_when_row_deleted, tree_view);
  }

  /* When a new model is set, the statistics will have changed: */
  emit_statistics_changed(tree_view);
}


/* enable/disable all plugins in the tree.
 */
void
prefs_plugin_tree_enable_all(tree, enable)
  GtkTreeView *tree;
  int enable;
{
  GtkTreeModel *model = gtk_tree_view_get_model(tree);
  GtkTreeIter iter, child_iter;
  struct nessus_plugin *plugin;

  if (gtk_tree_model_get_iter_first(model, &iter))
  {
    do
    {
      if (gtk_tree_model_iter_children(model, &child_iter, &iter))
      {
	do
	{
	  gtk_tree_model_get(model, &child_iter, COL_PLUGIN, &plugin, -1);
	  plugin->enabled = enable;
	  trigger_row_update(model, &child_iter);
	}
	while (gtk_tree_model_iter_next(model, &child_iter));

	trigger_row_update(model, &iter);
      }
    }
    while (gtk_tree_model_iter_next(model, &iter));
  }	
}

/* Compute some statistics for the plugin tree
 *
 * The tree_view parameter must be a tree view created by
 * prefs_create_plugins_tree and filled with
 * prefs_fill_plugin_tree_model.
 *
 * The function determines the total number of plugins and the number of
 * active plugins and stores these values is *total and *enabled.
 */
void
prefs_plugin_tree_statistics(tree_view, total, enabled)
  GtkTreeView *tree_view;
  int *total;
  int *enabled;
{
  GtkTreeModel *tree_model = gtk_tree_view_get_model(tree_view);
  GtkTreeModel *real_model = gtk_tree_model_sort_get_model(
    GTK_TREE_MODEL_SORT(tree_model));
  struct nessus_plugin *plugins = g_object_get_data(G_OBJECT(real_model),
      "plugins");

  *total = 0;
  *enabled = 0;

  while (plugins != NULL)
  {
    *total += 1;
    if (plugins->enabled)
      *enabled += 1;

    plugins = plugins->next;
  }
}


#endif /* USE_GTK */
