// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2012 Dennis Nienhüser <nienhueser@kde.org>
//

#include "SearchWidget.h"

#include "BranchFilterProxyModel.h"
#include "GeoDataDocument.h"
#include "GeoDataLatLonAltBox.h"
#include "GeoDataPlacemark.h"
#include "GeoDataTreeModel.h"
#include "MarbleDebug.h"
#include "MarbleModel.h"
#include "MarblePlacemarkModel.h"
#include "MarbleWidget.h"
#include "SearchInputWidget.h"
#include "SearchRunnerManager.h"
#include "ViewportParams.h"

#include <QElapsedTimer>
#include <QListView>
#include <QVBoxLayout>

namespace Marble
{

class SearchWidgetPrivate
{
public:
    SearchRunnerManager *m_runnerManager;
    SearchInputWidget *m_searchField;
    QListView *m_searchResultView;
    MarbleWidget *m_widget;
    BranchFilterProxyModel m_branchfilter;
    QSortFilterProxyModel m_sortproxy;
    GeoDataDocument *m_document;
    QString m_planetId;

    SearchWidgetPrivate();
    void setSearchResult(const QList<GeoDataPlacemark *> &);
    void search(const QString &searchTerm, SearchMode searchMode);
    void clearSearch();
    void centerMapOn(const QModelIndex &index);
    void handlePlanetChange();
};

SearchWidgetPrivate::SearchWidgetPrivate()
    : m_runnerManager(nullptr)
    , m_searchField(nullptr)
    , m_searchResultView(nullptr)
    , m_widget(nullptr)
    , m_branchfilter()
    , m_sortproxy()
    , m_document(new GeoDataDocument)
{
    m_document->setName(QObject::tr("Search Results"));
}

void SearchWidgetPrivate::setSearchResult(const QList<GeoDataPlacemark *> &locations)
{
    if (locations.isEmpty()) {
        return;
    }

    QElapsedTimer timer;
    timer.start();

    // fill the local document with results
    m_widget->model()->placemarkSelectionModel()->clear();
    GeoDataTreeModel *treeModel = m_widget->model()->treeModel();
    treeModel->removeDocument(m_document);
    m_document->clear();
    m_document->setName(QObject::tr("Search for '%1'").arg(m_searchField->text()));
    for (GeoDataPlacemark *placemark : locations) {
        m_document->append(new GeoDataPlacemark(*placemark));
    }
    treeModel->addDocument(m_document);
    m_branchfilter.setBranchIndex(treeModel, treeModel->index(m_document));
    m_searchResultView->setRootIndex(m_sortproxy.mapFromSource(m_branchfilter.mapFromSource(treeModel->index(m_document))));
    m_widget->centerOn(m_document->latLonAltBox());
    mDebug() << " Time elapsed:" << timer.elapsed() << " ms";
}

SearchWidget::SearchWidget(QWidget *parent, Qt::WindowFlags flags)
    : QWidget(parent, flags)
    , d(new SearchWidgetPrivate)
{
    d->m_sortproxy.setSortRole(MarblePlacemarkModel::PopularityIndexRole);
    d->m_sortproxy.sort(0, Qt::AscendingOrder);
    d->m_sortproxy.setDynamicSortFilter(true);
    d->m_sortproxy.setSourceModel(&d->m_branchfilter);

    d->m_searchField = new SearchInputWidget(this);
    setFocusProxy(d->m_searchField);
    connect(d->m_searchField, SIGNAL(search(QString, SearchMode)), this, SLOT(search(QString, SearchMode)));

    d->m_searchResultView = new QListView(this);
    d->m_searchResultView->setModel(&d->m_sortproxy);
    d->m_searchResultView->setMinimumSize(0, 0);
    connect(d->m_searchResultView, SIGNAL(activated(QModelIndex)), this, SLOT(centerMapOn(QModelIndex)));

    auto layout = new QVBoxLayout(this);
    layout->addWidget(d->m_searchField);
    layout->addWidget(d->m_searchResultView);
    layout->setContentsMargins({});
}

SearchWidget::~SearchWidget()
{
    delete d;
}

void SearchWidget::setMarbleWidget(MarbleWidget *widget)
{
    if (d->m_widget) {
        return;
    }

    d->m_widget = widget;

    d->m_planetId = widget->model()->planetId();
    connect(widget->model(), SIGNAL(themeChanged(QString)), this, SLOT(handlePlanetChange()));

    d->m_searchField->setCompletionModel(widget->model()->placemarkModel());
    connect(d->m_searchField, SIGNAL(centerOn(GeoDataCoordinates)), widget, SLOT(centerOn(GeoDataCoordinates)));

    d->m_runnerManager = new SearchRunnerManager(widget->model(), this);
    connect(d->m_runnerManager, SIGNAL(searchResultChanged(QList<GeoDataPlacemark *>)), this, SLOT(setSearchResult(QList<GeoDataPlacemark *>)));
    connect(d->m_runnerManager, SIGNAL(searchFinished(QString)), d->m_searchField, SLOT(disableSearchAnimation()));

    GeoDataTreeModel *treeModel = d->m_widget->model()->treeModel();
    treeModel->addDocument(d->m_document);

    d->m_branchfilter.setSourceModel(treeModel);
    d->m_branchfilter.setBranchIndex(treeModel, treeModel->index(d->m_document));

    d->m_searchResultView->setRootIndex(d->m_sortproxy.mapFromSource(d->m_branchfilter.mapFromSource(treeModel->index(d->m_document))));
}

void SearchWidgetPrivate::search(const QString &searchTerm, SearchMode searchMode)
{
    if (searchTerm.isEmpty()) {
        clearSearch();
    } else {
        if (searchMode == AreaSearch) {
            m_runnerManager->findPlacemarks(searchTerm, m_widget->viewport()->viewLatLonAltBox());
        } else {
            m_runnerManager->findPlacemarks(searchTerm);
        }
    }
}

void SearchWidgetPrivate::clearSearch()
{
    m_widget->model()->placemarkSelectionModel()->clear();

    // clear the local document
    GeoDataTreeModel *treeModel = m_widget->model()->treeModel();
    treeModel->removeDocument(m_document);
    m_document->clear();
    treeModel->addDocument(m_document);
    m_branchfilter.setBranchIndex(treeModel, treeModel->index(m_document));
    m_searchResultView->setRootIndex(m_sortproxy.mapFromSource(m_branchfilter.mapFromSource(treeModel->index(m_document))));

    // clear cached search results
    m_runnerManager->findPlacemarks(QString());
}

void SearchWidgetPrivate::centerMapOn(const QModelIndex &index)
{
    if (!index.isValid()) {
        return;
    }
    auto object = index.model()->data(index, MarblePlacemarkModel::ObjectPointerRole).value<GeoDataObject *>();
    auto placemark = dynamic_cast<GeoDataPlacemark *>(object);
    if (placemark) {
        m_widget->centerOn(*placemark, true);
        m_widget->model()->placemarkSelectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
    }
}

void SearchWidgetPrivate::handlePlanetChange()
{
    const QString newPlanetId = m_widget->model()->planetId();

    if (newPlanetId == m_planetId) {
        return;
    }

    m_planetId = newPlanetId;
    clearSearch();
}

}

#include "moc_SearchWidget.cpp"
