#include <QDialog>
#include <QDialogButtonBox>
#include <QHeaderView>
#include <QStandardItemModel>
#include <QPushButton>
#include <QTableView>
#include "keyboard.h"
#include "evdev-lst-layouts.h"
#include "environment.h"
#include "find-themes.h"
#include "layoutmodel.h"
#include "macros.h"
#include "settings.h"
#include "./ui_keyboard.h"

Keyboard::Keyboard(QWidget *parent) : QWidget(parent), ui(new Ui::pageKeyboard)
{
    ui->setupUi(this);
}

Keyboard::~Keyboard()
{
    delete ui;
}

void Keyboard::getGrpToggleOptions(QVector<QSharedPointer<Pair>> &combo)
{
    // Generated based on XKEYBOARD-CONFIG(7)
    // clang-format off
    combo.append(QSharedPointer<Pair>(new Pair("", "")));
    combo.append(QSharedPointer<Pair>(new Pair("grp:shift_caps_toggle", tr("Shift+Caps Lock"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:alt_caps_toggle", tr("Alt+Caps Lock"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:shifts_toggle", tr("Both Shifts together"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:alts_toggle", tr("Both Alts together"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:ctrls_toggle", tr("Both Ctrls together"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:switch", tr("Right Alt (while pressed)"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:lswitch", tr("Left Alt (while pressed)"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:lwin_switch", tr("Left Win (while pressed)"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:rwin_switch", tr("Right Win (while pressed)"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:win_switch", tr("Any Win (while pressed)"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:menu_switch",
        tr("Menu (while pressed), Shift+Menu for Menu"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:caps_switch",
        tr("Caps Lock (while pressed), Alt+Caps Lock for the original Caps Lock action"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:rctrl_switch",
        tr("Right Ctrl (while pressed)"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:toggle", tr("Right Alt"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:lalt_toggle", tr("Left Alt"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:caps_toggle", tr("Caps Lock"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:caps_select",
        tr("Caps Lock to first layout; Shift+Caps Lock to second layout"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:win_menu_select",
        tr("Left Win to first layout; Right Win/Menu to second layout"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:ctrl_select",
        tr("Left Ctrl to first layout; Right Ctrl to second layout"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:alt_altgr_toggle",
        tr("Both Alts together; AltGr alone chooses third level"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:ctrl_shift_toggle", tr("Ctrl+Shift"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:lctrl_lshift_toggle",
        tr("Left Ctrl+Left Shift"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:rctrl_rshift_toggle",
        tr("Right Ctrl+Right Shift"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:ctrl_shift_toggle_bidir",
        tr("Left Ctrl+Left Shift chooses previous layout, Right Ctrl + Right Shift chooses next layout"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:ctrl_alt_toggle", tr("Alt+Ctrl"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:lctrl_lalt_toggle", tr("Left Alt+Left Ctrl"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:rctrl_ralt_toggle",
        tr("Right Alt+Right Ctrl"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:ctrl_alt_toggle_bidir",
        tr("Left Ctrl+Left Alt chooses previous layout, Right Ctrl + Right Alt chooses next layout"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:alt_shift_toggle", tr("Alt+Shift"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:lalt_lshift_toggle",
        tr("Left Alt+Left Shift"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:ralt_rshift_toggle",
        tr("Right Alt+Right Shift"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:alt_shift_toggle_bidir",
        tr("Left Alt+Left Shift chooses previous layout, Right Alt + Right Shift chooses next layout"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:menu_toggle", tr("Menu"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:lwin_toggle", tr("Left Win"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:alt_space_toggle", tr("Alt+Space"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:win_space_toggle", tr("Win+Space"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:ctrl_space_toggle", tr("Ctrl+Space"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:rwin_toggle", tr("Right Win"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:lshift_toggle", tr("Left Shift"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:rshift_toggle", tr("Right Shift"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:lctrl_toggle", tr("Left Ctrl"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:rctrl_toggle", tr("Right Ctrl"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:sclk_toggle", tr("Scroll Lock"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:lctrl_lwin_rctrl_menu",
        tr("Ctrl+Left Win to first layout; Ctrl+Menu to second layout"))));
    combo.append(QSharedPointer<Pair>(new Pair("grp:lctrl_lwin_toggle", tr("Left Ctrl+Left Win"))));
    // clang-format on
}

void Keyboard::activate()
{
    /*
     * Keyboard Layout
     *
     * We fallback on the environment variable if nothing has been set in the environment file
     */
    QString xkb_default_layout = qgetenv("XKB_DEFAULT_LAYOUT");
    settingsAddEnvStr("XKB_DEFAULT_LAYOUT", xkb_default_layout.isEmpty() ? "" : xkb_default_layout);

    m_model = new LayoutModel(this);
    ui->layoutView->setModel(m_model);
    connect(ui->layoutAdd, &QPushButton::pressed, this, &Keyboard::addSelectedLayout);
    connect(ui->layoutRemove, &QPushButton::pressed, this, &Keyboard::deleteSelectedLayout);

    ui->layoutCombo->addItem(tr("Select layout to add..."));
    for (auto layout : evdev_lst_layouts) {
        ui->layoutCombo->addItem(layout.description);
    }

    /* Repeat Rate */
    settingsAddXmlInt("/labwc_config/keyboard/repeatRate", 25);
    ui->repeatRate->setValue(getInt("/labwc_config/keyboard/repeatRate"));
    ui->repeatRate->setToolTip(tr("Rate at which keypresses are repeated per second"));

    /* Repeat Delay */
    settingsAddXmlInt("/labwc_config/keyboard/repeatDelay", 600);
    ui->repeatDelay->setValue(getInt("/labwc_config/keyboard/repeatDelay"));
    ui->repeatDelay->setToolTip(tr("Delay before keypresses are repeated"));

    /* Numlock */
    settingsAddXmlBoo("/labwc_config/keyboard/numlock", false);
    ui->numlock->setChecked(getBool("/labwc_config/keyboard/numlock"));
    ui->numlock->setToolTip(tr("Enable Num Lock when recognizing a new keyboard"));

    // Keyboard Layout Group Switching
    settingsAddEnvStr("XKB_DEFAULT_OPTIONS", "");
    ui->layoutGrpSwitcher->setToolTip(tr("Key combination to switch keyboard layout"));
    QVector<QSharedPointer<Pair>> combo;
    getGrpToggleOptions(combo);

    QString current = getStr("XKB_DEFAULT_OPTIONS");
    ui->layoutGrpSwitcher->setText(current);

    // QComboBoxes with longs strings are ellided with some themes (like Kvantum and Breeze) when
    // the drop down menu is opened. The elliding can be avoided, but then the text is just cut off
    // instead. We could of course just set the QComboBox width to the widest item, but that screws
    // up the layout quite badly.
    //
    // So, instead we use a QPushButton to open a QDialog with a QTableView. For this with need to
    // build a model with two columns:
    QStandardItemModel *model = new QStandardItemModel(this);
    model->setColumnCount(2);
    model->setHorizontalHeaderLabels({ tr("Key"), tr("Description") });
    model->setHeaderData(0, Qt::Horizontal, Qt::AlignLeft, Qt::TextAlignmentRole);
    model->setHeaderData(1, Qt::Horizontal, Qt::AlignLeft, Qt::TextAlignmentRole);
    auto addItem = [&](const QString &key, const QString &text) {
        QStandardItem *keyItem = new QStandardItem(key);
        QStandardItem *textItem = new QStandardItem(text);
        keyItem->setData(key, Qt::UserRole);
        model->appendRow({ keyItem, textItem });
    };
    foreach (auto policy, combo) {
        addItem(policy.get()->value(), policy.get()->description());
    }

    // The lambda parameters must match the signal: clicked(bool) vs [](bool) { ... }
    connect(ui->layoutGrpSwitcher, &QPushButton::clicked, this, [this, model, current](bool) {
        QDialog dialog(this);
        dialog.setWindowTitle(tr("Select key combination"));

        QVBoxLayout layout(&dialog);

        QTableView view;
        view.setModel(model);
        view.setSelectionBehavior(QAbstractItemView::SelectRows);
        view.setSelectionMode(QAbstractItemView::SingleSelection);
        view.setEditTriggers(QAbstractItemView::NoEditTriggers);
        view.setTextElideMode(Qt::ElideNone);
        view.resizeColumnsToContents();
        view.horizontalHeader()->setStretchLastSection(true);
        view.verticalHeader()->hide();
        view.horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);

        // Restore selection when opening dialog
        for (int row = 0; row < model->rowCount(); ++row) {
            if (model->item(row, 0)->text() == current) {
                QModelIndex idx = model->index(row, 0);
                view.setCurrentIndex(idx);
                view.scrollTo(idx, QAbstractItemView::PositionAtCenter);
            }
        }

        layout.addWidget(&view);

        QDialogButtonBox buttons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
        layout.addWidget(&buttons);

        // Set sensible dialog size
        dialog.setMinimumWidth(500);
        dialog.setMinimumHeight(400);
        dialog.adjustSize();

        connect(&buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
        connect(&buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
        connect(&view, &QTableView::doubleClicked, &dialog, &QDialog::accept);

        if (dialog.exec() == QDialog::Accepted) {
            QModelIndex idx = view.currentIndex();
            if (idx.isValid()) {
                const int row = idx.row();
                const QString key = model->item(row, 0)->text();
                ui->layoutGrpSwitcher->setText(key);
            }
        }
    });
}

void Keyboard::addSelectedLayout(void)
{
    QString description = ui->layoutCombo->currentText();

    for (auto layout : evdev_lst_layouts) {
        if (description == layout.description) {
            m_model->addLayout(layout.code, layout.description);
        }
    }
}

void Keyboard::deleteSelectedLayout(void)
{
    m_model->deleteLayout(ui->layoutView->currentIndex().row());
}

void Keyboard::onApply()
{
    /*
     * We include variants in XKB_DEFAULT_LAYOUT, for example
     * "latam(deadtilde),ru(phonetic),gr", so XKB_DEFAULT_VARIANT is set to
     * empty.
     */
    QString layout = m_model->getXkbDefaultLayout();
    if (!layout.isEmpty()) {
        setStr("XKB_DEFAULT_LAYOUT", layout);
        environmentSet("XKB_DEFAULT_VARIANT", "");
    }

    setInt("/labwc_config/keyboard/repeatRate", ui->repeatRate->value());
    setInt("/labwc_config/keyboard/repeatDelay", ui->repeatDelay->value());
    setBool("/labwc_config/keyboard/numlock", ui->numlock->isChecked());

    setStr("XKB_DEFAULT_OPTIONS", ui->layoutGrpSwitcher->text());
}
