/*
 * psirosterwidget.cpp
 * Copyright (C) 2010  Yandex LLC (Michail Pishchagin)
 *
 * 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, see <https://www.gnu.org/licenses/>.
 *
 */

#include "psirosterwidget.h"

#include "contactlistdragmodel.h"
#include "contactlistitem.h"
#include "contactlistproxymodel.h"
#include "debug.h"
#ifdef MODELTEST
#include "modeltest.h"
#endif
#include "psiaccount.h"
#include "psicontact.h"
#include "psicontactlistview.h"
#include "psifilteredcontactlistview.h"
#include "psioptions.h"
#include "widgets/actionlineedit.h"
#include "widgets/iconaction.h"

#include <QKeyEvent>
#include <QLineEdit>
#include <QMessageBox>
#include <QMimeData>
#include <QSortFilterProxyModel>
#include <QStackedWidget>
#include <QVBoxLayout>

static const QString contactSortStyleOptionPath   = "options.ui.contactlist.contact-sort-style";
static const QString showOfflineOptionPath        = "options.ui.contactlist.show.offline-contacts";
static const QString showHiddenOptionPath         = "options.ui.contactlist.show.hidden-contacts-group";
static const QString showAgentsOptionPath         = "options.ui.contactlist.show.agent-contacts";
static const QString showSelfOptionPath           = "options.ui.contactlist.show.self-contact";
static const QString showStatusMessagesOptionPath = "options.ui.contactlist.status-messages.show";
static const QString allowAutoResizeOptionPath    = "options.ui.contactlist.automatically-resize-roster";
static const QString showScrollBarOptionPath      = "options.ui.contactlist.disable-scrollbar";
static const QString enableGroupsOptionPath       = "options.ui.contactlist.enable-groups";

//----------------------------------------------------------------------------
// PsiRosterFilterProxyModel
//----------------------------------------------------------------------------

class PsiRosterFilterProxyModel : public QSortFilterProxyModel {
    Q_OBJECT
public:
    PsiRosterFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
    {
        sort(0, Qt::AscendingOrder);
        setFilterCaseSensitivity(Qt::CaseInsensitive);
        setSortLocaleAware(true);
    }

protected:
    // reimplemented
    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
    {
        QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);

        QStringList data;
        if (index.isValid()) {
            // TODO: also check for vCard value
            data << index.data(Qt::DisplayRole).toString() << index.data(ContactListModel::JidRole).toString();
        }

        for (const QString &str : std::as_const(data)) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
            if (str.contains(filterRegExp()))
#else
            if (str.contains(filterRegularExpression()))
#endif
                return true;
        }

        return false;
    }

    // reimplemented
    bool lessThan(const QModelIndex &left, const QModelIndex &right) const
    {
        ContactListItem *item1 = static_cast<ContactListItem *>(left.internalPointer());
        ContactListItem *item2 = static_cast<ContactListItem *>(right.internalPointer());
        if (!item1 || !item2)
            return false;
        return item1->lessThan(item2);
    }
};

//----------------------------------------------------------------------------
// PsiRosterWidget
//----------------------------------------------------------------------------

PsiRosterWidget::PsiRosterWidget(QWidget *parent) :
    QWidget(parent), stackedWidget_(nullptr), contactListPage_(nullptr), filterPage_(nullptr),
    contactListPageView_(nullptr), filterPageView_(nullptr), contactListModel_(nullptr), filterModel_(nullptr)
{
    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);

    stackedWidget_ = new QStackedWidget(this);
    stackedWidget_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    layout->addWidget(stackedWidget_);

    // contactListPage_
    contactListPage_ = new QWidget(nullptr);
    contactListPage_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    stackedWidget_->addWidget(contactListPage_);

    QVBoxLayout *contactListPageLayout = new QVBoxLayout(contactListPage_);
    contactListPageLayout->setContentsMargins(0, 0, 0, 0);
    contactListPageView_ = new PsiContactListView(contactListPage_);
    contactListPageView_->installEventFilter(this);
    contactListPageView_->setObjectName("contactListView");
    contactListPageLayout->addWidget(contactListPageView_);

    // filterPage_
    filterPage_ = new QWidget(nullptr);
    filterPage_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    filterPage_->installEventFilter(this);
    stackedWidget_->addWidget(filterPage_);

    QVBoxLayout *filterPageLayout = new QVBoxLayout(filterPage_);
    filterPageLayout->setContentsMargins(0, 0, 0, 0);
    filterPageLayout->setSpacing(0);

    filterEdit_          = new ActionLineEdit(filterPage_);
    QAction *clearAction = new IconAction("", "psi/clearChat", tr("Clear"), 0, this);
    connect(clearAction, SIGNAL(triggered()), SLOT(clearFilterEdit()));
    filterEdit_->addAction(clearAction);
    connect(filterEdit_, SIGNAL(textChanged(const QString &)), SLOT(filterEditTextChanged(const QString &)));
    filterEdit_->installEventFilter(this);
    filterPageLayout->addWidget(filterEdit_);

    filterPageView_ = new PsiFilteredContactListView(filterPage_);
    connect(filterPageView_, SIGNAL(quitFilteringMode()), SLOT(quitFilteringMode()));
    filterPageView_->installEventFilter(this);
    filterPageLayout->addWidget(filterPageView_);
    connect(filterPageView_, &ContactListView::contactSelected, this, &PsiRosterWidget::contactPick);
}

PsiRosterWidget::~PsiRosterWidget() { }

void PsiRosterWidget::setContactList(PsiContactList *contactList)
{
    Q_ASSERT(contactList);
    Q_ASSERT(!contactList_);
    contactList_ = contactList;
    connect(PsiOptions::instance(), SIGNAL(optionChanged(const QString &)), SLOT(optionChanged(const QString &)));

    connect(contactList_, SIGNAL(showAgentsChanged(bool)), SLOT(showAgentsChanged(bool)));
    connect(contactList_, SIGNAL(showHiddenChanged(bool)), SLOT(showHiddenChanged(bool)));
    connect(contactList_, SIGNAL(showSelfChanged(bool)), SLOT(showSelfChanged(bool)));
    connect(contactList_, SIGNAL(showOfflineChanged(bool)), SLOT(showOfflineChanged(bool)));
    optionChanged(showAgentsOptionPath);
    optionChanged(showHiddenOptionPath);
    optionChanged(showSelfOptionPath);
    optionChanged(showOfflineOptionPath);
    optionChanged(contactSortStyleOptionPath);
    optionChanged(allowAutoResizeOptionPath);
    optionChanged(showScrollBarOptionPath);

    contactListModel_ = new ContactListDragModel(contactList_, this);
    contactListModel_->invalidateLayout();
    contactListModel_->setAccountsEnabled(true);
    contactListModel_->setGroupsEnabled(PsiOptions::instance()->getOption(enableGroupsOptionPath).toBool());
    // connect
#ifdef MODELTEST
    new ModelTest(contactListModel_, this);
#endif

    ContactListProxyModel *contactListProxyModel = new ContactListProxyModel(this);
    contactListProxyModel->setSourceModel(contactListModel_);
#ifdef MODELTEST
    new ModelTest(contactListProxyModel, this);
#endif

    contactListPageView_->setModel(contactListProxyModel);
}

void PsiRosterWidget::optionChanged(const QString &option)
{
    if (!contactList_)
        return;
    if (option == contactSortStyleOptionPath) {
        contactList_->setContactSortStyle(PsiOptions::instance()->getOption(contactSortStyleOptionPath).toString());
    } else if (option == showAgentsOptionPath) {
        contactList_->setShowAgents(PsiOptions::instance()->getOption(showAgentsOptionPath).toBool());
    } else if (option == showHiddenOptionPath) {
        contactList_->setShowHidden(PsiOptions::instance()->getOption(showHiddenOptionPath).toBool());
    } else if (option == showSelfOptionPath) {
        contactList_->setShowSelf(PsiOptions::instance()->getOption(showSelfOptionPath).toBool());
    } else if (option == showOfflineOptionPath) {
        contactList_->setShowOffline(PsiOptions::instance()->getOption(showOfflineOptionPath).toBool());
    } else if (option == allowAutoResizeOptionPath) {
        contactListPageView_->setAutoResizeEnabled(
            PsiOptions::instance()->getOption(allowAutoResizeOptionPath).toBool());
    } else if (option == showScrollBarOptionPath) {
        contactListPageView_->setVerticalScrollBarPolicy(
            PsiOptions::instance()->getOption(showScrollBarOptionPath).toBool() ? Qt::ScrollBarAlwaysOff
                                                                                : Qt::ScrollBarAsNeeded);
    } else if (option == enableGroupsOptionPath) {
        contactListModel_->setGroupsEnabled(PsiOptions::instance()->getOption(enableGroupsOptionPath).toBool());
    }
}

void PsiRosterWidget::showAgentsChanged(bool enabled)
{
    PsiOptions::instance()->setOption(showAgentsOptionPath, enabled);
}

void PsiRosterWidget::showHiddenChanged(bool enabled)
{
    PsiOptions::instance()->setOption(showHiddenOptionPath, enabled);
}

void PsiRosterWidget::showSelfChanged(bool enabled) { PsiOptions::instance()->setOption(showSelfOptionPath, enabled); }

void PsiRosterWidget::showOfflineChanged(bool enabled)
{
    PsiOptions::instance()->setOption(showOfflineOptionPath, enabled);
}

void PsiRosterWidget::setShowStatusMsg(bool enabled)
{
    PsiOptions::instance()->setOption(showStatusMessagesOptionPath, enabled);
}

void PsiRosterWidget::filterEditTextChanged(const QString &text)
{
    if (filterModel_)
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
        filterModel_->setFilterRegExp(QRegularExpression::escape(text));
#else
        filterModel_->setFilterRegularExpression(QRegularExpression::escape(text));
#endif
}

void PsiRosterWidget::quitFilteringMode()
{
    if (!pickContactMode_) {
        setFilterModeEnabled(false);
    }
}

void PsiRosterWidget::updateFilterMode()
{
    if (!pickContactMode_) {
        setFilterModeEnabled(!filterEdit_->text().isEmpty());
    }
}

void PsiRosterWidget::setFilterModeEnabled(bool enabled)
{
    SLOW_TIMER(100);
    bool currentlyEnabled = stackedWidget_->currentWidget() == filterPage_;
    if (enabled == currentlyEnabled)
        return;

    PsiContactListView *selectionSource      = nullptr;
    PsiContactListView *selectionDestination = nullptr;

    if (enabled) {
        Q_ASSERT(!filterModel_);
        filterModel_ = new PsiRosterFilterProxyModel(this);

        ContactListDragModel *clone = new ContactListDragModel(contactList_);
        clone->invalidateLayout();
        clone->setParent(filterModel_);

        filterModel_->setSourceModel(clone);
        filterPageView_->setModel(filterModel_);

        selectionSource      = contactListPageView_;
        selectionDestination = filterPageView_;

        stackedWidget_->setCurrentWidget(filterPage_);

        filterEdit_->selectAll();
        filterEdit_->setFocus();
    } else {
        selectionSource      = filterPageView_;
        selectionDestination = contactListPageView_;

        stackedWidget_->setCurrentWidget(contactListPage_);
        contactListPageView_->setFocus();

        delete filterModel_;
        filterModel_ = nullptr;
    }

    QMimeData *selection = selectionSource->selection();
    selectionDestination->restoreSelection(selection);
    delete selection;
}

void PsiRosterWidget::setPickContactMode(bool value)
{
    pickContactMode_ = value;
    if (value) {
        setFilterModeEnabled(true);
    }
    filterPageView_->setActivateAction(value ? ContactListView::SignalSelected : ContactListView::Activate);
}

QList<PsiContact *> PsiRosterWidget::selectedContacts() const { return {}; }

void PsiRosterWidget::clearFilterEdit()
{
    if (filterEdit_->text().isEmpty() && !pickContactMode_)
        setFilterModeEnabled(false);
    else
        filterEdit_->setText("");
}

bool PsiRosterWidget::eventFilter(QObject *obj, QEvent *e)
{
    if (e->type() == QEvent::DeferredDelete || e->type() == QEvent::Destroy) {
        return false;
    }

#if 0
    // we probably don't want this behavior in Psi
    if (e->type() == QEvent::WindowDeactivate) {
        setFilterModeEnabled(false);
    }
#endif

    if (!isActiveWindow())
        return false;

    if (e->type() == QEvent::KeyPress) {
        QKeyEvent *ke   = static_cast<QKeyEvent *>(e);
        QString    text = ke->text().trimmed();
        if (!text.isEmpty() && (obj == contactListPageView_ || obj == contactListPage_)) {
            bool correctChar = (text[0].isLetterOrNumber() || text[0].isPunct())
                && (ke->modifiers() == Qt::NoModifier || ke->modifiers() == Qt::ShiftModifier);
            if (correctChar && !contactListPageView_->textInputInProgress()) {
                setFilterModeEnabled(true);
                filterEdit_->setText(text);
                return true;
            }
        } else if (ke->key() == Qt::Key_F3 && !pickContactMode_) {
            setFilterModeEnabled(!(filterEdit_->isVisible() && filterEdit_->text().isEmpty()));
            filterEdit_->setText("");
            return true;
        }

        if (obj == filterEdit_ || obj == filterPageView_ || obj == filterPage_) {
            if (ke->key() == Qt::Key_Escape) {
                if (pickContactMode_) {
                    deleteLater();
                } else {
                    setFilterModeEnabled(false);
                }
                return true;
            }

            if (ke->key() == Qt::Key_Backspace && filterEdit_->text().isEmpty() && !pickContactMode_) {
                setFilterModeEnabled(false);
                return true;
            }

            if (ke->key() == Qt::Key_End) {
                filterEdit_->setCursorPosition(filterEdit_->text().length());
                return true;
            }

            if (ke->key() == Qt::Key_Home) {
                filterEdit_->setCursorPosition(0);
                return true;
            }

            if (filterPageView_->handleKeyPressEvent(ke)) {
                return true;
            }
        }
    }

    return QObject::eventFilter(obj, e);
}

#include "psirosterwidget.moc"
