/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 *  Copyright © 2013 Red Hat, Inc.
 *
 *  This file is part of Epiphany.
 *
 *  Epiphany 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 of the License, or
 *  (at your option) any later version.
 *
 *  Epiphany 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 Epiphany.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"
#include "clear-data-view.h"

#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <string.h>
#include <webkit2/webkit2.h>

#include "ephy-history-service.h"
#include "ephy-embed-shell.h"
#include "ephy-settings.h"

struct _ClearDataView {
  EphyDataView parent_instance;

  GtkWidget *treeview;
  GtkTreeModel *treestore;
  GtkTreeModelFilter *treemodelfilter;

  GCancellable *cancellable;
};

enum {
  TYPE_COLUMN,
  ACTIVE_COLUMN,
  NAME_COLUMN,
  DATA_COLUMN,
  SENSITIVE_COLUMN
};

G_DEFINE_TYPE (ClearDataView, clear_data_view, EPHY_TYPE_DATA_VIEW)

#define PERSISTENT_DATA_TYPES WEBKIT_WEBSITE_DATA_COOKIES | \
  WEBKIT_WEBSITE_DATA_DISK_CACHE | \
  WEBKIT_WEBSITE_DATA_OFFLINE_APPLICATION_CACHE | \
  WEBKIT_WEBSITE_DATA_LOCAL_STORAGE | \
  WEBKIT_WEBSITE_DATA_WEBSQL_DATABASES | \
  WEBKIT_WEBSITE_DATA_INDEXEDDB_DATABASES | \
  WEBKIT_WEBSITE_DATA_PLUGIN_DATA | \
  WEBKIT_WEBSITE_DATA_HSTS_CACHE | \
  WEBKIT_WEBSITE_DATA_ITP

typedef struct {
  guint id;
  WebKitWebsiteDataTypes type;
  const char *name;
} DataEntry;

static const DataEntry data_entries[] = {
  { 0x001, WEBKIT_WEBSITE_DATA_COOKIES, N_("Cookies") },
  { 0x002, WEBKIT_WEBSITE_DATA_DISK_CACHE, N_("HTTP disk cache") },
  { 0x004, WEBKIT_WEBSITE_DATA_LOCAL_STORAGE, N_("Local storage data") },
  { 0x008, WEBKIT_WEBSITE_DATA_OFFLINE_APPLICATION_CACHE, N_("Offline web application cache") },
  { 0x010, WEBKIT_WEBSITE_DATA_INDEXEDDB_DATABASES, N_("IndexedDB databases") },
  { 0x020, WEBKIT_WEBSITE_DATA_WEBSQL_DATABASES, N_("WebSQL databases") },
  { 0x040, WEBKIT_WEBSITE_DATA_PLUGIN_DATA, N_("Plugins data") },
  { 0x080, WEBKIT_WEBSITE_DATA_HSTS_CACHE, N_("HSTS policies cache") },
  { 0x100, WEBKIT_WEBSITE_DATA_ITP, N_("Intelligent Tracking Prevention data") }
};

static WebKitWebsiteDataManager *
get_website_data_manger (void)
{
  WebKitWebContext *web_context;

  web_context = ephy_embed_shell_get_web_context (ephy_embed_shell_get_default ());
  return webkit_web_context_get_website_data_manager (web_context);
}

static void
website_data_fetched_cb (WebKitWebsiteDataManager *manager,
                         GAsyncResult             *result,
                         ClearDataView            *clear_data_view)
{
  GList *data_list;
  GtkTreeStore *treestore;
  GError *error = NULL;
  int active_items;

  data_list = webkit_website_data_manager_fetch_finish (manager, result, &error);
  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
    g_error_free (error);
    return;
  }

  ephy_data_view_set_is_loading (EPHY_DATA_VIEW (clear_data_view), FALSE);

  if (!data_list) {
    ephy_data_view_set_has_data (EPHY_DATA_VIEW (clear_data_view), FALSE);

    if (error)
      g_error_free (error);
    return;
  }

  ephy_data_view_set_has_data (EPHY_DATA_VIEW (clear_data_view), TRUE);
  active_items = g_settings_get_int (EPHY_SETTINGS_MAIN, EPHY_PREFS_ACTIVE_CLEAR_DATA_ITEMS);

  treestore = GTK_TREE_STORE (clear_data_view->treestore);
  for (guint i = 0; i < G_N_ELEMENTS (data_entries); i++) {
    GtkTreeIter parent_iter;
    gboolean empty = TRUE;

    gtk_tree_store_insert_with_values (treestore, &parent_iter, NULL, -1,
                                       TYPE_COLUMN, data_entries[i].type,
                                       ACTIVE_COLUMN, active_items & data_entries[i].id,
                                       NAME_COLUMN, _(data_entries[i].name),
                                       DATA_COLUMN, NULL,
                                       SENSITIVE_COLUMN, TRUE,
                                       -1);
    for (GList *l = data_list; l && l->data; l = g_list_next (l)) {
      WebKitWebsiteData *data = (WebKitWebsiteData *)l->data;

      if (!(webkit_website_data_get_types (data) & data_entries[i].type))
        continue;

      gtk_tree_store_insert_with_values (treestore, NULL, &parent_iter, -1,
                                         TYPE_COLUMN, data_entries[i].type,
                                         ACTIVE_COLUMN, active_items & data_entries[i].id,
                                         NAME_COLUMN, webkit_website_data_get_name (data),
                                         DATA_COLUMN, webkit_website_data_ref (data),
                                         SENSITIVE_COLUMN, TRUE,
                                         -1);
      empty = FALSE;
    }

    if (empty)
      gtk_tree_store_remove (treestore, &parent_iter);
  }

  g_list_free_full (data_list, (GDestroyNotify)webkit_website_data_unref);
}

static gboolean
all_children_visible (GtkTreeModel       *model,
                      GtkTreeIter        *child_iter,
                      GtkTreeModelFilter *filter)
{
  GtkTreeIter filter_iter;

  gtk_tree_model_filter_convert_child_iter_to_iter (filter, &filter_iter, child_iter);
  return gtk_tree_model_iter_n_children (model, child_iter) == gtk_tree_model_iter_n_children (GTK_TREE_MODEL (filter), &filter_iter);
}

static void
on_clear_button_clicked (ClearDataView *clear_data_view)
{
  GtkTreeIter top_iter;
  WebKitWebsiteDataTypes types_to_clear = 0;
  GList *data_to_remove = NULL;
  WebKitWebsiteDataTypes types_to_remove = 0;

  if (!gtk_tree_model_get_iter_first (clear_data_view->treestore, &top_iter))
    return;

  do {
    guint type;
    gboolean active;
    GtkTreeIter child_iter;

    gtk_tree_model_get (clear_data_view->treestore, &top_iter,
                        TYPE_COLUMN, &type,
                        ACTIVE_COLUMN, &active,
                        -1);
    if (active && all_children_visible (clear_data_view->treestore, &top_iter, clear_data_view->treemodelfilter)) {
      types_to_clear |= type;
    } else if (gtk_tree_model_iter_children (clear_data_view->treestore, &child_iter, &top_iter)) {
      gboolean empty = TRUE;

      do {
        WebKitWebsiteData *data;
        GtkTreeIter filter_iter;

        if (gtk_tree_model_filter_convert_child_iter_to_iter (clear_data_view->treemodelfilter, &filter_iter, &child_iter)) {
          gtk_tree_model_get (clear_data_view->treestore, &child_iter,
                              ACTIVE_COLUMN, &active,
                              DATA_COLUMN, &data,
                              -1);

          if (active) {
            data_to_remove = g_list_prepend (data_to_remove, data);
            empty = FALSE;
          } else
            webkit_website_data_unref (data);
        }
      } while (gtk_tree_model_iter_next (clear_data_view->treestore, &child_iter));

      if (!empty)
        types_to_remove |= type;
    }
  } while (gtk_tree_model_iter_next (clear_data_view->treestore, &top_iter));

  if (types_to_clear) {
    webkit_website_data_manager_clear (get_website_data_manger (),
                                       types_to_clear, 0,
                                       NULL, NULL, NULL);
  }

  if (types_to_remove) {
    webkit_website_data_manager_remove (get_website_data_manger (),
                                        types_to_remove, data_to_remove,
                                        NULL, NULL, NULL);
  }

  g_list_free_full (data_to_remove, (GDestroyNotify)webkit_website_data_unref);

  /* Reload tree */
  ephy_data_view_set_is_loading (EPHY_DATA_VIEW (clear_data_view), TRUE);
  gtk_tree_store_clear (GTK_TREE_STORE (clear_data_view->treestore));
  webkit_website_data_manager_fetch (get_website_data_manger (),
                                     PERSISTENT_DATA_TYPES,
                                     clear_data_view->cancellable,
                                     (GAsyncReadyCallback)website_data_fetched_cb,
                                     clear_data_view);
}

static gboolean
any_item_checked (ClearDataView *self)
{
  GtkTreeIter top_iter;

  if (!gtk_tree_model_get_iter_first (self->treestore, &top_iter))
    return FALSE;

  do {
    gboolean active;
    GtkTreeIter child_iter;

    gtk_tree_model_get (self->treestore, &top_iter,
                        ACTIVE_COLUMN, &active, -1);
    if (active) {
      return TRUE;
    } else if (gtk_tree_model_iter_children (self->treestore, &child_iter, &top_iter)) {
      do {
        GtkTreeIter filter_iter;

        if (gtk_tree_model_filter_convert_child_iter_to_iter (self->treemodelfilter, &filter_iter, &child_iter)) {
          gtk_tree_model_get (self->treestore, &child_iter,
                              ACTIVE_COLUMN, &active, -1);
          if (active)
            return TRUE;
        }
      } while (gtk_tree_model_iter_next (self->treestore, &child_iter));
    }
  } while (gtk_tree_model_iter_next (self->treestore, &top_iter));

  return FALSE;
}

static void
item_toggled_cb (GtkCellRendererToggle *renderer,
                 const char            *path_str,
                 ClearDataView         *clear_data_view)
{
  GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
  GtkTreeIter filter_iter, iter;
  gboolean active;

  gtk_tree_model_get_iter (GTK_TREE_MODEL (clear_data_view->treemodelfilter),
                           &filter_iter, path);
  gtk_tree_model_filter_convert_iter_to_child_iter (clear_data_view->treemodelfilter,
                                                    &iter, &filter_iter);
  gtk_tree_model_get (clear_data_view->treestore, &iter,
                      ACTIVE_COLUMN, &active,
                      -1);
  gtk_tree_store_set (GTK_TREE_STORE (clear_data_view->treestore), &iter,
                      ACTIVE_COLUMN, !active,
                      -1);

  if (gtk_tree_model_iter_has_child (clear_data_view->treestore, &iter)) {
    GtkTreeIter child_iter;
    g_autofree char *name = NULL;
    int active_items;

    active_items = g_settings_get_int (EPHY_SETTINGS_MAIN, EPHY_PREFS_ACTIVE_CLEAR_DATA_ITEMS);

    gtk_tree_model_get (clear_data_view->treestore, &iter,
                        NAME_COLUMN, &name,
                        -1);
    for (guint i = 0; i < G_N_ELEMENTS (data_entries); i++) {
      if (g_strcmp0 (gettext (data_entries[i].name), name) == 0) {
        if (active)
          active_items &= ~data_entries[i].id;
        else
          active_items |= data_entries[i].id;

        break;
      }
    }

    g_settings_set_int (EPHY_SETTINGS_MAIN, EPHY_PREFS_ACTIVE_CLEAR_DATA_ITEMS, active_items);

    gtk_tree_model_iter_children (clear_data_view->treestore, &child_iter, &iter);
    do {
      gtk_tree_store_set (GTK_TREE_STORE (clear_data_view->treestore), &child_iter,
                          ACTIVE_COLUMN, !active,
                          -1);
    } while (gtk_tree_model_iter_next (clear_data_view->treestore, &child_iter));
  } else {
    GtkTreeIter parent_iter;

    /* Update the parent */
    gtk_tree_model_iter_parent (clear_data_view->treestore, &parent_iter, &iter);
    if (active) {
      /* When unchecking a child we know the parent should be unchecked too */
      gtk_tree_store_set (GTK_TREE_STORE (clear_data_view->treestore), &parent_iter,
                          ACTIVE_COLUMN, FALSE,
                          -1);
    } else {
      GtkTreeIter child_iter;
      gboolean all_active = TRUE;

      /* When checking a child, parent should be checked if all its children are */
      gtk_tree_model_iter_children (clear_data_view->treestore, &child_iter, &parent_iter);
      do {
        gtk_tree_model_get (clear_data_view->treestore, &child_iter,
                            ACTIVE_COLUMN, &all_active,
                            -1);
      } while (all_active && gtk_tree_model_iter_next (clear_data_view->treestore, &child_iter));

      if (all_active) {
        gtk_tree_store_set (GTK_TREE_STORE (clear_data_view->treestore), &parent_iter,
                            ACTIVE_COLUMN, TRUE,
                            -1);
      }
    }
  }

  gtk_tree_path_free (path);

  ephy_data_view_set_can_clear (EPHY_DATA_VIEW (clear_data_view), any_item_checked (clear_data_view));
}

static void
search_text_changed_cb (ClearDataView *clear_data_view)
{
  gtk_tree_model_filter_refilter (clear_data_view->treemodelfilter);
}

static gboolean
row_visible_func (GtkTreeModel  *model,
                  GtkTreeIter   *iter,
                  ClearDataView *clear_data_view)
{
  const char *search_text;
  char *name;
  gboolean visible;

  if (gtk_tree_model_iter_has_child (model, iter))
    return TRUE;

  search_text = ephy_data_view_get_search_text (EPHY_DATA_VIEW (clear_data_view));
  if (!search_text || search_text[0] == '\0')
    return TRUE;

  gtk_tree_model_get (model, iter,
                      NAME_COLUMN, &name,
                      -1);

  visible = name && strstr (name, search_text);
  g_free (name);

  if (visible) {
    GtkTreeIter parent_iter;
    GtkTreePath *path;

    gtk_tree_model_iter_parent (model, &parent_iter, iter);
    path = gtk_tree_model_get_path (model, &parent_iter);
    gtk_tree_view_expand_row (GTK_TREE_VIEW (clear_data_view->treeview), path, FALSE);
    gtk_tree_path_free (path);
  }

  return visible;
}

static void
clear_data_view_dispose (GObject *object)
{
  ClearDataView *clear_data_view = (ClearDataView *)object;

  if (clear_data_view->cancellable) {
    g_cancellable_cancel (clear_data_view->cancellable);
    g_clear_object (&clear_data_view->cancellable);
  }

  G_OBJECT_CLASS (clear_data_view_parent_class)->dispose (object);
}

static void
clear_data_view_class_init (ClearDataViewClass *klass)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose = clear_data_view_dispose;

  g_type_ensure (WEBKIT_TYPE_WEBSITE_DATA);

  gtk_widget_class_set_template_from_resource (widget_class,
                                               "/org/gnome/epiphany/gtk/clear-data-view.ui");

  gtk_widget_class_bind_template_child (widget_class, ClearDataView, treeview);
  gtk_widget_class_bind_template_child (widget_class, ClearDataView, treestore);
  gtk_widget_class_bind_template_child (widget_class, ClearDataView, treemodelfilter);
  gtk_widget_class_bind_template_callback (widget_class, item_toggled_cb);
  gtk_widget_class_bind_template_callback (widget_class, on_clear_button_clicked);
  gtk_widget_class_bind_template_callback (widget_class, search_text_changed_cb);
}

static void
clear_data_view_init (ClearDataView *clear_data_view)
{
  gtk_widget_init_template (GTK_WIDGET (clear_data_view));

  gtk_tree_model_filter_set_visible_func (clear_data_view->treemodelfilter,
                                          (GtkTreeModelFilterVisibleFunc)row_visible_func,
                                          clear_data_view,
                                          NULL);

  ephy_data_view_set_is_loading (EPHY_DATA_VIEW (clear_data_view), TRUE);

  clear_data_view->cancellable = g_cancellable_new ();

  webkit_website_data_manager_fetch (get_website_data_manger (),
                                     PERSISTENT_DATA_TYPES,
                                     clear_data_view->cancellable,
                                     (GAsyncReadyCallback)website_data_fetched_cb,
                                     clear_data_view);
}
