/*
    SPDX-FileCopyrightText: 2010-2018 Thomas Baumgart <tbaumgart@kde.org>
    SPDX-FileCopyrightText: 2010-2016 Cristian Oneț <onet.cristian@gmail.com>
    SPDX-FileCopyrightText: 2010 Alvaro Soliverez <asoliverez@gmail.com>
    SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
    SPDX-FileCopyrightText: 2020 Robert Szczesiak <dev.rszczesiak@gmail.com>
    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "kmymoneymvccombo.h"
#include "kmymoneymvccombo_p.h"

// ----------------------------------------------------------------------------
// QT Includes

#include <QStandardItemModel>
#include <QAbstractItemView>
#include <QCompleter>
#include <QFocusEvent>
#include <QApplication>
#include <QMetaMethod>

// ----------------------------------------------------------------------------
// KDE Includes

#include <KLineEdit>

// ----------------------------------------------------------------------------
// Project Includes


KMyMoneyMVCCombo::KMyMoneyMVCCombo(QWidget* parent) :
    KComboBox(parent),
    d_ptr(new KMyMoneyMVCComboPrivate)
{
    view()->setAlternatingRowColors(true);
    connect(this, static_cast<void (KComboBox::*)(int)>(&KMyMoneyMVCCombo::KComboBox::activated), this, &KMyMoneyMVCCombo::activated);
}

KMyMoneyMVCCombo::KMyMoneyMVCCombo(bool editable, QWidget* parent) :
    KComboBox(editable, parent),
    d_ptr(new KMyMoneyMVCComboPrivate)
{
    Q_D(KMyMoneyMVCCombo);
    d->m_completer = new QCompleter(this);
    d->m_completer->setCaseSensitivity(Qt::CaseInsensitive);
    d->m_completer->setModel(model());
    setCompleter(d->m_completer);

    // setSubstringSearch(!KMyMoneySettings::stringMatchFromStart());

    view()->setAlternatingRowColors(true);
    setInsertPolicy(QComboBox::NoInsert); // don't insert new objects due to object creation
    connect(this, static_cast<void (KComboBox::*)(int)>(&KMyMoneyMVCCombo::KComboBox::activated), this, &KMyMoneyMVCCombo::activated);
}

KMyMoneyMVCCombo::KMyMoneyMVCCombo(KMyMoneyMVCComboPrivate &dd, QWidget* parent) :
    KComboBox(parent),
    d_ptr(&dd)
{
    view()->setAlternatingRowColors(true);
    connect(this, static_cast<void (KComboBox::*)(int)>(&KMyMoneyMVCCombo::KComboBox::activated), this, &KMyMoneyMVCCombo::activated);
}

KMyMoneyMVCCombo::KMyMoneyMVCCombo(KMyMoneyMVCComboPrivate &dd, bool editable, QWidget* parent) :
    KComboBox(editable, parent),
    d_ptr(&dd)
{
    Q_D(KMyMoneyMVCCombo);
    d->m_completer = new QCompleter(this);
    d->m_completer->setCaseSensitivity(Qt::CaseInsensitive);
    d->m_completer->setModel(model());
    setCompleter(d->m_completer);

    // setSubstringSearch(!KMyMoneySettings::stringMatchFromStart());

    view()->setAlternatingRowColors(true);
    setInsertPolicy(QComboBox::NoInsert); // don't insert new objects due to object creation
    connect(this, static_cast<void (KComboBox::*)(int)>(&KMyMoneyMVCCombo::KComboBox::activated), this, &KMyMoneyMVCCombo::activated);
}

KMyMoneyMVCCombo::~KMyMoneyMVCCombo()
{
    Q_D(KMyMoneyMVCCombo);
    delete d;
}

void KMyMoneyMVCCombo::setEditable(bool editable)
{
    Q_D(KMyMoneyMVCCombo);
    KComboBox::setEditable(editable);

    if(editable) {
        if(!d->m_completer) {
            d->m_completer = new QCompleter(this);
            d->m_completer->setCaseSensitivity(Qt::CaseInsensitive);
            d->m_completer->setModel(model());
        }
        setCompleter(d->m_completer);
    }
}

void KMyMoneyMVCCombo::setSubstringSearch(bool enabled)
{
    Q_D(KMyMoneyMVCCombo);
    d->m_completer->setCompletionMode(QCompleter::PopupCompletion);
    d->m_completer->setModel(model());
    d->m_completer->setFilterMode(enabled ? Qt::MatchContains : Qt::MatchStartsWith);
}

void KMyMoneyMVCCombo::setSubstringSearchForChildren(QWidget*const widget, bool enabled)
{
    Q_CHECK_PTR(widget);
    const QList<KMyMoneyMVCCombo*> comboList = widget->findChildren<KMyMoneyMVCCombo*>();
    for (auto combo : comboList) {
        combo->setSubstringSearch(enabled);
    }
}

void KMyMoneyMVCCombo::setPlaceholderText(const QString& hint) const
{
    KLineEdit* le = qobject_cast<KLineEdit*>(lineEdit());
    if (le) {
        le->setPlaceholderText(hint);
    }
}

QString KMyMoneyMVCCombo::selectedItem() const
{
    Q_D(const KMyMoneyMVCCombo);
    auto dataVariant = itemData(currentIndex());
    if (dataVariant.isValid())
        d->m_id = dataVariant.toString();
    else
        d->m_id.clear();
    return d->m_id;
}

void KMyMoneyMVCCombo::setSelectedItem(const QString& id)
{
    Q_D(KMyMoneyMVCCombo);
    d->m_id = id;
    setCurrentIndex(findData(QVariant(d->m_id)));
}

void KMyMoneyMVCCombo::activated(int index)
{
    Q_D(KMyMoneyMVCCombo);
    auto dataVariant = itemData(index);
    if (dataVariant.isValid()) {
        d->m_id = dataVariant.toString();
        Q_EMIT itemSelected(d->m_id);
    }
}

void KMyMoneyMVCCombo::connectNotify(const QMetaMethod & signal)
{
    Q_D(KMyMoneyMVCCombo);
    if (signal != QMetaMethod::fromSignal(&KMyMoneyMVCCombo::createItem)) {
        d->m_canCreateObjects = true;
    }
}

void KMyMoneyMVCCombo::disconnectNotify(const QMetaMethod & signal)
{
    Q_D(KMyMoneyMVCCombo);
    if (signal != QMetaMethod::fromSignal(&KMyMoneyMVCCombo::createItem)) {
        d->m_canCreateObjects = false;
    }
}

void KMyMoneyMVCCombo::setCurrentText(const QString& txt)
{
    KComboBox::setItemText(KComboBox::currentIndex(), txt);
}

void KMyMoneyMVCCombo::setCurrentText()
{
    KComboBox::setItemText(KComboBox::currentIndex(), QString());
}


void KMyMoneyMVCCombo::focusOutEvent(QFocusEvent* e)
{
    Q_D(KMyMoneyMVCCombo);
    // when showing m_completion we'll receive a focus out event even if the focus
    // will still remain at this widget since this widget is the completion's focus proxy
    // so ignore the focus out event caused by showing a widget of type Qt::Popup
    if (e->reason() == Qt::PopupFocusReason)
        return;

    if (d->m_inFocusOutEvent) {
        KComboBox::focusOutEvent(e);
        return;
    }

    //check if the focus went to a widget in TransactionFrom, in the Register, or on a WizardPage
    if (e->reason() == Qt::MouseFocusReason) {
        QObject *w = this->parent();
        QObject *q = qApp->focusWidget()->parent();
        // KMyMoneyTagCombo is inside KTagContainer, KMyMoneyPayeeCombo isn't it
        if (w->inherits("KTagContainer"))
            w = w->parent();
        while (q && q->objectName() != "qt_scrollarea_viewport")
            q = q->parent();
        if (q != w && qApp->focusWidget()->parent() != w && qApp->focusWidget()->objectName() != "register") {
            KComboBox::focusOutEvent(e);
            return;
        }
    }

    d->m_inFocusOutEvent = true;
    if (isEditable() && !currentText().isEmpty() && e->reason() != Qt::ActiveWindowFocusReason) {
        if (d->m_canCreateObjects) {
            // in case we tab out, we make sure that if the current completion
            // contains the current text that we set the current text to
            // the full completion text but only if the completion box is visible.
            // BUG 254984 is resolved with the visibility check
            if (e->reason() != Qt::MouseFocusReason) {
                if (d->m_completer->popup() && d->m_completer->popup()->isVisible()
                        && d->m_completer->currentCompletion().contains(currentText(), Qt::CaseInsensitive)) {
                    lineEdit()->setText(d->m_completer->currentCompletion());
                }
            }

            //check if the current text is contained in the internal list, if not ask the user if want to create a new item.
            checkCurrentText();

            // else if we cannot create objects, and the current text is not
            // in the list, then we clear the text and the selection.
        } else if (!contains(currentText())) {
            clearEditText();
        }
        //this is to cover the case when you highlight an item but don't activate it with Enter
        if (currentText() != itemText(currentIndex())) {
            setCurrentIndex(findText(currentText(), Qt::MatchExactly));
            Q_EMIT activated(currentIndex());
        }
    }

    KComboBox::focusOutEvent(e);

    // force update of hint and id if there is no text in the widget
    if (isEditable() && currentText().isEmpty()) {
        QString id = d->m_id;
        d->m_id.clear();
        setCurrentIndex(-1);
        if (!id.isEmpty())
            Q_EMIT itemSelected(d->m_id);
        update();
    }

    d->m_inFocusOutEvent = false;
    // This is used only be KMyMoneyTagCombo at this time
    Q_EMIT lostFocus();
}

void KMyMoneyMVCCombo::checkCurrentText()
{
    Q_D(KMyMoneyMVCCombo);
    const auto payeeName = currentText();
    if (!contains(payeeName)) {
        QString id;
        // announce that we go into a possible dialog to create an object
        // This can be used by upstream widgets to disable filters etc.
        Q_EMIT objectCreation(true);

        Q_EMIT createItem(payeeName, id);

        // Announce that we return from object creation
        Q_EMIT objectCreation(false);

        // update the field to a possibly created object
        d->m_id = id;
        addEntry(payeeName, id);
        setCurrentTextById(id);
    }
}

void KMyMoneyMVCCombo::addEntry(const QString& newTxt, const QString& id)
{
    // find the correct position in the list
    int idx;
    for(idx = 0; idx < model()->rowCount(); ++idx) {
        const QString txt = itemText(idx);
        if (txt.compare(newTxt) > 0) {
            break;
        }
    }
    // and insert the new item
    insertItem(idx - 1, QIcon(), newTxt, id);
}

void KMyMoneyMVCCombo::setCurrentTextById(const QString& id)
{
    clearEditText();
    if (!id.isEmpty()) {
        int index = findData(QVariant(id), Qt::UserRole, Qt::MatchExactly);
        if (index > -1) {
            setCompletedText(itemText(index));
            setEditText(itemText(index));
            setCurrentIndex(index);
        }
    }
}

void KMyMoneyMVCCombo::protectItem(int id, bool protect)
{
    QStandardItemModel* standardModel = qobject_cast<QStandardItemModel*> (model());
    QStandardItem* standardItem = standardModel->item(id);
    standardItem->setSelectable(!protect);
}
