#include "include/keygrabber.h"

#include <QMenu>

KeyGrabber::KeyGrabber(QWidget* parent)
    : QTreeWidget(parent)
{
    setColumnCount(2);
    setColumnWidth(0, 400);
    setAlternatingRowColors(true);
    setHeaderLabels(QStringList() << tr("Action") << tr("Keyboard Shortcut"));

    connect(this, &QTreeWidget::itemChanged, this, &KeyGrabber::itemChanged);
}

bool KeyGrabber::hasConflicts() const
{ return m_firstConflict != nullptr; }

void KeyGrabber::scrollToConflict()
{
    if (!m_firstConflict) {
        return;
    }

    scrollTo(indexFromItem(m_firstConflict));
}

void KeyGrabber::itemChanged(QTreeWidgetItem*)
{
    // Don't do anything if it isn't active or we're in the middle of testing for conflicts
    // since item->setBackground() emits an itemChanged signal.
    if (!currentItem() || m_testingForConflicts) {
        return;
    }

    checkForConflicts();
}

void KeyGrabber::checkForConflicts()
{
    m_testingForConflicts = true;

    // Find conflicts among shortcuts. We take a list of all shortcuts, sort them, then
    // walk through them and compare them for equality.
    QList<NodeItem> allNodes = m_allActions;
    std::sort(allNodes.begin(), allNodes.end(), [](const NodeItem& a, const NodeItem& b) {
        return a.treeItem->text(1) < b.treeItem->text(1);
    });

    for (const auto& n : allNodes) {
        n.treeItem->setBackground(1, QBrush());
    }

    m_firstConflict = nullptr;

    for (int i = 0; i < allNodes.count() - 1; i++) {
        QTreeWidgetItem* current = allNodes[i].treeItem;
        QTreeWidgetItem* next = allNodes[i + 1].treeItem;

        if (current->text(1).isEmpty()) {
            continue;
        }

        if (current->text(1) == next->text(1)) {
            current->setBackground(1, QBrush(QColor(255, 100, 100, 64)));
            next->setBackground(1, QBrush(QColor(255, 100, 100, 64)));

            if (!m_firstConflict) {
                m_firstConflict = current;
            }
        }
    }

    m_testingForConflicts = false;
}

void KeyGrabber::addMenus(const QList<const QMenu*>& listOfMenus)
{
    for (const QMenu* menu : listOfMenus) {
        if (menu->objectName() == "menu_Language" || menu->objectName().isEmpty()) {
            continue;
        }

        auto item = new QTreeWidgetItem();
        item->setText(0, menu->menuAction()->iconText());

        populateNode(item, menu);

        addTopLevelItem(item);
    }
}

QList<KeyGrabber::NodeItem>& KeyGrabber::getAllBindings()
{ return m_allActions; }

void KeyGrabber::keyPressEvent(QKeyEvent* event)
{
    QString grab;
    QString key = QKeySequence(event->key()).toString();
    int modifiers = event->modifiers();

    // Keys to ignore (e.g. navigation keys with no modifiers)
    if (modifiers == 0) {
        switch (event->key()) {
        case Qt::Key_Up:
        case Qt::Key_Down:
            QTreeWidget::keyPressEvent(event);
            return;
        }
    }

    const QVariant& data = currentItem()->data(0, Qt::UserRole);
    if (!data.isValid()) {
        QTreeWidget::keyPressEvent(event);
        return;
    }

    if (modifiers & Qt::ControlModifier)
        grab.append("Ctrl+");
    if (modifiers & Qt::AltModifier)
        grab.append("Alt+");
    if (modifiers & Qt::MetaModifier)
        grab.append("Meta+");
    if (modifiers & Qt::ShiftModifier)
        grab.append("Shift+");

    switch (event->key()) {
    case Qt::Key_Alt:
    case Qt::Key_Control:
    case Qt::Key_Meta:
    case Qt::Key_Shift:
        return;

    case Qt::Key_F1:
    case Qt::Key_F2:
    case Qt::Key_F3:
    case Qt::Key_F4:
    case Qt::Key_F5:
    case Qt::Key_F6:
    case Qt::Key_F7:
    case Qt::Key_F8:
    case Qt::Key_F9:
    case Qt::Key_F10:
    case Qt::Key_F11:
    case Qt::Key_F12:
    case Qt::Key_F13:
    case Qt::Key_F14:
    case Qt::Key_F15:
    case Qt::Key_F16:
    case Qt::Key_F17:
    case Qt::Key_F18:
    case Qt::Key_F19:
    case Qt::Key_F20:
    case Qt::Key_F21:
    case Qt::Key_F22:
    case Qt::Key_F23:
    case Qt::Key_F24:
    case Qt::Key_F25:
    case Qt::Key_F26:
    case Qt::Key_F27:
    case Qt::Key_F28:
    case Qt::Key_F29:
    case Qt::Key_F30:
    case Qt::Key_F31:
    case Qt::Key_F32:
    case Qt::Key_F33:
    case Qt::Key_F34:
    case Qt::Key_F35:
        grab.append(key);
        break;

    case Qt::Key_Backspace:
        if (modifiers) {
            grab.append(key);
        } else {
            currentItem()->setText(1, "");
            return;
        }
        break;

    default:
        if (modifiers) {
            grab.append(key);
        }
        break;
    }

    currentItem()->setText(1, grab);
}

void KeyGrabber::populateNode(QTreeWidgetItem*& rootItem, const QMenu* menu)
{
    for (QAction* action : menu->actions()) {
        if (action->isSeparator()) {
            continue;

        } else if (action->menu()) {
            // If the action is a sub-menu, we add a new tree node and populate it with
            // the children of this menu. Exceptions that should not be added can be
            // specified here.
            if (action->menu()->objectName() == "menuRecent_Files")
                continue;

            auto item = new QTreeWidgetItem();
            item->setText(0, action->iconText());
            populateNode(item, action->menu());
            rootItem->addChild(item);
        } else {
            // Any action that does not have an object name or label won't be added.
            // This way we exclude things such as the entries in the recent documents
            // list.
            if (action->objectName().isEmpty() || action->iconText().isEmpty()) {
                continue;
            }

            auto item = new QTreeWidgetItem();
            item->setText(0, action->iconText());
            item->setText(1, action->shortcut().toString());
            // Every action that can have a shortcut gets this user value set to 'true'.
            // This is used in keyPressEvent to figure out whether to read the key evts.
            item->setData(0, Qt::UserRole, true);
            rootItem->addChild(item);

            m_allActions.push_back(NodeItem{action, item});
        }
    }
}
