/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <unx/gtk/gtksalmenu.hxx>

#include <unx/gtk/gloactiongroup.h>

#include <sal/log.hxx>

/*
 * GLOAction
 */

#define G_TYPE_LO_ACTION                                (g_lo_action_get_type ())
#define G_LO_ACTION(inst)                               (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
                                                         G_TYPE_LO_ACTION, GLOAction))
namespace {

struct GLOAction
{
    GObject         parent_instance;

    gint            item_id;            // Menu item ID.
    bool            submenu;            // TRUE if action is a submenu action.
    bool            enabled;            // TRUE if action is enabled.
    GVariantType*   parameter_type;     // A GVariantType with the action parameter type.
    GVariantType*   state_type;         // A GVariantType with item state type
    GVariant*       state_hint;         // A GVariant with state hints.
    GVariant*       state;              // A GVariant with current item state
};

}

typedef GObjectClass GLOActionClass;

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#if defined __clang__
#if __has_warning("-Wdeprecated-volatile")
#pragma clang diagnostic ignored "-Wdeprecated-volatile"
#endif
#endif
#endif
G_DEFINE_TYPE (GLOAction, g_lo_action, G_TYPE_OBJECT);
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

static GLOAction*
g_lo_action_new()
{
    return G_LO_ACTION (g_object_new (G_TYPE_LO_ACTION, nullptr));
}

static void
g_lo_action_init (GLOAction *action)
{
    action->item_id = -1;
    action->submenu = false;
    action->enabled = true;
    action->parameter_type = nullptr;
    action->state_type = nullptr;
    action->state_hint = nullptr;
    action->state = nullptr;
}

static void
g_lo_action_finalize (GObject *object)
{
    GLOAction* action = G_LO_ACTION(object);

    if (action->parameter_type)
        g_variant_type_free (action->parameter_type);

    if (action->state_type)
        g_variant_type_free (action->state_type);

    if (action->state_hint)
        g_variant_unref (action->state_hint);

    if (action->state)
        g_variant_unref (action->state);

    G_OBJECT_CLASS (g_lo_action_parent_class)->finalize (object);
}

static void
g_lo_action_class_init (GLOActionClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);

    object_class->finalize = g_lo_action_finalize;
}

/*
 * GLOActionGroup
 */

struct GLOActionGroupPrivate
{
    GHashTable  *table;    /* string -> GLOAction */
};

static void g_lo_action_group_iface_init (GActionGroupInterface *);

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#if defined __clang__
#if __has_warning("-Wdeprecated-volatile")
#pragma clang diagnostic ignored "-Wdeprecated-volatile"
#endif
#endif
#endif
G_DEFINE_TYPE_WITH_CODE (GLOActionGroup,
    g_lo_action_group, G_TYPE_OBJECT,
    G_ADD_PRIVATE(GLOActionGroup)
    G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP,
                           g_lo_action_group_iface_init));
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

static gchar **
g_lo_action_group_list_actions (GActionGroup *group)
{
    GLOActionGroup *loGroup = G_LO_ACTION_GROUP (group);
    GHashTableIter iter;
    gint n, i = 0;
    gchar **keys;
    gpointer key;

    n = g_hash_table_size (loGroup->priv->table);
    keys = g_new (gchar *, n + 1);

    g_hash_table_iter_init (&iter, loGroup->priv->table);
    while (g_hash_table_iter_next (&iter, &key, nullptr))
        keys[i++] = g_strdup (static_cast<gchar*>(key));
    g_assert_cmpint (i, ==, n);
    keys[n] = nullptr;

    return keys;
}

static gboolean
g_lo_action_group_query_action (GActionGroup        *group,
                                const gchar         *action_name,
                                gboolean            *enabled,
                                const GVariantType **parameter_type,
                                const GVariantType **state_type,
                                GVariant           **state_hint,
                                GVariant           **state)
{
    //SAL_INFO("vcl.unity", "g_lo_action_group_query_action on " << group);
    GLOActionGroup *lo_group = G_LO_ACTION_GROUP (group);
    GLOAction* action = G_LO_ACTION (g_hash_table_lookup (lo_group->priv->table, action_name));

    if (action == nullptr)
        return FALSE;

    if (enabled)
    {
        *enabled = action->enabled;
    }

    if (parameter_type)
        *parameter_type = action->parameter_type;

    if (state_type)
        *state_type = action->state_type;

    if (state_hint)
        *state_hint = (action->state_hint) ? g_variant_ref (action->state_hint) : nullptr;

    if (state)
        *state = (action->state) ? g_variant_ref (action->state) : nullptr;

    return true;
}

static void
g_lo_action_group_perform_submenu_action (GLOActionGroup *group,
                                          const gchar    *action_name,
                                          GVariant       *state)
{
    bool bState = g_variant_get_boolean (state);
    SAL_INFO("vcl.unity", "g_lo_action_group_perform_submenu_action on " << group << " to " << bState);

    if (bState)
        GtkSalMenu::Activate(action_name);
    else
        GtkSalMenu::Deactivate(action_name);
}

static void
g_lo_action_group_change_state (GActionGroup *group,
                                const gchar  *action_name,
                                GVariant     *value)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_change_state on " << group );
    g_return_if_fail (value != nullptr);

    g_variant_ref_sink (value);

    if (action_name != nullptr)
    {
        GLOActionGroup* lo_group = G_LO_ACTION_GROUP (group);
        GLOAction* action = G_LO_ACTION (g_hash_table_lookup (lo_group->priv->table, action_name));

        if (action != nullptr)
        {
            if (action->submenu)
                g_lo_action_group_perform_submenu_action (lo_group, action_name, value);
            else
            {
                bool is_new = false;

                /* If action already exists but has no state, it should be removed and added again. */
                if (action->state_type == nullptr)
                {
                    g_action_group_action_removed (G_ACTION_GROUP (group), action_name);
                    action->state_type = g_variant_type_copy (g_variant_get_type(value));
                    is_new = true;
                }

                if (g_variant_is_of_type (value, action->state_type))
                {
                    if (action->state)
                        g_variant_unref(action->state);

                    action->state = g_variant_ref (value);

                    if (is_new)
                        g_action_group_action_added (G_ACTION_GROUP (group), action_name);
                    else
                        g_action_group_action_state_changed (group, action_name, value);
                }
            }
        }
    }

    g_variant_unref (value);
}

static void
g_lo_action_group_activate (GActionGroup *group,
                            const gchar  *action_name,
                            GVariant     *parameter)
{
    if (parameter != nullptr)
        g_action_group_change_action_state(group, action_name, parameter);
    GtkSalMenu::DispatchCommand(action_name);
}

void
g_lo_action_group_insert (GLOActionGroup *group,
                          const gchar    *action_name,
                          gint            item_id,
                          gboolean        submenu)
{
    g_lo_action_group_insert_stateful (group, action_name, item_id, submenu, nullptr, nullptr, nullptr, nullptr);
}

void
g_lo_action_group_insert_stateful (GLOActionGroup     *group,
                                   const gchar        *action_name,
                                   gint                item_id,
                                   gboolean            submenu,
                                   const GVariantType *parameter_type,
                                   const GVariantType *state_type,
                                   GVariant           *state_hint,
                                   GVariant           *state)
{
    g_return_if_fail (G_IS_LO_ACTION_GROUP (group));

    GLOAction* old_action = G_LO_ACTION (g_hash_table_lookup (group->priv->table, action_name));

    if (old_action != nullptr && old_action->item_id == item_id)
        return;

    if (old_action != nullptr)
        g_lo_action_group_remove (group, action_name);

    GLOAction* action = g_lo_action_new();

    g_hash_table_insert (group->priv->table, g_strdup (action_name), action);

    action->item_id = item_id;
    action->submenu = submenu;

    if (parameter_type)
        action->parameter_type = const_cast<GVariantType*>(parameter_type);

    if (state_type)
        action->state_type = const_cast<GVariantType*>(state_type);

    if (state_hint)
        action->state_hint = g_variant_ref_sink (state_hint);

    if (state)
        action->state = g_variant_ref_sink (state);

    g_action_group_action_added (G_ACTION_GROUP (group), action_name);
}

static void
g_lo_action_group_finalize (GObject *object)
{
    GLOActionGroup *lo_group = G_LO_ACTION_GROUP (object);

    g_hash_table_unref (lo_group->priv->table);

    G_OBJECT_CLASS (g_lo_action_group_parent_class)->finalize (object);
}

static void
g_lo_action_group_init (GLOActionGroup *group)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_init on " << group);
    group->priv = static_cast<GLOActionGroupPrivate *>(g_lo_action_group_get_instance_private (group));
    group->priv->table = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                  g_free, g_object_unref);
}

static void
g_lo_action_group_class_init (GLOActionGroupClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    object_class->finalize = g_lo_action_group_finalize;
}

static void
g_lo_action_group_iface_init (GActionGroupInterface *iface)
{
    iface->list_actions = g_lo_action_group_list_actions;
    iface->query_action = g_lo_action_group_query_action;
    iface->change_action_state = g_lo_action_group_change_state;
    iface->activate_action = g_lo_action_group_activate;
}

GLOActionGroup *
g_lo_action_group_new()
{
    GLOActionGroup* group = G_LO_ACTION_GROUP (g_object_new (G_TYPE_LO_ACTION_GROUP, nullptr));
    return group;
}

void
g_lo_action_group_set_action_enabled (GLOActionGroup *group,
                                      const gchar    *action_name,
                                      gboolean        enabled)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_set_action_enabled on " << group);
    g_return_if_fail (G_IS_LO_ACTION_GROUP (group));
    g_return_if_fail (action_name != nullptr);

    GLOAction* action = G_LO_ACTION (g_hash_table_lookup (group->priv->table, action_name));

    if (action == nullptr)
        return;

    action->enabled = enabled;

    g_action_group_action_enabled_changed (G_ACTION_GROUP (group), action_name, enabled);
}

void
g_lo_action_group_remove (GLOActionGroup *group,
                          const gchar    *action_name)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_remove on " << group);
    g_return_if_fail (G_IS_LO_ACTION_GROUP (group));

    if (action_name != nullptr)
    {
        g_action_group_action_removed (G_ACTION_GROUP (group), action_name);
        g_hash_table_remove (group->priv->table, action_name);
    }
}

void
g_lo_action_group_clear (GLOActionGroup  *group)
{
    SAL_INFO("vcl.unity", "g_lo_action_group_clear on " << group);
    g_return_if_fail (G_IS_LO_ACTION_GROUP (group));

    GList* keys = g_hash_table_get_keys (group->priv->table);

    for (GList* element = g_list_first (keys); element != nullptr; element = g_list_next (element))
    {
        g_lo_action_group_remove (group, static_cast<gchar*>(element->data));
    }

    g_list_free (keys);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
