/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include <config.h>

#include <glib/gi18n.h>
#include <glibtop/procopenfiles.h>

#include <sys/wait.h>

#include <set>
#include <string>
#include <sstream>
#include <iterator>

#include <glibmm/regex.h>

#include "application.h"
#include "lsof.h"
#include "util.h"


using std::string;


namespace
{
class Lsof
{
Glib::RefPtr<Glib::Regex> re;

bool
matches (const string &filename) const
{
  return this->re->match (filename);
}

public:

Lsof(const string &pattern,
     bool          caseless)
{
  Glib::RegexCompileFlags flags = static_cast<Glib::RegexCompileFlags>(0);

  if (caseless)
    flags |= Glib::REGEX_CASELESS;

  this->re = Glib::Regex::create (pattern, flags);
}


template<typename OutputIterator>
void
search (const ProcInfo &info,
        OutputIterator  out) const
{
  glibtop_open_files_entry *entries;
  glibtop_proc_open_files buf;

  entries = glibtop_get_proc_open_files (&buf, info.pid);

  for (unsigned i = 0; i != buf.number; ++i)
    if (entries[i].type & GLIBTOP_FILE_TYPE_FILE)
      {
        const string filename (entries[i].info.file.name);
        if (this->matches (filename))
          *out++ = filename;
      }

  g_free (entries);
}
};



// GUI Stuff


enum ProcmanLsof
{
  PROCMAN_LSOF_COL_PIXBUF,
  PROCMAN_LSOF_COL_PROCESS,
  PROCMAN_LSOF_COL_PID,
  PROCMAN_LSOF_COL_FILENAME,
  PROCMAN_LSOF_NCOLS
};


struct GUI: private procman::NonCopyable
{
  GtkListStore *model;
  GtkSearchEntry *entry;
  GtkWindow *window;
  GtkLabel *count;
  GsmApplication *app;
  bool case_insensitive;
  bool regex_error_displayed;


  GUI()
    : model (NULL),
    entry (NULL),
    window (NULL),
    count (NULL),
    app (NULL),
    case_insensitive (),
    regex_error_displayed (false)
  {
    procman_debug ("New Lsof GUI %p", (void *) this);
  }


  ~GUI()
  {
    procman_debug ("Destroying Lsof GUI %p", (void *) this);
  }


  void
  update_count (unsigned count)
  {
    gchar *title;

    if (this->pattern ().length () == 0)
      title = g_strdup_printf (ngettext ("%d open file", "%d open files", count), count);
    else
      title = g_strdup_printf (ngettext ("%d matching open file", "%d matching open files", count), count);
    gtk_window_set_title (this->window, title);
    g_free (title);
  }


  string
  pattern () const
  {
    return gtk_entry_get_text (GTK_ENTRY (this->entry));
  }


  void
  search ()
  {
    typedef std::set<string> MatchSet;

    bool regex_error = false;

    gtk_list_store_clear (this->model);
    try {
        Lsof lsof (this->pattern (), this->case_insensitive);

        unsigned count = 0;

        for (const auto&v : app->processes)
          {
            const auto&info = v.second;
            MatchSet matches;
            lsof.search (info, std::inserter (matches, matches.begin ()));
            count += matches.size ();

            for (const auto&match : matches)
              {
                GtkTreeIter file;
                gtk_list_store_append (this->model, &file);
                gtk_list_store_set (this->model, &file,
                                    PROCMAN_LSOF_COL_PIXBUF, info.pixbuf->gobj (),
                                    PROCMAN_LSOF_COL_PROCESS, info.name.c_str (),
                                    PROCMAN_LSOF_COL_PID, info.pid,
                                    PROCMAN_LSOF_COL_FILENAME, match.c_str (),
                                    -1);
              }
          }

        this->update_count (count);
      }
    catch (Glib::RegexError&error) {
        regex_error = true;
      }

    if (regex_error && !this->regex_error_displayed)
      {
        this->regex_error_displayed = true;
        gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (entry)), GTK_STYLE_CLASS_ERROR);
      }
    else if (!regex_error && this->regex_error_displayed)
      {
        this->regex_error_displayed = false;
        gtk_style_context_remove_class (gtk_widget_get_style_context (GTK_WIDGET (entry)), GTK_STYLE_CLASS_ERROR);
      }
  }


  static void
  search_changed (GtkSearchEntry *,
                  gpointer data)
  {
    static_cast<GUI*>(data)->search ();
  }


  static void
  close_button_clicked (GtkButton *,
                        gpointer data)
  {
    GUI *gui = static_cast<GUI*>(data);

    gtk_widget_destroy (GTK_WIDGET (gui->window));
    delete gui;
  }


  static void
  case_button_toggled (GtkToggleButton *button,
                       gpointer         data)
  {
    bool state = gtk_toggle_button_get_active (button);

    static_cast<GUI*>(data)->case_insensitive = state;
  }


  static gboolean
  window_delete_event (GtkWidget *,
                       GdkEvent *,
                       gpointer data)
  {
    delete static_cast<GUI*>(data);
    return FALSE;
  }
};
}




void
procman_lsof (GsmApplication *app)
{
  GtkListStore *model = \
    gtk_list_store_new (PROCMAN_LSOF_NCOLS,
                        GDK_TYPE_PIXBUF,    // PROCMAN_LSOF_COL_PIXBUF
                        G_TYPE_STRING,      // PROCMAN_LSOF_COL_PROCESS
                        G_TYPE_UINT,        // PROCMAN_LSOF_COL_PID
                        G_TYPE_STRING       // PROCMAN_LSOF_COL_FILENAME
                        );

  GtkTreeView *tree = GTK_TREE_VIEW (gtk_tree_view_new_with_model (GTK_TREE_MODEL (model)));

  g_object_unref (model);

  GtkTreeViewColumn *column;
  GtkCellRenderer *renderer;

  // PIXBUF / PROCESS

  column = gtk_tree_view_column_new ();

  renderer = gtk_cell_renderer_pixbuf_new ();
  gtk_tree_view_column_pack_start (column, renderer, FALSE);
  gtk_tree_view_column_set_attributes (column, renderer,
                                       "pixbuf", PROCMAN_LSOF_COL_PIXBUF,
                                       NULL);

  renderer = gtk_cell_renderer_text_new ();
  gtk_tree_view_column_pack_start (column, renderer, FALSE);
  gtk_tree_view_column_set_attributes (column, renderer,
                                       "text", PROCMAN_LSOF_COL_PROCESS,
                                       NULL);

  gtk_tree_view_column_set_title (column, _("Process"));
  gtk_tree_view_column_set_sort_column_id (column, PROCMAN_LSOF_COL_PROCESS);
  gtk_tree_view_column_set_resizable (column, TRUE);
  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
  gtk_tree_view_column_set_min_width (column, 10);
  gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), PROCMAN_LSOF_COL_PROCESS,
                                        GTK_SORT_ASCENDING);


  // PID
  renderer = gtk_cell_renderer_text_new ();
  column = gtk_tree_view_column_new_with_attributes (_("PID"), renderer,
                                                     "text", PROCMAN_LSOF_COL_PID,
                                                     NULL);
  gtk_tree_view_column_set_sort_column_id (column, PROCMAN_LSOF_COL_PID);
  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
  gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);


  // FILENAME
  renderer = gtk_cell_renderer_text_new ();
  column = gtk_tree_view_column_new_with_attributes (_("Filename"), renderer,
                                                     "text", PROCMAN_LSOF_COL_FILENAME,
                                                     NULL);
  gtk_tree_view_column_set_sort_column_id (column, PROCMAN_LSOF_COL_FILENAME);
  gtk_tree_view_column_set_resizable (column, TRUE);
  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
  gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);


  GtkWindow *dialog;

  GtkBuilder *builder = gtk_builder_new ();

  gtk_builder_add_from_resource (builder, "/org/gnome/gnome-system-monitor/data/lsof.ui", NULL);

  dialog = GTK_WINDOW (gtk_builder_get_object (builder, "lsof_dialog"));

  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (app->main_window));

  GtkSearchEntry *entry = GTK_SEARCH_ENTRY (gtk_builder_get_object (builder, "entry"));

  GtkCheckButton *case_button = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "case_button"));

// Scrolled TreeView
  GtkScrolledWindow *scrolled = GTK_SCROLLED_WINDOW (gtk_builder_get_object (builder, "scrolled"));

  gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (tree));

  GUI *gui = new GUI;   // wil be deleted by the close button or delete-event

  gui->app = app;
  gui->model = model;
  gui->window = dialog;
  gui->entry = entry;
  gui->case_insensitive = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (case_button));

  g_signal_connect (G_OBJECT (entry), "search-changed",
                    G_CALLBACK (GUI::search_changed), gui);
  g_signal_connect (G_OBJECT (case_button), "toggled",
                    G_CALLBACK (GUI::case_button_toggled), gui);
  g_signal_connect (G_OBJECT (dialog), "delete-event",
                    G_CALLBACK (GUI::window_delete_event), gui);

  gtk_builder_connect_signals (builder, NULL);

  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (GsmApplication::get ()->main_window));
  gtk_widget_show_all (GTK_WIDGET (dialog));
  gui->search ();

  g_object_unref (G_OBJECT (builder));
}
