/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*-
 * -*- coding: utf-8 -*-
 *
 * Copyright (C) 2020 KylinSoft Co., Ltd.
 *
 * This program 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
 * any later version.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */
#include <QDebug>
#include "keybindings-manager.h"
#include "config.h"
#include "clib-syslog.h"

#include <gio/gdesktopappinfo.h>
#include <QMessageBox>
#include <QScreen>



#define DESKTOP_APP_DIR "/usr/share/applications/"
//#define GSETTINGS_KEYBINDINGS_DIR "/org/ukui/desktop/session/"
#define GSETTINGS_KEYBINDINGS_DIR "/org/ukui/desktop/keybindings/"
#define CUSTOM_KEYBINDING_SCHEMA  "org.ukui.control-center.keybinding"

KeybindingsManager *KeybindingsManager::mKeybinding = nullptr;

KeybindingsManager::KeybindingsManager()
{

}

KeybindingsManager::~KeybindingsManager()
{

}

KeybindingsManager *KeybindingsManager::KeybindingsManagerNew()
{
    if(nullptr == mKeybinding)
        mKeybinding = new KeybindingsManager();
    return mKeybinding;
}

/**
 * @brief parse_binding
 * Whether the binding exists
 * 获取此绑定的条目
 * @return
 */
static bool
parse_binding (Binding *binding)
{
    gboolean success;

    if(!binding)
        return false;

    binding->key.keysym = 0;
    binding->key.state = 0;
    g_free (binding->key.keycodes);
    binding->key.keycodes = NULL;

    if (binding->binding_str == NULL ||
        binding->binding_str[0] == '\0' ||
        g_strcmp0 (binding->binding_str, "Disabled") == 0 ||
        g_strcmp0 (binding->binding_str, "disabled") == 0 ) {
            return false;
    }
    success = egg_accelerator_parse_virtual (binding->binding_str,
                                             &binding->key.keysym,
                                             &binding->key.keycodes,
                                             (EggVirtualModifierType *)&binding->key.state);
    if (!success)
        USD_LOG(LOG_DEBUG,"Key binding (%s) is invalid", binding->settings_path);

    return success;
}

static gint
compare_bindings (gconstpointer a,
                  gconstpointer b)
{
    Binding *key_a = (Binding *) a;
    char    *key_b = (char *) b;

    return g_strcmp0 (key_b, key_a->settings_path);
}

/**
 * @brief KeybindingsManager::bindings_get_entry
 * Gets binding shortcut data
 * 获取绑定快捷键数据
 * @return
 */
bool KeybindingsManager::bindings_get_entry (KeybindingsManager *manager,const char *settings_path)
{
    GSettings *settings;
    Binding   *new_binding;
    GSList    *tmp_elem;
    char      *action = nullptr;
    char      *key    = nullptr;

    if (!settings_path) {
            return false;
    }

    /* Get entries for this binding
     * 获取此绑定的条目
     */
    settings = g_settings_new_with_path (CUSTOM_KEYBINDING_SCHEMA, settings_path);
    action = g_settings_get_string (settings, "action");
    key = g_settings_get_string (settings, "binding");
    g_object_unref (settings);


    if (action==nullptr || key==nullptr)
    {
            USD_LOG(LOG_DEBUG,"Key binding (%s) is incomplete", settings_path);
            return false;
    }

    tmp_elem = g_slist_find_custom (manager->binding_list,
                                    settings_path,
                                    compare_bindings);

    if (!tmp_elem) {
        new_binding = g_new0 (Binding, 1);
    } else {
        new_binding = (Binding *) tmp_elem->data;
        g_free (new_binding->binding_str);
        g_free (new_binding->action);
        g_free (new_binding->settings_path);

        new_binding->previous_key.keysym = new_binding->key.keysym;
        new_binding->previous_key.state = new_binding->key.state;
        new_binding->previous_key.keycodes = new_binding->key.keycodes;
        new_binding->key.keycodes = NULL;
    }

    new_binding->binding_str = key;
    new_binding->action = action;
    new_binding->settings_path = g_strdup (settings_path);

    if (parse_binding (new_binding)) {
        if (!tmp_elem)
            manager->binding_list = g_slist_prepend (manager->binding_list, new_binding);
    } else {
        g_free (new_binding->binding_str);
        g_free (new_binding->action);
        g_free (new_binding->settings_path);
        g_free (new_binding->previous_key.keycodes);
        g_free (new_binding);
        if (tmp_elem)
            manager->binding_list = g_slist_delete_link (manager->binding_list, tmp_elem);
        return false;
    }

    return true;
}

/**
 * @brief KeybindingsManager::bindings_clear
 * Clear binding cache
 * 清除绑定缓存
 */
void KeybindingsManager::bindings_clear (KeybindingsManager *manager)
{
    GSList *l;

    if (manager->binding_list != NULL)
    {
        for (l = manager->binding_list; l; l = l->next) {
            Binding *b = (Binding *)l->data;
            g_free (b->binding_str);
            g_free (b->action);
            g_free (b->settings_path);
            g_free (b->previous_key.keycodes);
            g_free (b->key.keycodes);
            g_free (b);
        }
        g_slist_free (manager->binding_list);
        manager->binding_list = NULL;
    }
}

void KeybindingsManager::bindings_get_entries (KeybindingsManager *manager)
{
    gchar **custom_list = NULL;
    gint i;

    bindings_clear(manager);
    custom_list = dconf_util_list_subdirs (GSETTINGS_KEYBINDINGS_DIR, FALSE);

    if (custom_list != NULL)
    {
        for (i = 0; custom_list[i] != NULL; i++)
        {
            gchar *settings_path;
            settings_path = g_strdup_printf("%s%s", GSETTINGS_KEYBINDINGS_DIR, custom_list[i]);
            bindings_get_entry (manager,settings_path);
            g_free (settings_path);
        }
        g_strfreev (custom_list);
    }
}

/**
 * @brief same_keycode
 * Is it the same key code
 * 是否为相同的键码
 * @return
 */
static bool
same_keycode (const Key *key, const Key *other)
{
    if (key->keycodes != NULL && other->keycodes != NULL) {
        unsigned int *c;

        for (c = key->keycodes; *c; ++c) {
            if (key_uses_keycode (other, *c))
                return true;
        }
    }
    return false;
}

/**
 * @brief same_key
 * Compare whether the keys are shortcuts
 * 对比按键是否为快捷键
 * @return
 */
static bool same_key (const Key *key, const Key *other)
{
    if (key->state == other->state) {
        if (key->keycodes != NULL && other->keycodes != NULL) {
            unsigned int *c1, *c2;

            for (c1 = key->keycodes, c2 = other->keycodes;
                 *c1 || *c2; ++c1, ++c2) {
                         if (*c1 != *c2)
                            return false;
            }
        } else if (key->keycodes != NULL || other->keycodes != NULL)
            return false;

        return true;
    }
    return false;
}

/**
 * @brief KeybindingsManager::key_already_used
 * @English Compare the shortcuts already used
 * @简体 已经使用的快捷键 进行比对
 * @return
 */
bool KeybindingsManager::key_already_used (KeybindingsManager*manager,Binding   *binding)
{
    GSList *li;

    for (li = manager->binding_list; li != NULL; li = li->next) {
        Binding *tmp_binding =  (Binding*) li->data;

        if (tmp_binding != binding &&
            same_keycode (&tmp_binding->key, &binding->key) &&
            tmp_binding->key.state == binding->key.state) {
                return true;
        }
    }
    return false;
}

/**
 * @brief KeybindingsManager::binding_unregister_keys
 * Unbind key
 * 注销绑定按键
 */
void KeybindingsManager::binding_unregister_keys (KeybindingsManager *manager)
{
    GSList *li;
    bool need_flush = FALSE;
    USD_LOG(LOG_DEBUG,"run here...");
    gdk_x11_display_error_trap_push (gdk_display_get_default());
    try {
        for (li = manager->binding_list; li != NULL; li = li->next) {
            Binding *binding = (Binding *) li->data;
            USD_LOG(LOG_DEBUG,"run here...");
            if (binding->key.keycodes) {
                need_flush = TRUE;

                grab_key_unsafe (&binding->key, FALSE, manager->screens);
            }
        }
        if (need_flush)
            gdk_display_flush(gdk_display_get_default());
    } catch (...) {

    }
    gdk_x11_display_error_trap_pop_ignored(gdk_display_get_default());
}

/**
 * @brief KeybindingsManager::binding_register_keys
 * Bind register key
 * 绑定寄存器按键
 */
void KeybindingsManager::binding_register_keys (KeybindingsManager *manager)
{
    GSList *li;
    bool need_flush = false;
    gdk_x11_display_error_trap_push (gdk_display_get_default());
    /* Now check for changes and grab new key if not already used
     * 现在检查更改并获取新密钥（如果尚未使用）
     */
    for (li = manager->binding_list; li != NULL; li = li->next) {
        Binding *binding = (Binding *) li->data;
        if (!same_key (&binding->previous_key, &binding->key)) {

            /* Ungrab key if it changed and not clashing with previously set binding */
            if (!key_already_used (manager,binding)) {
                gint i;
                need_flush = true;
                if (binding->previous_key.keycodes) {
                        grab_key_unsafe (&binding->previous_key, FALSE, manager->screens);
                }
                grab_key_unsafe (&binding->key, TRUE, manager->screens);
                binding->previous_key.keysym = binding->key.keysym;
                binding->previous_key.state = binding->key.state;
                g_free (binding->previous_key.keycodes);

                for (i = 0; binding->key.keycodes&&binding->key.keycodes[i]; ++i);
                binding->previous_key.keycodes = g_new0 (guint, i);

                for (i = 0; binding->key.keycodes&&binding->key.keycodes[i]; ++i)
                binding->previous_key.keycodes[i] = binding->key.keycodes[i];

            } else
                USD_LOG(LOG_DEBUG,"Key binding (%s) is already in use", binding->binding_str);
        }
    }

    if (need_flush)
        gdk_display_flush (gdk_display_get_default());
    if(gdk_x11_display_error_trap_pop (gdk_display_get_default()))
        USD_LOG(LOG_DEBUG,"Grab failed for some keys, another application may already have access the them.");
}

/**
 * @brief keybindings_filter   键盘事件回调函数
 * @param gdk_xevent  GDK XEvent事件
 * @param event   GDK Event事件
 * @param manager 类
 * @return 未处理事件，请继续处理
 */
GdkFilterReturn
keybindings_filter (GdkXEvent           *gdk_xevent,
                    GdkEvent            *event,
                    KeybindingsManager  *manager)
{
    XEvent *xevent = (XEvent *) gdk_xevent;
    GSList *li;
//    USD_LOG(LOG_DEBUG,"had event ：%d",xevent->type);
    if (xevent->type != KeyPress) {
        return GDK_FILTER_CONTINUE;
    }

    for (li = manager->binding_list; li != NULL; li = li->next) {
        Binding *binding = (Binding *) li->data;

        if (match_key (&binding->key, xevent)) {
            GError  *error = NULL;
            gboolean retval;
            gchar  **argv = NULL;

            if (binding->action == NULL)
                return GDK_FILTER_CONTINUE;

            if (!g_shell_parse_argv (binding->action,
                                     NULL, &argv,
                                     &error)) {
                    return GDK_FILTER_CONTINUE;
            }

            GDesktopAppInfo *info = g_desktop_app_info_new_from_filename(binding->action);
            retval = g_app_info_launch_uris((GAppInfo *)info, NULL, NULL, NULL);

            g_strfreev (argv);

            /* Run failed popup
             * 运行失败弹窗 */
            if (!retval) {
                QString strs = QObject::tr("Error while trying to run \"%1\";\n which is linked to the key \"%2\"").
                                        arg(binding->action).arg(binding->binding_str);
                QMessageBox *msgbox = new QMessageBox();
                msgbox->setWindowTitle(QObject::tr("Shortcut message box"));
                msgbox->setText(strs);
                msgbox->setStandardButtons(QMessageBox::Yes);
                msgbox->setButtonText(QMessageBox::Yes,QObject::tr("Yes"));
                msgbox->exec();
                delete msgbox;
                msgbox = nullptr;
            }
            return GDK_FILTER_REMOVE;
        }
    }
    return GDK_FILTER_CONTINUE;
}
static void
show_path (DConfClient *client, const gchar *path)
{
  if (dconf_is_key (path, NULL))
    {
      g_autoptr(GVariant) value = NULL;
      g_autofree gchar *value_str = NULL;

      value = dconf_client_read (client, path);

      if (value != NULL)
        value_str = g_variant_print (value, TRUE);

      USD_LOG(LOG_DEBUG,"  %s\n", value_str != NULL ? value_str : "unset");
    }
}
/**
 * @brief KeybindingsManager::bindings_callback  dconf监听快捷键改变回调函数
 * @param client
 * @param prefix
 * @param changes
 * @param tag
 * @param manager 类
 */
void KeybindingsManager::bindings_callback (DConfClient  *client,
                                            gchar        *prefix,
                                            const gchar **changes,
                                            gchar        *tag,
                                            KeybindingsManager *manager)
{
    Q_UNUSED(client);
    Q_UNUSED(changes);

    if (strncmp(GSETTINGS_KEYBINDINGS_DIR,prefix,strlen(GSETTINGS_KEYBINDINGS_DIR))) {
        return;
    }

    USD_LOG(LOG_DEBUG,"keybindings: received 'changed' signal from dconf. gchar:%s changes:%s tag:%s ",prefix, changes[0], tag);


    for (const gchar **item = changes; *changes; ++changes)
      {
        g_autofree gchar *full = NULL;

        full = g_strconcat (prefix, *item, NULL);
        USD_LOG (LOG_DEBUG,"prefix%s full%s\n", prefix, full);
        show_path (client, full);
      }

    binding_unregister_keys (manager);
    bindings_get_entries (manager);
    binding_register_keys (manager);
}


/**
 * @brief get_screens_list  获取gdkscreen屏幕并存放链表
 * @return  返回列表
 */
void KeybindingsManager::get_screens_list (void)
{
    GdkScreen  *screen = gdk_screen_get_default ();
    screens->append(screen);
}

bool KeybindingsManager::start()
{
    USD_LOG(LOG_DEBUG,"-- Keybindings Manager Start --");
    QList<GdkScreen*>::iterator l, begin, end;
    GdkDisplay  *dpy;
    GdkScreen   *screen;
    GdkWindow   *window;
    Display     *xdpy;
    Window       xwindow;
    XWindowAttributes atts;

    gdk_init(NULL,NULL);
    dpy = gdk_display_get_default ();

//    xdpy = GDK_DISPLAY_XDISPLAY (dpy);
    xdpy = QX11Info::display();

    screen = gdk_display_get_default_screen(dpy);
    window  = gdk_screen_get_root_window (screen);
    xwindow = GDK_WINDOW_XID (window);

    /* Monitor keyboard events
     * 监听键盘事件
     */
    gdk_window_add_filter (window,
                           (GdkFilterFunc) keybindings_filter,
                           this);


    try {
        /* Add KeyPressMask to the currently reportable event masks
         * 将KeyPressMask添加到当前可报告的事件掩码
         */
        gdk_x11_display_error_trap_push (gdk_display_get_default());
        XGetWindowAttributes (xdpy, xwindow, &atts);
        XSelectInput (xdpy, xwindow, atts.your_event_mask | KeyPressMask);
        gdk_x11_display_error_trap_pop_ignored(gdk_display_get_default());
    } catch (int) {
        USD_LOG(LOG_DEBUG,"had a error in here...");
    }
    screens = new QList<GdkScreen*>();
    get_screens_list ();

    binding_list = NULL;
    bindings_get_entries (this);
    binding_register_keys(this);

    /* Link to dconf, receive a shortcut key change signal from dconf
     * 链接dconf, 从dconf收到更改快捷键信号
     */
    {
        char ** childs;
        int len;
        QList<char *> vals;

        client = dconf_client_new ();
        dconf_client_watch_fast (client, GSETTINGS_KEYBINDINGS_DIR);

        dconf_client_watch_sync (client, GSETTINGS_KEYBINDINGS_DIR);
        g_signal_connect (client, "changed", G_CALLBACK (bindings_callback), this);
#if 0 //无效，无法使用gsetings的方法监控dconf
        childs = dconf_client_list (client, GSETTINGS_KEYBINDINGS_DIR, &len);

        for (int i = 0; childs[i] != NULL; i++){
            USD_LOG(LOG_DEBUG,"val:%s",childs[i]);
            if (dconf_is_rel_dir (childs[i], NULL))
            {
                USD_LOG(LOG_DEBUG,"val:");
                char * val = g_strdup (childs[i]);
                vals.append(val);
                USD_LOG(LOG_DEBUG,"val:%s",val);
            }
        }

        USD_LOG(LOG_DEBUG,"len:%d",len);
        for (char * path : vals){
            USD_LOG(LOG_DEBUG,"val:%s",path);
            char tempbuf[128]="";

            memcpy(tempbuf, GSETTINGS_KEYBINDINGS_DIR, strlen(GSETTINGS_KEYBINDINGS_DIR));
            strcat(tempbuf, path);
            USD_LOG(LOG_DEBUG,"path:%s, tempbuf%s",path, tempbuf);
            const QByteArray ba(GSETTINGS_KEYBINDINGS_DIR,strlen(GSETTINGS_KEYBINDINGS_DIR));
            const QByteArray bba(tempbuf,strlen(tempbuf));

            USD_LOG(LOG_DEBUG,"ba[%s] bba[%s]",ba.data(),bba.data());

            QGSettings * settings = new QGSettings(ba, bba);
            connect(settings, &QGSettings::changed, this, [=] (const QString &key){
                USD_LOG(LOG_DEBUG,"key=%s",key.toLatin1().data());
            });
        }
#endif
    }
    return true;
}

void KeybindingsManager::stop()
{
    USD_LOG(LOG_DEBUG,"Stopping keybindings manager");

    if (client != NULL) {
            g_object_unref (client);
            client = NULL;
    }
    QList<GdkScreen*>::iterator l,end;

    l = screens->begin();
    GdkScreen *screen = *l;
    gdk_window_remove_filter (gdk_screen_get_root_window (screen),
                              (GdkFilterFunc) keybindings_filter,
                              this);


    binding_unregister_keys (this);
    bindings_clear (this);

    screens->clear();
    delete screens;
    screens = NULL;
}
