/***************************************************************************
 *   Copyright (C) 2010~2012 by CSSlayer                                   *
 *                                                                         *
 *   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 2 of the License, or     *
 *   (at your option) 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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.              *
 ***************************************************************************/

#include "fcitxkbd.h"
#include "fcitx/fcitx.h"
#include "module/dbus/dbusstuff.h"

/**
 * FcitxKbd:
 *
 * A #FcitxKbd allow you to control fcitx via DBus.
 */

static const gchar introspection_xml[] =
    "<node>"
    "    <interface name=\"org.fcitx.Fcitx.Keyboard\">"
    "        <method name=\"SetDefaultLayout\">\n"
    "           <arg name=\"layout\" direction=\"out\" type=\"s\"/>"
    "           <arg name=\"variant\" direction=\"out\" type=\"s\"/>"
    "        </method>\n"
    "        <method name=\"GetLayouts\">"
    "            <arg name=\"layouts\" direction=\"out\" type=\"a(ssss)\"/>"
    "        </method>"
    "        <method name=\"GetLayoutForIM\">"
    "            <arg name=\"im\" direction=\"in\" type=\"s\"/>"
    "            <arg name=\"layout\" direction=\"out\" type=\"s\"/>"
    "            <arg name=\"variant\" direction=\"out\" type=\"s\"/>"
    "        </method>"
    "        <method name=\"SetLayoutForIM\">"
    "            <arg name=\"im\" direction=\"in\" type=\"s\"/>"
    "            <arg name=\"layout\" direction=\"in\" type=\"s\"/>"
    "            <arg name=\"variant\" direction=\"in\" type=\"s\"/>"
    "        </method>"
    "    </interface>"
    "</node>";

FCITX_EXPORT_API
GType fcitx_kbd_get_type(void) G_GNUC_CONST;
FCITX_EXPORT_API
GType fcitx_layout_item_get_type() G_GNUC_CONST;

G_DEFINE_TYPE(FcitxKbd, fcitx_kbd, G_TYPE_DBUS_PROXY);

static GDBusInterfaceInfo *_fcitx_kbd_get_interface_info(void);
static void fcitx_layout_item_free(gpointer data);

static GDBusInterfaceInfo *_fcitx_kbd_get_interface_info(void) {
    static gsize has_info = 0;
    static GDBusInterfaceInfo *info = NULL;
    if (g_once_init_enter(&has_info)) {
        GDBusNodeInfo *introspection_data;
        introspection_data =
            g_dbus_node_info_new_for_xml(introspection_xml, NULL);
        info = introspection_data->interfaces[0];
        g_once_init_leave(&has_info, 1);
    }
    return info;
}

static void fcitx_kbd_finalize(GObject *object) {
    G_GNUC_UNUSED FcitxKbd *im = FCITX_KBD(object);

    if (G_OBJECT_CLASS(fcitx_kbd_parent_class)->finalize != NULL)
        G_OBJECT_CLASS(fcitx_kbd_parent_class)->finalize(object);
}

static void fcitx_kbd_init(FcitxKbd *im) {
    /* Sets the expected interface */
    g_dbus_proxy_set_interface_info(G_DBUS_PROXY(im),
                                    _fcitx_kbd_get_interface_info());
}

static void fcitx_kbd_class_init(FcitxKbdClass *klass) {
    GObjectClass *gobject_class;

    gobject_class = G_OBJECT_CLASS(klass);
    gobject_class->finalize = fcitx_kbd_finalize;
}

/**
 * fcitx_kbd_new:
 * @bus_type: #GBusType
 * @flags:  #GDBusProxyFlags
 * @display_number: display_number
 * @cancellable: A #GCancellable or %NULL
 * @error: Error or %NULL
 *
 * New a #fcitxKbd.
 *
 * Returns: A newly allocated #FcitxKbd.
 */
FCITX_EXPORT_API
FcitxKbd *fcitx_kbd_new(GBusType bus_type, GDBusProxyFlags flags,
                        gint display_number, GCancellable *cancellable,
                        GError **error) {
    gchar servicename[64];
    sprintf(servicename, "%s-%d", FCITX_DBUS_SERVICE, display_number);

    char *name = servicename;
    FcitxKbd *im = g_initable_new(
        FCITX_TYPE_KBD, cancellable, error, "g-flags", flags, "g-name", name,
        "g-bus-type", bus_type, "g-object-path", "/keyboard",
        "g-interface-name", "org.fcitx.Fcitx.Keyboard", NULL);

    return FCITX_KBD(im);
}

/**
 * fcitx_kbd_get_layouts_nofree: (rename-to fcitx_kbd_get_layouts)
 * @kbd: A #FcitxKbd
 *
 * Get Fcitx all im list
 *
 * Returns: (transfer full) (element-type FcitxLayoutItem): A #FcitxLayoutItem
 *List
 * Rename to: fcitx_kbd_get_layouts
 **/
FCITX_EXPORT_API
GPtrArray *fcitx_kbd_get_layouts_nofree(FcitxKbd *kbd) {
    GError *error = NULL;
    GVariant *variant = g_dbus_proxy_call_sync(
        G_DBUS_PROXY(kbd), "GetLayouts", NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START,
        -1, NULL, &error);

    GPtrArray *array = NULL;

    if (error) {
        g_warning("%s", error->message);
        g_error_free(error);
    } else if (variant) {
        array = g_ptr_array_new();
        GVariantIter *iter;
        gchar *layout, *kbdvariant, *name, *langcode;
        g_variant_get(variant, "(a(ssss))", &iter);
        while (g_variant_iter_next(iter, "(ssss)", &layout, &kbdvariant, &name,
                                   &langcode, NULL)) {
            FcitxLayoutItem *item = g_malloc0(sizeof(FcitxLayoutItem));
            item->layout = layout;
            item->variant = kbdvariant;
            item->name = name;
            item->langcode = langcode;
            g_ptr_array_add(array, item);
        }
        g_variant_iter_free(iter);
    }

    return array;
}

/**
 * fcitx_kbd_get_layouts: (skip)
 **/
FCITX_EXPORT_API
GPtrArray *fcitx_kbd_get_layouts(FcitxKbd *kbd) {
    GPtrArray *array = fcitx_kbd_get_layouts_nofree(kbd);
    if (array)
        g_ptr_array_set_free_func(array, fcitx_layout_item_free);
    return array;
}

/**
 * fcitx_kbd_get_layout_for_im:
 * @kbd: A #FcitxKbd
 * @imname: input method name
 * @layout: (out) (allow-none): return'd layout
 * @variant: (out) (allow-none): return'd variant
 *
 * Get a layout binding with input method
 **/
FCITX_EXPORT_API
void fcitx_kbd_get_layout_for_im(FcitxKbd *kbd, const gchar *imname,
                                 gchar **layout, gchar **variant) {
    GError *error = NULL;
    GVariant *v = g_dbus_proxy_call_sync(
        G_DBUS_PROXY(kbd), "GetLayoutForIM", g_variant_new("(s)", imname),
        G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, &error);

    if (error) {
        g_warning("%s", error->message);
        g_error_free(error);
        *layout = NULL;
        *variant = NULL;
    } else if (v) {
        g_variant_get(v, "(ss)", layout, variant);
        g_variant_unref(v);
    }
}

/**
 * fcitx_kbd_set_layout_for_im:
 * @kbd: A #FcitxKbd
 * @imname: input method name
 * @layout: layout
 * @variant: variant
 *
 * Set a layout binding with input method
 **/
FCITX_EXPORT_API
void fcitx_kbd_set_layout_for_im(FcitxKbd *kbd, const gchar *imname,
                                 const gchar *layout, const gchar *variant) {
    g_dbus_proxy_call(G_DBUS_PROXY(kbd), "SetLayoutForIM",
                      g_variant_new("(sss)", imname, layout, variant),
                      G_DBUS_CALL_FLAGS_NO_AUTO_START, 0, NULL, NULL, NULL);
}

/**
 * fcitx_kbd_set_default_layout:
 * @kbd: A #FcitxKbd
 * @layout: layout
 * @variant: variant
 *
 * Set a layout binding with the state when there is no input method
 **/
FCITX_EXPORT_API
void fcitx_kbd_set_default_layout(FcitxKbd *kbd, const gchar *layout,
                                  const gchar *variant) {
    g_dbus_proxy_call(G_DBUS_PROXY(kbd), "SetDefaultLayout",
                      g_variant_new("(ss)", layout, variant),
                      G_DBUS_CALL_FLAGS_NO_AUTO_START, 0, NULL, NULL, NULL);
}

static void fcitx_layout_item_free(gpointer data) {
    FcitxLayoutItem *item = data;
    g_free(item->langcode);
    g_free(item->name);
    g_free(item->variant);
    g_free(item->layout);
    g_free(data);
}

static FcitxLayoutItem *fcitx_layout_item_copy(FcitxLayoutItem *item) {
    FcitxLayoutItem *new_item = g_malloc0(sizeof(FcitxLayoutItem));
    new_item->layout = strdup(item->layout);
    new_item->variant = strdup(item->variant);
    new_item->name = strdup(item->name);
    new_item->langcode = strdup(item->langcode);
    return new_item;
}

G_DEFINE_BOXED_TYPE(FcitxLayoutItem, fcitx_layout_item, fcitx_layout_item_copy,
                    fcitx_layout_item_free)
