/*
    SPDX-FileCopyrightText: 2009 Wang Hoi <zealot.hoi@gmail.com>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

/** @file scim_panel_dbus.cpp
 */

#define KDE_signal signal

#undef QT_NO_STL
#define QT_STL

#include <config-scim.h>
#include <errno.h>
#include <list>
#include <signal.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <QCoreApplication>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QMutex>
#include <QObject>
#include <QProcess>
#include <QString>
#include <QStringList>
#include <QThread>

#define Uses_C_STDIO
#define Uses_C_STDLIB
#define Uses_SCIM_LOOKUP_TABLE
#define Uses_SCIM_SOCKET
#define Uses_SCIM_TRANSACTION
#define Uses_SCIM_TRANS_COMMANDS
#define Uses_SCIM_CONFIG
#define Uses_SCIM_CONFIG_MODULE
// #define Uses_SCIM_DEBUG
#define Uses_SCIM_HELPER
#define Uses_SCIM_HELPER_MODULE
#define Uses_SCIM_PANEL_AGENT
#define Uses_STL_MAP

#define ENABLE_DEBUG 9
// scim use exceptions and template, so it doesn't work in hidden visibility
#pragma GCC visibility push(default)
#include <scim.h>
#pragma GCC visibility pop

Q_DECLARE_METATYPE(scim::Property)
Q_DECLARE_METATYPE(scim::PanelFactoryInfo)
Q_DECLARE_METATYPE(scim::HelperInfo)

using namespace Qt::StringLiterals;
using namespace scim;

// PanelAgent related functions
static bool initialize_panel_agent(const String &config, const String &display, bool resident);
static bool run_panel_agent(void);
static void start_auto_start_helpers(void);

static void slot_transaction_start(void);
static void slot_transaction_end(void);
static void slot_reload_config(void);
static void slot_turn_on(void);
static void slot_turn_off(void);
static void slot_update_screen(int screen);
static void slot_update_spot_location(int x, int y);
static void slot_update_factory_info(const PanelFactoryInfo &info);
static void slot_show_help(const String &help);
static void slot_show_factory_menu(const std::vector<PanelFactoryInfo> &menu);

static void slot_show_preedit_string(void);
static void slot_show_aux_string(void);
static void slot_show_lookup_table(void);
static void slot_hide_preedit_string(void);
static void slot_hide_aux_string(void);
static void slot_hide_lookup_table(void);
static void slot_update_preedit_string(const String &str, const AttributeList &attrs);
static void slot_update_preedit_caret(int caret);
static void slot_update_aux_string(const String &str, const AttributeList &attrs);
static void slot_update_lookup_table(const LookupTable &table);
static void slot_register_properties(const PropertyList &props);
static void slot_update_property(const Property &prop);

static void slot_register_helper_properties(int id, const PropertyList &props);
static void slot_update_helper_property(int id, const Property &prop);
static void slot_register_helper(int id, const HelperInfo &helper);
static void slot_remove_helper(int id);
static void slot_lock(void);
static void slot_unlock(void);

/////////////////////////////////////////////////////////////////////////////
// Declaration of internal variables.
/////////////////////////////////////////////////////////////////////////////
// static bool               _ui_initialized              = false;

static ConfigModule *_config_module = nullptr;
static ConfigPointer _config;

static std::vector<HelperInfo> _helper_list;

static bool _should_exit = false;

// static bool               _panel_is_on                 = false;

static PanelAgent *_panel_agent = nullptr;

static QList<PanelFactoryInfo> _factory_list;

class PanelAgentThread;
PanelAgentThread *_panel_agent_thread;

class DBusHandler;
DBusHandler *_dbus_handler;

static QMutex _panel_agent_lock;
static QMutex _global_resource_lock;
static QMutex _transaction_lock;

/////////////////////////////////////////////////////////////////////////////
// Implementation of internal functions.
/////////////////////////////////////////////////////////////////////////////

static void ui_config_reload_callback(const ConfigPointer &config)
{
    _config = config;
}

static QString AttrList2String(const AttributeList &attr_list)
{
    QString result;
    for (const Attribute &attr : attr_list) {
        int type = (int)attr.get_type();
        unsigned int start = attr.get_start();
        unsigned int length = attr.get_length();
        unsigned int value = attr.get_value();
        result += QString::fromLatin1("%1:%2:%3:%4;").arg(type).arg(start).arg(length).arg(value);
    }
    return result;
}

static QVariantList LookupTable2VariantList(const LookupTable &lookup_table)
{
    QVariantList result;
    QStringList labels;
    QStringList candidates;
    QStringList attrlist_list;
    int current_page_size = lookup_table.get_current_page_size();

    for (int i = 0; i < current_page_size; ++i) {
        labels << QString::fromStdWString(lookup_table.get_candidate_label(i));
    }
    for (int i = 0; i < current_page_size; ++i) {
        candidates << QString::fromStdWString(lookup_table.get_candidate_in_current_page(i));
        attrlist_list << AttrList2String(lookup_table.get_attributes_in_current_page(i));
    }

    result << labels << candidates << attrlist_list << lookup_table.get_current_page_start()
           << (lookup_table.get_current_page_start() + lookup_table.get_current_page_size() < ((int)lookup_table.number_of_candidates()));

    // result << labels << candidates << attrlist_list;
    return result;
}

static QString Property2String(const Property &prop)
{
    QString result;
    int state = 0;
    if (prop.active())
        state |= SCIM_PROPERTY_ACTIVE;
    if (prop.visible())
        state |= SCIM_PROPERTY_VISIBLE;
    result = QString::fromLatin1("%1:%2:%3:%4:%5")
                 .arg(QString::fromUtf8(prop.get_key().c_str()))
                 .arg(QString::fromUtf8(prop.get_label().c_str()))
                 .arg(QString::fromUtf8(prop.get_icon().c_str()))
                 .arg(QString::fromUtf8(prop.get_tip().c_str()))
                 .arg(state);
    return result;
}

#if 0
static QString
PanelFactoryInfo2String(const PanelFactoryInfo &info)
{
    QString result = QString("%1:%2:%3:%4")
                     .arg(QString::fromUtf8(info.uuid.c_str()))
                     .arg(QString::fromUtf8(info.name.c_str()))
                     .arg(QString::fromUtf8(info.icon.c_str()))
                     .arg(QString::fromUtf8(info.lang.c_str()));
    return result;
}
#endif

static QStringList PropertyList2LeafOnlyStringList(const QList<Property> &props)
{
    QStringList list;
    list.clear();
    for (int i = 0; i < props.size(); ++i) {
        bool ok = true;
        for (int j = 0; j < props.size(); ++j) {
            if (props.at(i).is_a_leaf_of(props.at(j))) {
                SCIM_DEBUG_MAIN(1) << "Leaf" << props.at(i).get_key() << " " << props.at(j).get_key() << "\n";
                ok = false;
            }
        }
        if (ok)
            list << Property2String(props.at(i));
    }
    return list;
}

static int dbus_event_type = QEvent::registerEventType(1988);
class DBusEvent : public QEvent
{
public:
    enum SCIM_EVENT_TYPE {
        TURN_ON,
        TURN_OFF,
        UP_SCREEN,
        UP_SPOT_LOC,
        UP_AUX,
        UP_PREEDIT_STR,
        UP_PREEDIT_CARET,
        UP_LOOKUPTABLE,
        UP_LOOKUPTABLE_CURSOR,
        UP_FACTORY_INFO,
        UP_PROPERTY,
        UP_HELPER_PROPERTY,
        REG_PROPERTIES,
        REG_HELPER_PROPERTIES,
        REG_HELPER,
        RM_HELPER,
        RM_PROPERTY,
        SHOW_LOOKUPTABLE,
        HIDE_LOOKUPTABLE,
        SHOW_PREEDIT,
        HIDE_PREEDIT,
        SHOW_AUX,
        HIDE_AUX,
        SHOW_HELP,
        SHOW_FACTORY_MENU,
    };
    DBusEvent(SCIM_EVENT_TYPE t, const QVariantList &arglist = QVariantList())
        : QEvent((QEvent::Type)dbus_event_type)
        , m_evtype(t)
        , m_data(arglist)
    {
    }
    SCIM_EVENT_TYPE scim_event_type() const
    {
        return m_evtype;
    }
    const QVariantList &data() const
    {
        return m_data;
    }

private:
    SCIM_EVENT_TYPE m_evtype;
    QVariantList m_data;
};
class DBusHandler : public QObject
{
    Q_OBJECT
public:
    explicit DBusHandler(QObject *parent = nullptr)
        : QObject(parent)
    {
        QDBusConnection("scim_panel"_L1).connect(""_L1, ""_L1, "org.kde.impanel"_L1, "MovePreeditCaret"_L1, this, SLOT(MovePreeditCaret(int)));
        QDBusConnection("scim_panel"_L1).connect(""_L1, ""_L1, "org.kde.impanel"_L1, "SelectCandidate"_L1, this, SLOT(SelectCandidate(int)));
        QDBusConnection("scim_panel"_L1).connect(""_L1, ""_L1, "org.kde.impanel"_L1, "LookupTablePageUp"_L1, this, SLOT(LookupTablePageUp()));
        QDBusConnection("scim_panel"_L1).connect(""_L1, ""_L1, "org.kde.impanel"_L1, "LookupTablePageDown"_L1, this, SLOT(LookupTablePageDown()));
        QDBusConnection("scim_panel"_L1).connect(""_L1, ""_L1, "org.kde.impanel"_L1, "TriggerProperty"_L1, this, SLOT(TriggerProperty(QString)));
        QDBusConnection("scim_panel"_L1).connect(""_L1, ""_L1, "org.kde.impanel"_L1, "PanelCreated"_L1, this, SLOT(PanelCreated()));
        QDBusConnection("scim_panel"_L1).connect(""_L1, ""_L1, "org.kde.impanel"_L1, "Exit"_L1, this, SLOT(Exit()));
        QDBusConnection("scim_panel"_L1).connect(""_L1, ""_L1, "org.kde.impanel"_L1, "ReloadConfig"_L1, this, SLOT(ReloadConfig()));
        QDBusConnection("scim_panel"_L1).connect(""_L1, ""_L1, "org.kde.impanel"_L1, "Configure"_L1, this, SLOT(Configure()));

        logo_prop = Property("/Logo", "SCIM", String(SCIM_ICON_DIR) + "/trademark.png", "SCIM Input Method");
        show_help_prop = Property("/StartHelp", "Help", "help-about", "About SCIM…");
        factory_prop_prefix = QString::fromUtf8("/Factory/");
        helper_prop_prefix = QString::fromUtf8("/Helper/");
    }
    ~DBusHandler()
    {
    }

    void setInitialHelpers(const std::vector<HelperInfo> &_helper_list)
    {
        QList<Property> props;
        for (const HelperInfo &info : _helper_list) {
            if (((info.option & SCIM_HELPER_STAND_ALONE) != 0) && ((info.option & SCIM_HELPER_AUTO_START) == 0)) {
                props << Property(String(helper_prop_prefix.toUtf8().constData()) + info.uuid, info.name, info.icon, info.description);
            }
        }
        if (!props.isEmpty()) {
            helper_props_map.insert(0, props);
        }
    }
public Q_SLOTS:
    void MovePreeditCaret(int pos)
    {
        SCIM_DEBUG_MAIN(1) << Q_FUNC_INFO << pos << "\n";
        _panel_agent->move_preedit_caret(pos);
    }
    void SelectCandidate(int idx)
    {
        SCIM_DEBUG_MAIN(1) << Q_FUNC_INFO << idx << "\n";
        _panel_agent->select_candidate(idx);
    }
    void LookupTablePageUp()
    {
        SCIM_DEBUG_MAIN(1) << Q_FUNC_INFO << "\n";
        _panel_agent->lookup_table_page_up();
    }
    void LookupTablePageDown()
    {
        SCIM_DEBUG_MAIN(1) << Q_FUNC_INFO << "\n";
        _panel_agent->lookup_table_page_down();
    }
    void TriggerProperty(const QString &key)
    {
        SCIM_DEBUG_MAIN(1) << qPrintable(key) << "\n";
        if (key == QString::fromUtf8(show_help_prop.get_key().c_str())) {
            SCIM_DEBUG_MAIN(1) << "about_to_show_help"
                               << "\n";
            _panel_agent->request_help();
            return;
        }
        if (key == QString::fromUtf8(logo_prop.get_key().c_str())) {
            SCIM_DEBUG_MAIN(1) << "about_to_show_factory_menu"
                               << "\n";
            _panel_agent->request_factory_menu();
            return;
        }
        if (key.startsWith(factory_prop_prefix)) {
            QString factory_uuid = key;
            factory_uuid.remove(0, factory_prop_prefix.size());
            SCIM_DEBUG_MAIN(1) << "about_to_change_factory" << qPrintable(factory_uuid) << "\n";
            _panel_agent->change_factory(factory_uuid.toUtf8().constData());
            return;
        }
        if (key.startsWith(helper_prop_prefix)) {
            QString helper_uuid = key;
            helper_uuid.remove(0, helper_prop_prefix.size());
            SCIM_DEBUG_MAIN(1) << "about_to_start_helper" << qPrintable(helper_uuid) << "\n";
            _panel_agent->start_helper(helper_uuid.toUtf8().constData());
            return;
        }
        int i = 0;
        for (i = 0; i < panel_props.size(); ++i) {
            if (panel_props.at(i).get_key() == String(key.toUtf8().constData())) {
                break;
            }
        }
        // found one
        QStringList list_result;
        list_result.clear();
        if (i < panel_props.size()) {
            for (int j = 0; j < panel_props.size(); ++j) {
                if (panel_props.at(j).is_a_leaf_of(panel_props.at(i))) {
                    list_result << Property2String(panel_props.at(j));
                }
            }
            if (list_result.isEmpty()) {
                _panel_agent->trigger_property(key.toUtf8().constData());
            } else {
                SCIM_DEBUG_MAIN(1) << "ExecMenu" << qPrintable(key) << "\n";
                QDBusMessage message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "ExecMenu"_L1);
                message << list_result;
                QDBusConnection("scim_panel"_L1).send(message);
            }
        }
    }
    void PanelCreated()
    {
        QDBusMessage message;
        QStringList list_result;

        list_result.clear();
        list_result << Property2String(logo_prop);
        list_result << PropertyList2LeafOnlyStringList(panel_props);

        for (const QList<Property> &props : std::as_const(helper_props_map)) {
            list_result << PropertyList2LeafOnlyStringList(props);
        }

        list_result << Property2String(show_help_prop);

        message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "RegisterProperties"_L1);

        message << list_result;
        QDBusConnection("scim_panel"_L1).send(message);
    }
    void Exit()
    {
        SCIM_DEBUG_MAIN(1) << Q_FUNC_INFO << "\n";
        _panel_agent->exit();
        _panel_agent->stop();
        exit(1);
    }
    void ReloadConfig()
    {
        SCIM_DEBUG_MAIN(1) << Q_FUNC_INFO << "\n";
        _panel_agent->reload_config();
        if (!_config.null())
            _config->reload();
    }
    void Configure()
    {
        SCIM_DEBUG_MAIN(1) << Q_FUNC_INFO << "\n";
        QProcess::startDetached("scim-setup"_L1, QStringList());
    }

protected:
    bool event(QEvent *e) override
    {
        QStringList list_result;
        QList<Property> prop_list;
        if (e->type() == dbus_event_type) {
            DBusEvent *ev = (DBusEvent *)e;

            QDBusMessage message;
            QVariantList data;

            switch (ev->scim_event_type()) {
            case DBusEvent::TURN_ON:
                /*
                list_result.clear();
                list_result << Property2String(logo_prop);
                list_result << PropertyList2LeafOnlyStringList(panel_props);
                for(const QList<Property> &props : std::as_const(helper_props_map)) {
                    list_result << PropertyList2LeafOnlyStringList(props);
                }
                list_result << Property2String(show_help_prop);

                message = QDBusMessage::createSignal("/kimpanel"_L1,
                    "org.kde.kimpanel.inputmethod",
                    "RegisterProperties");

                message << list_result;
                QDBusConnection("scim_panel").send(message);
                */
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "Enable"_L1);
                message << true;
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::TURN_OFF:
                logo_prop.set_icon(String(SCIM_ICON_DIR) + "/trademark.png");

                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "UpdateProperty"_L1);
                message << Property2String(logo_prop);
                QDBusConnection("scim_panel"_L1).send(message);
                /*
                list_result.clear();
                list_result << Property2String(logo_prop);
                for (const QList<Property> &prop_list : std::as_const(helper_props_map)) {
                    list_result << PropertyList2LeafOnlyStringList(prop_list);
                }
                list_result << Property2String(show_help_prop);

                message = QDBusMessage::createSignal("/kimpanel",
                    "org.kde.kimpanel.inputmethod",
                    "RegisterProperties");

                message << list_result;

                QDBusConnection("scim_panel").send(message);
                */
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "Enable"_L1);
                message << false;
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::SHOW_HELP:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "ExecDialog"_L1);
                message << Property2String(Property("/Help", "Help", "", ev->data().at(0).toString().toUtf8().constData()));
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::SHOW_FACTORY_MENU:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "ExecMenu"_L1);
                // X                 _factory_list.clear();
                list_result.clear();
                data = ev->data();
                for (const QVariant &v : std::as_const(data)) {
                    PanelFactoryInfo factory_info = v.value<PanelFactoryInfo>();
                    // X                     _factory_list << factory_info;
                    list_result << Property2String(Property(String(factory_prop_prefix.toUtf8().constData()) + factory_info.uuid,
                                                            factory_info.name,
                                                            factory_info.icon,
                                                            factory_info.lang));
                }
                message << list_result;
                SCIM_DEBUG_MAIN(1) << qPrintable(list_result.join(QLatin1Char(';'))) << "\n";
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::SHOW_PREEDIT:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "ShowPreedit"_L1);
                message << true;
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::SHOW_AUX:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "ShowAux"_L1);
                message << true;
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::SHOW_LOOKUPTABLE:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "ShowLookupTable"_L1);
                message << true;
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::HIDE_PREEDIT:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "ShowPreedit"_L1);
                message << false;
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::HIDE_AUX:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "ShowAux"_L1);
                message << false;
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::HIDE_LOOKUPTABLE:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "ShowLookupTable"_L1);
                message << false;
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::UP_PROPERTY:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "UpdateProperty"_L1);
                message << Property2String(ev->data().at(0).value<Property>());
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::UP_LOOKUPTABLE:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "UpdateLookupTable"_L1);
                message << ev->data().at(0).toStringList();
                message << ev->data().at(1).toStringList();
                message << ev->data().at(2).toStringList();
                message << ev->data().at(3).toBool();
                message << ev->data().at(4).toBool();
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::UP_LOOKUPTABLE_CURSOR:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "UpdateLookupTableCursor"_L1);
                message << ev->data().at(0).toInt();
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::UP_PREEDIT_CARET:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "UpdatePreeditCaret"_L1);
                message << ev->data().at(0).toInt();
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::UP_PREEDIT_STR:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "UpdatePreeditText"_L1);
                message << ev->data().at(0).toString();
                message << ev->data().at(1).toString();
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::UP_AUX:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "UpdateAux"_L1);
                message << ev->data().at(0).toString();
                message << ev->data().at(1).toString();
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::UP_SPOT_LOC:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "UpdateSpotLocation"_L1);
                message << ev->data().at(0).toInt();
                message << ev->data().at(1).toInt();
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::UP_SCREEN:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "UpdateScreen"_L1);
                message << ev->data().at(0).toInt();
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::UP_FACTORY_INFO:
                SCIM_DEBUG_MAIN(1) << "update factory info\n";

                logo_prop.set_icon(ev->data().at(0).value<PanelFactoryInfo>().icon);
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "UpdateProperty"_L1);
                message << Property2String(logo_prop);
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            case DBusEvent::REG_HELPER:
                break;
            case DBusEvent::REG_HELPER_PROPERTIES:
                helper_props_map.clear();
                prop_list.clear();
                data = ev->data().at(1).toList();
                for (const QVariant &v : std::as_const(data)) {
                    Property prop = v.value<Property>();
                    prop_list << prop;
                }
                helper_props_map.insert(ev->data().at(0).toInt(), prop_list);

                list_result.clear();
                list_result << Property2String(logo_prop);
                list_result << PropertyList2LeafOnlyStringList(panel_props);
                for (const QList<Property> &props : std::as_const(helper_props_map)) {
                    list_result << PropertyList2LeafOnlyStringList(props);
                }
                list_result << Property2String(show_help_prop);

                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "RegisterProperties"_L1);

                message << list_result;
                QDBusConnection("scim_panel"_L1).send(message);

                break;
            case DBusEvent::REG_PROPERTIES:
                panel_props.clear();
                data = ev->data();
                for (const QVariant &v : std::as_const(data)) {
                    Property prop = v.value<Property>();
                    panel_props << prop;
                    SCIM_DEBUG_MAIN(1) << "REG_PROPERTIES" << qPrintable(Property2String(v.value<Property>())) << "\n";
                }

                list_result.clear();
                list_result << Property2String(logo_prop);
                list_result << PropertyList2LeafOnlyStringList(panel_props);
                SCIM_DEBUG_MAIN(1) << "SIMPLIFY_PROPS " << panel_props.size() << " " << list_result.size() - 1 << "\n";
                for (const QList<Property> &props : std::as_const(helper_props_map)) {
                    list_result << PropertyList2LeafOnlyStringList(props);
                }
                list_result << Property2String(show_help_prop);

                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "RegisterProperties"_L1);

                message << list_result;
                QDBusConnection("scim_panel"_L1).send(message);

                break;
            case DBusEvent::RM_HELPER:
                helper_props_map.remove(ev->data().at(0).toInt());
                list_result.clear();
                list_result << Property2String(logo_prop);
                list_result << PropertyList2LeafOnlyStringList(panel_props);
                for (const QList<Property> &props : std::as_const(helper_props_map)) {
                    list_result << PropertyList2LeafOnlyStringList(props);
                }
                list_result << Property2String(show_help_prop);

                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "RegisterProperties"_L1);

                message << list_result;
                QDBusConnection("scim_panel"_L1).send(message);

                break;
            case DBusEvent::RM_PROPERTY:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "RemoveProperty"_L1);
                // message << ev->data().at(0).V();
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            default:
                message = QDBusMessage::createSignal("/kimpanel"_L1, "org.kde.kimpanel.inputmethod"_L1, "ImplementMe"_L1);
                // message << ev->data().at(0).toInt();
                SCIM_DEBUG_MAIN(1) << "Implement me:" << ev->scim_event_type() << "\n";
                QDBusConnection("scim_panel"_L1).send(message);
                break;
            }

            return true;
        }
        return QObject::event(e);
    }

private:
    QList<Property> panel_props;
    QMap<int, QList<Property>> helper_props_map;
    Property logo_prop;
    Property show_help_prop;
    QString factory_prop_prefix;
    QString helper_prop_prefix;
    QStringList cached_panel_props;
};

class PanelAgentThread : public QThread
{
    Q_OBJECT
public:
    explicit PanelAgentThread(QObject *parent = nullptr)
        : QThread(parent)
    {
    }
    ~PanelAgentThread()
    {
    }
    void run() override
    {
        if (!_panel_agent->run())
            std::cerr << "Failed to run Panel.\n";
        _global_resource_lock.lock();
        _should_exit = true;
        _global_resource_lock.unlock();
    }
};

//////////////////////////////////////////////////////////////////////
// Start of PanelAgent Functions
//////////////////////////////////////////////////////////////////////
static bool initialize_panel_agent(const String &config, const String &display, bool resident)
{
    _panel_agent = new PanelAgent();

    if (!_panel_agent->initialize(config, display, resident))
        return false;

    _panel_agent->signal_connect_transaction_start(slot(slot_transaction_start));
    _panel_agent->signal_connect_transaction_end(slot(slot_transaction_end));
    _panel_agent->signal_connect_reload_config(slot(slot_reload_config));
    _panel_agent->signal_connect_turn_on(slot(slot_turn_on));
    _panel_agent->signal_connect_turn_off(slot(slot_turn_off));
    _panel_agent->signal_connect_update_screen(slot(slot_update_screen));
    _panel_agent->signal_connect_update_spot_location(slot(slot_update_spot_location));
    _panel_agent->signal_connect_update_factory_info(slot(slot_update_factory_info));
    _panel_agent->signal_connect_show_help(slot(slot_show_help));
    _panel_agent->signal_connect_show_factory_menu(slot(slot_show_factory_menu));
    _panel_agent->signal_connect_show_preedit_string(slot(slot_show_preedit_string));
    _panel_agent->signal_connect_show_aux_string(slot(slot_show_aux_string));
    _panel_agent->signal_connect_show_lookup_table(slot(slot_show_lookup_table));
    _panel_agent->signal_connect_hide_preedit_string(slot(slot_hide_preedit_string));
    _panel_agent->signal_connect_hide_aux_string(slot(slot_hide_aux_string));
    _panel_agent->signal_connect_hide_lookup_table(slot(slot_hide_lookup_table));
    _panel_agent->signal_connect_update_preedit_string(slot(slot_update_preedit_string));
    _panel_agent->signal_connect_update_preedit_caret(slot(slot_update_preedit_caret));
    _panel_agent->signal_connect_update_aux_string(slot(slot_update_aux_string));
    _panel_agent->signal_connect_update_lookup_table(slot(slot_update_lookup_table));
    _panel_agent->signal_connect_register_properties(slot(slot_register_properties));
    _panel_agent->signal_connect_update_property(slot(slot_update_property));
    _panel_agent->signal_connect_register_helper_properties(slot(slot_register_helper_properties));
    _panel_agent->signal_connect_update_helper_property(slot(slot_update_helper_property));
    _panel_agent->signal_connect_register_helper(slot(slot_register_helper));
    _panel_agent->signal_connect_remove_helper(slot(slot_remove_helper));
    _panel_agent->signal_connect_lock(slot(slot_lock));
    _panel_agent->signal_connect_unlock(slot(slot_unlock));

    _panel_agent->get_helper_list(_helper_list);

    return true;
}

static bool run_panel_agent(void)
{
    SCIM_DEBUG_MAIN(1) << "run_panel_agent ()\n";

    _panel_agent_thread = NULL;

    if (_panel_agent && _panel_agent->valid()) {
        _panel_agent_thread = new PanelAgentThread();
        _panel_agent_thread->start();
    }

    return (_panel_agent_thread != NULL);
}

static void start_auto_start_helpers(void)
{
    SCIM_DEBUG_MAIN(1) << "start_auto_start_helpers () begin\n";

    // Add Helper object items.
    for (size_t i = 0; i < _helper_list.size(); ++i) {
        SCIM_DEBUG_MAIN(1) << "--" << _helper_list[i].uuid << "--" << _helper_list[i].name << "--\n";
        if ((_helper_list[i].option & SCIM_HELPER_AUTO_START)) {
            _panel_agent->start_helper(_helper_list[i].uuid);
        }
    }
    SCIM_DEBUG_MAIN(1) << "start_auto_start_helpers () end\n";
}

static void slot_transaction_start(void)
{
    SCIM_DEBUG_MAIN(1) << "slot_transaction_start ()\n";
    _transaction_lock.lock();
}

static void slot_transaction_end(void)
{
    SCIM_DEBUG_MAIN(1) << "slot_transaction_end ()\n";
    _transaction_lock.unlock();
}

static void slot_reload_config(void)
{
    SCIM_DEBUG_MAIN(1) << "slot_reload_config ()\n";
    if (!_config.null())
        _config->reload();
    else
        SCIM_DEBUG_MAIN(1) << "config is null\n";
}

static void slot_turn_on(void)
{
    SCIM_DEBUG_MAIN(1) << "slot_turn_on ()" << _dbus_handler << "\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::TURN_ON));
}

static void slot_turn_off(void)
{
    SCIM_DEBUG_MAIN(1) << "slot_turn_off ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::TURN_OFF));

    /*
     */
}

static void slot_update_screen(int num)
{
    SCIM_DEBUG_MAIN(1) << "slot_update_screen ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::UP_SCREEN, QVariantList() << num));
}

static void slot_update_factory_info(const PanelFactoryInfo &info)
{
    SCIM_DEBUG_MAIN(1) << "slot_update_factory_info ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::UP_FACTORY_INFO, QVariantList() << QVariant::fromValue(info)));
}

static void slot_show_help(const String &help)
{
    SCIM_DEBUG_MAIN(1) << "slot_show_help ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::SHOW_HELP, QVariantList() << QString::fromUtf8(help.c_str())));
}

static void slot_show_factory_menu(const std::vector<PanelFactoryInfo> &factories)
{
    SCIM_DEBUG_MAIN(1) << "slot_show_factory_menu ()\n";
    QVariantList list;
    for (const PanelFactoryInfo &info : factories) {
        list << QVariant::fromValue(info);
    }
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::SHOW_FACTORY_MENU, list));
}

static void slot_update_spot_location(int x, int y)
{
    SCIM_DEBUG_MAIN(1) << "slot_update_spot_location ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::UP_SPOT_LOC, QVariantList() << x << y));
}

static void slot_show_preedit_string(void)
{
    SCIM_DEBUG_MAIN(1) << "slot_show_preedit_string ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::SHOW_PREEDIT));
}

static void slot_show_aux_string(void)
{
    SCIM_DEBUG_MAIN(1) << "slot_show_aux_string ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::SHOW_AUX));
}

static void slot_show_lookup_table(void)
{
    SCIM_DEBUG_MAIN(1) << "slot_show_lookup_table ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::SHOW_LOOKUPTABLE));
}

static void slot_hide_preedit_string(void)
{
    SCIM_DEBUG_MAIN(1) << "slot_hide_preedit_string ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::HIDE_PREEDIT));
}

static void slot_hide_aux_string(void)
{
    SCIM_DEBUG_MAIN(1) << "slot_hide_aux_string ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::HIDE_AUX));
}

static void slot_hide_lookup_table(void)
{
    SCIM_DEBUG_MAIN(1) << "slot_hide_lookup_table ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::HIDE_LOOKUPTABLE));
}

static void slot_update_preedit_string(const String &str, const AttributeList &attrs)
{
    SCIM_DEBUG_MAIN(1) << "slot_update_preedit_string ()" << str << "\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::UP_PREEDIT_STR, QVariantList() << QString::fromUtf8(str.c_str()) << AttrList2String(attrs)));
}

static void slot_update_preedit_caret(int caret)
{
    SCIM_DEBUG_MAIN(1) << "slot_update_preedit_caret ()" << caret << "\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::UP_PREEDIT_CARET, QVariantList() << caret));
}

static void slot_update_aux_string(const String &str, const AttributeList &attrs)
{
    SCIM_DEBUG_MAIN(1) << "slot_update_aux_string ()" << str << "\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::UP_AUX, QVariantList() << QString::fromUtf8(str.c_str()) << AttrList2String(attrs)));
}

static void slot_update_lookup_table(const LookupTable &table)
{
    SCIM_DEBUG_MAIN(1) << "slot_update_lookup_table ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::UP_LOOKUPTABLE, LookupTable2VariantList(table)));

    qApp->postEvent(
        _dbus_handler,
        new DBusEvent(DBusEvent::UP_LOOKUPTABLE_CURSOR, QVariantList() << (table.is_cursor_visible() ? table.get_cursor_pos_in_current_page() : -1)));
}

static void slot_register_properties(const PropertyList &props)
{
    SCIM_DEBUG_MAIN(1) << "slot_register_properties ()\n";
    QVariantList list;
    for (const Property &prop : props) {
        list << QVariant::fromValue(prop);
    }
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::REG_PROPERTIES, list));
}

static void slot_update_property(const Property &prop)
{
    SCIM_DEBUG_MAIN(1) << "slot_update_property ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::UP_PROPERTY, QVariantList() << QVariant::fromValue(prop)));
}

static void slot_register_helper_properties(int id, const PropertyList &props)
{
    SCIM_DEBUG_MAIN(1) << "slot_register_helper_properties ()\n";
    QVariantList list;
    for (const Property &prop : props) {
        list << QVariant::fromValue(prop);
    }
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::REG_HELPER_PROPERTIES, QVariantList() << id << list));
}

static void slot_update_helper_property(int id, const Property &prop)
{
    SCIM_DEBUG_MAIN(1) << "slot_update_helper_property ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::UP_HELPER_PROPERTY, QVariantList() << id << QVariant::fromValue(prop)));
}

static void slot_register_helper(int id, const HelperInfo &helper)
{
    SCIM_DEBUG_MAIN(1) << "slot_register_helper ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::REG_HELPER, QVariantList() << id << QVariant::fromValue(helper)));
}

static void slot_remove_helper(int id)
{
    SCIM_DEBUG_MAIN(1) << "slot_remove_helper ()\n";
    qApp->postEvent(_dbus_handler, new DBusEvent(DBusEvent::RM_HELPER, QVariantList() << id));
}

static void slot_lock(void)
{
    SCIM_DEBUG_MAIN(1) << "slot_lock ()\n";
    //_panel_agent_lock.lock();
}

static void slot_unlock(void)
{
    SCIM_DEBUG_MAIN(1) << "slot_unlock ()\n";
    //_panel_agent_lock.unlock();
}
//////////////////////////////////////////////////////////////////////
// End of PanelAgent-Functions
//////////////////////////////////////////////////////////////////////

static void signalhandler(int sig)
{
    Q_UNUSED(sig)
    SCIM_DEBUG_MAIN(1) << "In signal handler...\n";

    if (_panel_agent)
        _panel_agent->stop();
}

int main(int argc, char *argv[])
{
    std::vector<String> config_list;

    int i;

    bool daemon = false;

    int new_argc = 0;
    char **new_argv = new char *[40];

    String config_name("simple");
    String display_name;
    bool should_resident = true;

    //    QTextCodec::setCodecForCStrings(QTextCodec::codecForLocale());
    // Display version info
    //    std::cerr << "GTK Panel of SCIM " << SCIM_VERSION << "\n\n";

    // get modules list
    scim_get_config_module_list(config_list);

    // Add a dummy config module, it's not really a module!
    config_list.push_back("dummy");

    // Use socket Config module as default if available.
    if (!config_list.empty()) {
        if (std::find(config_list.begin(), config_list.end(), config_name) == config_list.end())
            config_name = config_list[0];
    }

    // DebugOutput::set_output("/home/ora/a.log");
    // DebugOutput::enable_debug (SCIM_DEBUG_AllMask);

    // parse command options
    i = 0;
    while (i < argc) {
        if (++i >= argc)
            break;

        if (String("-l") == argv[i] || String("--list") == argv[i]) {
            std::vector<String>::iterator it;

            std::cout << "\n";
            std::cout << "Available Config module:\n";
            for (it = config_list.begin(); it != config_list.end(); ++it)
                std::cout << "    " << *it << "\n";

            return 0;
        }

        if (String("-c") == argv[i] || String("--config") == argv[i]) {
            if (++i >= argc) {
                std::cerr << "no argument for option " << argv[i - 1] << "\n";
                return -1;
            }
            config_name = argv[i];
            continue;
        }

        if (String("-h") == argv[i] || String("--help") == argv[i]) {
            std::cout << "Usage: " << argv[0] << " [option]...\n\n"
                      << "The options are: \n"
                      << "  --display DISPLAY    Run on display DISPLAY.\n"
                      << "  -l, --list           List all of available config modules.\n"
                      << "  -c, --config NAME    Uses specified Config module.\n"
                      << "  -d, --daemon         Run " << argv[0] << " as a daemon.\n"
                      << "  -ns, --no-stay       Quit if no connected client.\n"
#if ENABLE_DEBUG
                      << "  -v, --verbose LEVEL  Enable debug info, to specific LEVEL.\n"
                      << "  -o, --output FILE    Output debug information into FILE.\n"
#endif
                      << "  -h, --help           Show this help message.\n";
            return 0;
        }

        if (String("-d") == argv[i] || String("--daemon") == argv[i]) {
            daemon = true;
            continue;
        }

        if (String("-ns") == argv[i] || String("--no-stay") == argv[i]) {
            should_resident = false;
            continue;
        }

        if (String("-v") == argv[i] || String("--verbose") == argv[i]) {
            if (++i >= argc) {
                std::cerr << "no argument for option " << argv[i - 1] << "\n";
                return -1;
            }
            DebugOutput::set_verbose_level(atoi(argv[i]));
            continue;
        }

        if (String("-o") == argv[i] || String("--output") == argv[i]) {
            if (++i >= argc) {
                std::cerr << "No argument for option " << argv[i - 1] << "\n";
                return -1;
            }
            DebugOutput::set_output(argv[i]);
            continue;
        }

        if (String("--display") == argv[i]) {
            if (++i >= argc) {
                std::cerr << "No argument for option " << argv[i - 1] << "\n";
                return -1;
            }
            display_name = argv[i];
            continue;
        }

        if (String("--") == argv[i])
            break;

        std::cerr << "Invalid command line option: " << argv[i] << "\n";
        return -1;
    } // End of command line parsing.

    new_argv[new_argc++] = argv[0];

    // Store the rest argvs into new_argv.
    for (++i; i < argc && new_argc < 40; ++i) {
        new_argv[new_argc++] = argv[i];
    }

    // Make up DISPLAY env.
    if (display_name.length()) {
        new_argv[new_argc++] = strdup("--display");
        new_argv[new_argc++] = const_cast<char *>(display_name.c_str());

        setenv("DISPLAY", display_name.c_str(), 1);
    }

    new_argv[new_argc] = 0;

    if (!config_name.length()) {
        std::cerr << "No Config module is available!\n";
        return -1;
    }

    if (config_name != "dummy") {
        // load config module
        _config_module = new ConfigModule(config_name);

        if (!_config_module || !_config_module->valid()) {
            std::cerr << "Can not load " << config_name << " Config module.\n";
            return -1;
        }

        // create config instance
        _config = _config_module->create_config();
    } else {
        _config = new DummyConfig();
    }

    if (_config.null()) {
        std::cerr << "Failed to create Config instance from " << config_name << " Config module.\n";
        return -1;
    }

    KDE_signal(SIGQUIT, signalhandler);
    KDE_signal(SIGTERM, signalhandler);
    KDE_signal(SIGINT, signalhandler);
    KDE_signal(SIGHUP, signalhandler);

    const QByteArray display = qgetenv("DISPLAY");
    if (display.constData())
        display_name = String(display.constData());

    if (!initialize_panel_agent(config_name, display_name, should_resident)) {
        std::cerr << "Failed to initialize Panel Agent!\n";
        return -1;
    }

    if (daemon)
        scim_daemon();

    // connect the configuration reload signal.
    _config->signal_connect_reload(slot(ui_config_reload_callback));

    qRegisterMetaType<scim::Property>("scim::Property");
    qRegisterMetaType<PanelFactoryInfo>("PanelFactoryInfo");
    qRegisterMetaType<HelperInfo>("HelperInfo");

    QDBusConnection::connectToBus(QDBusConnection::SessionBus, "scim_panel"_L1);
    _dbus_handler = new DBusHandler();
    _dbus_handler->setInitialHelpers(_helper_list);

    if (!run_panel_agent()) {
        std::cerr << "Failed to run Socket Server!\n";
        return -1;
    }

    start_auto_start_helpers();

    QCoreApplication app(argc, argv);
    app.exec();

    _panel_agent_thread->wait();
    _config.reset();

    std::cerr << "Successfully exited.\n";

    return 0;
}

#include "main.moc"

#undef QT_NO_KEYWORDS

/*
vim:ts=4:nowrap:expandtab
*/
