/*
     This file is part of anastasis-gtk.
     Copyright (C) 2021 Anastasis SARL

     Anastasis 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, or (at your
     option) any later version.

     Anastasis 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 Anastasis; see the file COPYING.  If not, write to the
     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     Boston, MA 02110-1301, USA.
*/

/**
 * @file src/anastasis/anastasis-gtk_handle-policy-meta.c
 * @brief Handle right-click context menu in policy review
 * @author Christian Grothoff
 */
#include <gnunet/gnunet_util_lib.h>
#include "anastasis_gtk_util.h"
#include "anastasis-gtk_action.h"
#include "anastasis-gtk_helper.h"
#include "anastasis-gtk_pe.h"
#include "anastasis-gtk_rows.h"
#include <jansson.h>


/**
 * Context for menu callbacks.
 */
struct MenuContext
{
  /**
   * The row that the user right-clicked, NULL if they clicked past the
   * last one.
   */
  AGRow *row;

};


/**
 * Free a `struct MenuContext`.  Called when the action group holding it goes
 * away, which happens on the next right-click at the latest.
 *
 * @param cls the `struct MenuContext` to free
 */
static void
free_menu_context (gpointer cls)
{
  struct MenuContext *ctx = cls;

  g_clear_object (&ctx->row);
  GNUNET_free (ctx);
}


/**
 * The user selected the 'add policy' menu item.
 *
 * @param action the activated action
 * @param parameter unused
 * @param user_data a `struct MenuContext`
 */
static void
add_from_ctx_menu (GSimpleAction *action,
                   GVariant *parameter,
                   gpointer user_data)
{
  (void) action;
  (void) parameter;
  (void) user_data;
  AG_add_policy ();
}


/**
 * The user selected the 'delete challenge' or 'delete policy' menu item.
 *
 * @param action the activated action
 * @param parameter unused
 * @param user_data a `struct MenuContext`
 */
static void
delete_from_ctx_menu (GSimpleAction *action,
                      GVariant *parameter,
                      gpointer user_data)
{
  struct MenuContext *ctx = user_data;
  guint pindex;

  (void) action;
  (void) parameter;
  if (NULL == ctx->row)
    return;
  pindex = AG_row_uint (ctx->row,
                        "policy_index");
  if (AG_row_bool (ctx->row,
                   "is_challenge"))
    AG_delete_challenge (pindex,
                         AG_row_uint (ctx->row,
                                      "method_index"));
  else
    AG_delete_policy (pindex);
}


/**
 * The user selected the 'edit policy' menu item.
 *
 * @param action the activated action
 * @param parameter unused
 * @param user_data a `struct MenuContext`
 */
static void
edit_from_ctx_menu (GSimpleAction *action,
                    GVariant *parameter,
                    gpointer user_data)
{
  struct MenuContext *ctx = user_data;

  (void) action;
  (void) parameter;
  if (NULL == ctx->row)
    return;
  AG_edit_policy (AG_row_uint (ctx->row,
                               "policy_index"));
}


/**
 * Build the menu model applicable to the row that was clicked.  The two
 * sections are rendered with a separator in between.
 *
 * @param have_row true if the user clicked on a row
 * @param is_challenge true if that row represents a challenge
 * @return the menu model
 */
static GMenuModel *
build_menu (bool have_row,
            bool is_challenge)
{
  GMenu *menu = g_menu_new ();

  if (have_row)
  {
    GMenu *section = g_menu_new ();

    if (! is_challenge)
    {
      /* only show 'edit' entry for lines that are for an entire policy */
      g_menu_append (section,
                     _ ("_Edit policy..."),
                     "policy.edit");
    }
    g_menu_append (section,
                   is_challenge
                   ? _ ("Delete challenge")
                   : _ ("Delete policy"),
                   "policy.delete");
    g_menu_append_section (menu,
                           NULL,
                           G_MENU_MODEL (section));
    g_object_unref (section);
  }
  {
    GMenu *section = g_menu_new ();

    g_menu_append (section,
                   _ ("_Add policy..."),
                   "policy.add");
    g_menu_append_section (menu,
                           NULL,
                           G_MENU_MODEL (section));
    g_object_unref (section);
  }
  return G_MENU_MODEL (menu);
}


/**
 * Install the actions the context menu of @a tv activates; they operate on the
 * row given by @a ctx.
 *
 * @param tv the list the menu belongs to
 * @param ctx context to hand to the actions, ownership is taken
 */
static void
install_actions (GtkWidget *tv,
                 struct MenuContext *ctx)
{
  static const GActionEntry entries[] = {
    { .name = "add",
      .activate = &add_from_ctx_menu },
    { .name = "edit",
      .activate = &edit_from_ctx_menu },
    { .name = "delete",
      .activate = &delete_from_ctx_menu }
  };
  GSimpleActionGroup *actions;

  actions = g_simple_action_group_new ();
  g_action_map_add_action_entries (G_ACTION_MAP (actions),
                                  entries,
                                  G_N_ELEMENTS (entries),
                                  ctx);
  /* the action group owns the context; installing a new group on the next
     right-click releases the previous one */
  g_object_set_data_full (G_OBJECT (actions),
                          "anastasis-menu-context",
                          ctx,
                          &free_menu_context);
  gtk_widget_insert_action_group (tv,
                                  "policy",
                                  G_ACTION_GROUP (actions));
  g_object_unref (actions);
}


/**
 * Return the context menu of @a tv, creating it on first use.  A single
 * popover is reused for the lifetime of the list; only its model and
 * position change.
 *
 * @param tv the list
 * @return the popover to show
 */
static GtkPopover *
get_popover (GtkWidget *tv)
{
  GtkWidget *popover;

  popover = g_object_get_data (G_OBJECT (tv),
                               "anastasis-context-menu");
  if (NULL == popover)
  {
    popover = gtk_popover_menu_new_from_model (NULL);
    gtk_popover_set_has_arrow (GTK_POPOVER (popover),
                               FALSE);
    gtk_widget_set_halign (popover,
                           GTK_ALIGN_START);
    gtk_widget_set_parent (popover,
                           tv);
    g_object_set_data_full (G_OBJECT (tv),
                            "anastasis-context-menu",
                            popover,
                            (GDestroyNotify) & gtk_widget_unparent);
  }
  return GTK_POPOVER (popover);
}


/**
 * We got a right-click on the policy list: compute which menu items are
 * applicable and display an appropriate menu.
 *
 * @param gesture the click gesture, restricted to button 3 by the UI file
 * @param n_press number of presses
 * @param x horizontal position of the click, in widget coordinates
 * @param y vertical position of the click, in widget coordinates
 * @param user_data NULL
 */
void
anastasis_gtk_review_policy_treeview_pressed_cb (GtkGestureClick *gesture,
                                                 gint n_press,
                                                 gdouble x,
                                                 gdouble y,
                                                 gpointer user_data)
{
  GtkWidget *widget
    = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
  struct MenuContext *ctx;
  AGRow *row;
  bool is_challenge = false;

  (void) n_press;
  (void) user_data;
  ctx = GNUNET_new (struct MenuContext);
  row = AG_row_at (widget,
                   y);
  if (NULL != row)
  {
    ctx->row = g_object_ref (row);
    is_challenge = AG_row_bool (row,
                                "is_challenge");
  }
  install_actions (widget,
                   ctx);
  {
    GtkPopover *popover = get_popover (widget);
    GMenuModel *model = build_menu (NULL != row,
                                    is_challenge);
    const GdkRectangle at = {
      .x = (int) x,
      .y = (int) y,
      .width = 1,
      .height = 1
    };

    gtk_popover_menu_set_menu_model (GTK_POPOVER_MENU (popover),
                                     model);
    g_object_unref (model);
    gtk_popover_set_pointing_to (popover,
                                 &at);
    gtk_popover_popup (popover);
  }
}
