/* 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.
 */

/** @file
 * The plugins are in the clients GUI, showing a Tree of plugins, and some
 * controls like buttons to dis/enable everything in the tree, filter the
 * shown NVTs and NVT- related options.
 */

#include <includes.h>

#ifdef USE_GTK
#include <gtk/gtk.h>

#include "../nessus_plugin.h"
#include "../plugin_infos.h"
#include "../preferences.h"
#include "globals.h"
#include "error_dlg.h"
#include "prefs_help.h"
#include "filter.h"
#include "prefs_plugins.h"
#include "prefs_plugins_tree.h"
#include "readonly.h"


static int
set_filter (GtkWidget *w, struct arglist *ctrls)
{
  struct plugin_filter filter;
  char *old;
  struct nessus_plugin *plugins = Context->plugins;
  unsigned int filter_count = 0;
  GtkWidget *label = arg_get_value (ctrls, "FILTER_LABEL");

  ask_filter(&filter);
  if(!filter.pattern)
    return 0;


  if ((old = arg_get_value(ctrls, "FILTER")))
    {
      arg_set_value (ctrls, "FILTER", sizeof(filter), &filter);
    }
  else
    arg_add_value (ctrls, "FILTER", ARG_STRUCT, sizeof(filter), &filter);


  Filter = filter;
  memcpy (&Filter, &filter, sizeof(filter));

  while (plugins != NULL )
    {
      if (filter_plugin (&filter, plugins))
        plugins->enabled = 0;
      else
        filter_count++;

      plugins = plugins->next;
    }

  if (strlen (filter.pattern) == 0)
    {
      gtk_label_set_text (GTK_LABEL(label), _("No filter active"));
    }
  else
    {
      char *text = g_strdup_printf (_("Filter matches %u plugins"), filter_count);
      gtk_label_set_text (GTK_LABEL(label), text);
      g_free (text);
    }

  prefs_plugin_tree_context_changed (Context, ctrls, TRUE);

  return 0;
}

static int
disable_all (GtkWidget *w, gpointer data)
{
  prefs_plugin_tree_enable_all(GTK_TREE_VIEW(data), FALSE);

  return 0;
}

static int
enable_all (GtkWidget *w, gpointer data)
{
  prefs_plugin_tree_enable_all(GTK_TREE_VIEW(data), TRUE);

  return 0;
}

static void
expand_all (GtkWidget *w, gpointer data)
{
  gtk_tree_view_expand_all(GTK_TREE_VIEW(data));

  /* Normally the tree should automatically be redrawn.  This doesn't
   * happen in gtk+ 2.0.2, though, so we trigger a redraw explicitly
   */
  gtk_widget_queue_draw(GTK_WIDGET(data));
}

static void
collapse_all (GtkWidget *w, gpointer data)
{
  gtk_tree_view_collapse_all (GTK_TREE_VIEW(data));
}

/**
 * @brief Update the statistics label.
 *
 * The user_data argument must be a pointer to the label widget.  This
 * function expects the label widget to have tree widget for which
 * statistics is to be shown to be associated to the label widget under
 * the name "tree" so that it can be retrieved with g_object_get_data.
 *
 * This function is set up as an idle handler by
 * update_plugin_statistics_label_when_idle.  Therefore it sets the
 * label's "idle-update-id" user data value to 0 to indicate that it has
 * run and should be added again.
 */
static gboolean
update_plugin_statistics_label (gpointer user_data)
{
  GtkWidget *label = GTK_WIDGET(user_data);
  GtkTreeView *tree = GTK_TREE_VIEW (g_object_get_data (G_OBJECT(label), "tree"));
  int total, enabled;
  char*text;

  prefs_plugin_tree_statistics(tree, &total, &enabled);

  text = g_strdup_printf(_("%d plugins; %d enabled"), total, enabled);

  gtk_label_set_text (GTK_LABEL(label), text);

  g_free(text);

  g_object_set_data(G_OBJECT(label), "idle-update-id", GUINT_TO_POINTER(0));

  return FALSE;
}

/**
 * @brief Schedule the idle update of the statistics label
 *
 * This function is called by the handlers for some of the signals sent
 * by the model behind the plugin tree that indicate that the model has
 * changed and that the statistics may have to be updated.
 *
 * Some user actions -- e.g. enable all -- can lead to many
 * signals being emitted at once, so to avoid unnecessary recomputations
 * of the statistics this function sets up
 * update_plugin_statistics_label to be called when the client becomes
 * idle.  To manage this, the function adds the gtk idle id for the
 * function as the userdata "idle-update-id" to the label widget.
 * update_plugin_statistics_label is only added as idle handler if that
 * attribute is 0 so that it will be set at most once before the client
 * become idle.
 */
static void
update_plugin_statistics_label_when_idle (GtkWidget *label)
{
  guint idle_id;

  idle_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT(label),
                                                 "idle-update-id"));
  if (!idle_id)
    {
      idle_id = g_idle_add (update_plugin_statistics_label, label);

      g_object_set_data (G_OBJECT(label), "idle-update-id",
                         GUINT_TO_POINTER(idle_id));
    }
}

static void
update_statistics (GtkWidget *tree_view, gpointer user_data)
{
  update_plugin_statistics_label_when_idle (user_data);
}

/**
 * @brief Builds up the plugin area with plugin tree, buttons and preferences.
 *
 * Draws the main window showing informations about the plugins of the server.
 * This includes the list to en/disable NVTs and groups of NVTs, some
 * convenient buttons like "Enable all" and the preferences hooks for dependency
 * activation, dependency silencing and automatic enabling for new plugins.
 * 
 * @param context Context of interest.
 * 
 * @return Arglist with the GUI widgets hooked into.
 */
struct arglist *
prefs_dialog_plugins (struct context *context)
{
  struct arglist *ctrls = emalloc(sizeof(struct arglist));
  GtkWidget *frame;
  GtkWidget *w_box;
  GtkWidget *hbox;
  GtkWidget *button;
  GtkWidget *tree_window;
  GtkWidget *tree;
  GtkWidget *label;
  GtkTreeModel *model;

  frame = gtk_frame_new(_("Plugin selection"));
  gtk_container_border_width(GTK_CONTAINER(frame), 10);
  arg_add_value(ctrls, "FRAME", ARG_PTR, -1, frame);
  read_only_set_recurse(frame);

  w_box = gtk_vbox_new(TRUE, 5);
  gtk_container_border_width(GTK_CONTAINER(w_box), 5);
  gtk_container_add(GTK_CONTAINER(frame), w_box);
  gtk_box_set_homogeneous(GTK_BOX(w_box), FALSE);
  read_only_set_recurse(w_box);

  /* Tree view of the plugins */
  tree_window = gtk_scrolled_window_new(NULL, NULL);
  read_only_set_active(tree_window);
  gtk_container_border_width(GTK_CONTAINER(tree_window), 10);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(tree_window),
      GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(tree_window),
      GTK_SHADOW_IN);
  gtk_box_pack_start(GTK_BOX(w_box), tree_window, TRUE, TRUE, 0);
  tree = prefs_create_plugins_tree(context, ctrls);
  gtk_container_add(GTK_CONTAINER(tree_window), tree);
  gtk_widget_show(tree);
  gtk_widget_show(tree_window);
  arg_add_value(ctrls, "PLUGIN_TREE", ARG_PTR, -1, tree);

  /* Label showing some statistics */
  label = gtk_label_new (_("No plugins"));
  gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
  gtk_box_pack_start (GTK_BOX(w_box), label, FALSE, FALSE, 0);
  gtk_widget_show(label);
  model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree));
  /* Connect to some tree model signals so that we update the label when
   * the model changes.  The way the model is updated currently means
   * that only row-changed and row-deleted are interesting. */
  g_signal_connect (G_OBJECT(tree), "statistics-changed",
                   (GCallback) update_statistics, label);
  g_object_set_data(G_OBJECT(label), "tree", tree);
  g_object_set_data(G_OBJECT(label), "idle-update-id", GUINT_TO_POINTER(0));

  /* Filter */
  hbox = gtk_hbox_new(FALSE, 5);
  gtk_box_pack_start(GTK_BOX(w_box), hbox, FALSE, FALSE, 5);
  gtk_widget_show(hbox);

  /* Label showing the filter status */
  label = gtk_label_new(_("No filter active"));
  gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
  gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
  gtk_widget_show(label);
  arg_add_value(ctrls, "FILTER_LABEL", ARG_PTR, -1, label);

  /* Filter Button */
  button = gtk_button_new_with_mnemonic(_("Fi_lter..."));
  g_signal_connect(GTK_OBJECT(button),
      "clicked", GTK_SIGNAL_FUNC(set_filter), ctrls);
  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
  gtk_widget_show(button);

  /* Enable all / disable all buttons */
  hbox = gtk_hbox_new(FALSE, 5);
  gtk_box_pack_start(GTK_BOX(w_box), hbox, FALSE, FALSE, 0);
  gtk_widget_show(hbox);

  button = gtk_button_new_with_label(_("Enable all"));
  g_signal_connect(GTK_OBJECT(button),
      "clicked", GTK_SIGNAL_FUNC(enable_all), tree);
  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
  gtk_widget_show(button);

  button = gtk_button_new_with_label(_("Disable all"));
  g_signal_connect(GTK_OBJECT(button),
      "clicked", GTK_SIGNAL_FUNC(disable_all), tree);
  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
  gtk_widget_show(button);

  /* Expand all / collapse all buttons */
  hbox = gtk_hbox_new(FALSE, 5);
  gtk_box_pack_start(GTK_BOX(w_box), hbox, FALSE, FALSE, 0);
  gtk_widget_show(hbox);
  read_only_set_active(hbox);

  button = gtk_button_new_with_label(_("Expand all"));
  g_signal_connect(GTK_OBJECT(button),
      "clicked", GTK_SIGNAL_FUNC(expand_all), tree);
  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
  gtk_widget_show(button);

  button = gtk_button_new_with_label(_("Collapse all"));
  g_signal_connect(GTK_OBJECT(button),
      "clicked", GTK_SIGNAL_FUNC(collapse_all), tree);
  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
  gtk_widget_show(button);

  /* Dependency buttons */
  hbox = gtk_hbox_new(FALSE, 5);
  gtk_box_pack_start (GTK_BOX(w_box), hbox, FALSE, FALSE, 5);
  gtk_widget_show (hbox);

  label = gtk_label_new (_("Dependencies:"));
  gtk_misc_set_alignment (GTK_MISC(label), 0, 0.5);
  gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
  gtk_widget_show(label);

  button =
      gtk_check_button_new_with_label(_("Enable at runtime"));
  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
  gtk_widget_show(button);
  arg_add_value(ctrls, "ENABLE_DEPS_AT_RUNTIME", ARG_PTR, -1, button);

  button = gtk_check_button_new_with_label (_("Silent"));
  gtk_box_pack_start (GTK_BOX(hbox), button, TRUE, TRUE, 0);
  gtk_widget_show (button);
  arg_add_value (ctrls, "SILENT_DEPS", ARG_PTR, -1, button);

  /* Auto-activate new plugins buttons */
  hbox = gtk_hbox_new (FALSE, 5);
  gtk_box_pack_start (GTK_BOX(w_box), hbox, FALSE, FALSE, 5);
  gtk_widget_show (hbox);

  button =
      gtk_check_button_new_with_label(_("Automatically enable new plugins"));
  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
  gtk_widget_show(button);
  arg_add_value(ctrls, "AUTO_ENABLE_NEW_PLUGINS", ARG_PTR, -1, button);

  /* Global NVT timeout */
  hbox = gtk_hbox_new (FALSE, 5);
  gtk_box_pack_start (GTK_BOX(w_box), hbox, FALSE, FALSE, 5);
  gtk_widget_show (hbox);

  label = gtk_label_new (_("Global NVT Timeout (sec):"));
  gtk_misc_set_alignment (GTK_MISC(label), 0, 0.5);
  gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  button = gtk_spin_button_new_with_range (-1, 32000, 2);
  gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0);
  gtk_widget_show (button);
  arg_add_value (ctrls, "GLOBAL_NVT_TIMEOUT", ARG_PTR, -1, button);

  /* Round- up */
  fill_plugins_family (context, ctrls);

  gtk_widget_show (w_box);
  gtk_widget_show (frame);

  return (ctrls);
}

/**
 * Make the plugins widgets "read only" so that the user can browse the
 * plugins tree but cannot make modifications.
 */
void
prefs_plugins_read_only (struct arglist * ctrls, gboolean readonly)
{
  read_only_set_read_only(GTK_WIDGET(arg_get_value(ctrls, "FRAME")),
    readonly);
  prefs_plugin_tree_read_only(GTK_TREE_VIEW(arg_get_value(ctrls,"PLUGIN_TREE")),
      readonly);
}

void
fill_plugins_family (struct context *context, struct arglist *ctrls)
{
  /* Init tree data */
  prefs_plugin_tree_context_changed (context, ctrls, FALSE);
}

#endif /* USE_GTK */
