// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2014 Abhinav Gangwar <abhgang@gmail.com>
//

// Self
#include "ClickOnThat.h"

// Qt
#include <QDir>
#include <QList>
#include <QRandomGenerator>
#include <QStringList>
#include <QTime>
#include <QVariant>

// Marble
#include <marble/GeoDataTreeModel.h>
#include <marble/MarbleDirs.h>
#include <marble/MarbleMap.h>
#include <marble/MarbleModel.h>
#include <marble/MarblePlacemarkModel.h>
#include <marble/MarbleWidget.h>

#include <marble/GeoDataDocument.h>
#include <marble/GeoDataIconStyle.h>
#include <marble/GeoDataLinearRing.h>
#include <marble/GeoDataMultiGeometry.h>
#include <marble/GeoDataPlacemark.h>
#include <marble/GeoDataPoint.h>
#include <marble/GeoDataPolygon.h>
#include <marble/GeoDataStyle.h>
#include <marble/GeoDataStyleMap.h>

namespace Marble
{
class ClickOnThatPrivate
{
public:
    ClickOnThatPrivate(MarbleWidget *marbleWidget)
        : m_marbleWidget(marbleWidget)
        , m_parent(nullptr)
        , m_correctAnswerPlacemark(nullptr)
        , m_selectPinDocument(nullptr)
        , m_countryNames(nullptr)
        , m_countryBoundaries(nullptr)
    {
        m_continentsAndOceans << QStringLiteral("Asia") << QStringLiteral("Africa") << QStringLiteral("North America") << QStringLiteral("South America")
                              << QStringLiteral("Antarctica") << QStringLiteral("Europe") << QStringLiteral("Australia") << QStringLiteral("Arctic Ocean")
                              << QStringLiteral("Indian Ocean") << QStringLiteral("North Atlantic Ocean") << QStringLiteral("North Pacific Ocean")
                              << QStringLiteral("South Pacific Ocean") << QStringLiteral("South Atlantic Ocean") << QStringLiteral("Southern Ocean");
    }

    ~ClickOnThatPrivate()
    {
        delete m_selectPinDocument;
    }

    MarbleWidget *m_marbleWidget;
    ClickOnThat *m_parent;

    /**
     * Store the GeoDataPlacemark also
     * for the correct answer so that
     * we can highlight and zoom in
     * ( to fit in the current view port )
     * to this placemark when user
     * chooses to view the right answer.
     */
    GeoDataPlacemark *m_correctAnswerPlacemark;
    GeoDataCoordinates m_correctAnswer;

    /**
     * @p m_selectPinDocument shows a pin
     * on map indicating whether the user
     * has clicked on right country
     */
    GeoDataDocument *m_selectPinDocument;

    /**
     * Document to store point placemarks which
     * have country names ( from file "boundaryplacemarks.cache" )
     */
    GeoDataDocument *m_countryNames;

    /**
     * Document which have placemarks whose geometry
     * specifies the boundaries of a country
     * (from file "ne_50m_admin_0_countries.pn2" )
     */
    GeoDataDocument *m_countryBoundaries;

    /*
     * If the placemark used for posting question
     * represent a continent, the user needs to click exactly on
     * a particular country whose geometry contains this point
     * placemark on map ( Ideally it should be any country
     * within that continent ). Also, oceans are point placemark, so
     * user needs to click exactly on same point this placemark
     * represents ( Ideally it should anywhere within the ocean territory ).
     * So, to avoid such placemark we will use this list.
     */
    QStringList m_continentsAndOceans;
};

ClickOnThat::ClickOnThat(MarbleWidget *marbleWidget)
    : QObject()
    , d(new ClickOnThatPrivate(marbleWidget))
{
    d->m_parent = this;
    connect(this,
            SIGNAL(announceHighlight(qreal, qreal, GeoDataCoordinates::Unit)),
            d->m_marbleWidget,
            SIGNAL(highlightedPlacemarksChanged(qreal, qreal, GeoDataCoordinates::Unit)));
}

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

void ClickOnThat::disablePinDocument()
{
    if (d->m_selectPinDocument) {
        d->m_selectPinDocument->setVisible(false);
        d->m_marbleWidget->model()->treeModel()->updateFeature(d->m_selectPinDocument);
    }
}

void ClickOnThat::initiateGame()
{
    /**
     * First remove the GeoDataDocument, which displays
     * country names, from map.
     */
    if (!d->m_countryNames) {
        const GeoDataTreeModel *const treeModel = d->m_marbleWidget->model()->treeModel();
        for (int i = 0; i < treeModel->rowCount(); ++i) {
            QVariant const data = treeModel->data(treeModel->index(i, 0), MarblePlacemarkModel::ObjectPointerRole);
            GeoDataObject *object = qvariant_cast<GeoDataObject *>(data);
            Q_ASSERT_X(object, "CountryByShape::initiateGame", "failed to get valid data from treeModel for GeoDataObject");
            if (auto doc = geodata_cast<GeoDataDocument>(object)) {
                QFileInfo fileInfo(doc->fileName());
                if (fileInfo.fileName() == QLatin1StringView("boundaryplacemarks.cache")) {
                    d->m_countryNames = doc;
                    break;
                }
            }
        }
    }

    if (!d->m_countryBoundaries) {
        const GeoDataTreeModel *const treeModel = d->m_marbleWidget->model()->treeModel();
        for (int i = 0; i < treeModel->rowCount(); ++i) {
            QVariant const data = treeModel->data(treeModel->index(i, 0), MarblePlacemarkModel::ObjectPointerRole);
            GeoDataObject *object = qvariant_cast<GeoDataObject *>(data);
            Q_ASSERT_X(object, "MainWindow::initiateGame", "failed to get valid data from treeModel for GeoDataObject");
            if (auto doc = geodata_cast<GeoDataDocument>(object)) {
                QFileInfo fileInfo(doc->fileName());
                if (fileInfo.fileName() == QLatin1StringView("ne_50m_admin_0_countries.pn2")) {
                    d->m_countryBoundaries = doc;
                    break;
                }
            }
        }
    }

    if (!d->m_selectPinDocument) {
        d->m_selectPinDocument = new GeoDataDocument;
        GeoDataPlacemark *pinPlacemark = new GeoDataPlacemark;

        GeoDataStyle::Ptr pinStyle(new GeoDataStyle);
        pinStyle->setId(QStringLiteral("answer"));
        GeoDataIconStyle iconStyle;
        iconStyle.setIconPath(MarbleDirs::path(QStringLiteral("bitmaps/target.png")));
        pinStyle->setIconStyle(iconStyle);

        GeoDataStyleMap styleMap;
        styleMap.setId(QStringLiteral("default-map"));
        styleMap.insert(QStringLiteral("normal"), QLatin1Char('#') + pinStyle->id());

        d->m_selectPinDocument->addStyle(pinStyle);
        d->m_selectPinDocument->addStyleMap(styleMap);

        d->m_selectPinDocument->append(pinPlacemark);
        pinPlacemark->setStyleUrl(QLatin1Char('#') + styleMap.id());
        d->m_selectPinDocument->setVisible(false);

        // Add this document to treeModel
        d->m_marbleWidget->model()->treeModel()->addDocument(d->m_selectPinDocument);
    }

    d->m_marbleWidget->setHighlightEnabled(true);
    d->m_marbleWidget->centerOn(23.0, 42.0);
    d->m_marbleWidget->setDistance(7500);
    connect(d->m_marbleWidget,
            SIGNAL(highlightedPlacemarksChanged(qreal, qreal, GeoDataCoordinates::Unit)),
            this,
            SLOT(determineResult(qreal, qreal, GeoDataCoordinates::Unit)));

    if (d->m_countryBoundaries && d->m_countryNames) {
        d->m_countryNames->setVisible(false);
        d->m_marbleWidget->model()->treeModel()->updateFeature(d->m_countryNames);
        emit gameInitialized();
    }
}

void ClickOnThat::postQuestion(QObject *gameObject)
{
    /**
     * Find a random placemark
     */
    Q_ASSERT_X(d->m_countryNames, "ClickOnThat::postQuestion", "CountryByShapePrivate::m_countryNames is NULL");
    QList<GeoDataPlacemark *> countryPlacemarks = d->m_countryNames->placemarkList();

    uint randomSeed = uint(QTime::currentTime().msec());
    QRandomGenerator::global()->seed(randomSeed);

    GeoDataPlacemark *placemark = nullptr;
    GeoDataPoint *point = nullptr;
    bool found = false;
    while (!found) {
        placemark = countryPlacemarks[QRandomGenerator::global()->generate() % countryPlacemarks.size()];
        if (!d->m_continentsAndOceans.contains(placemark->name(), Qt::CaseSensitive)) {
            found = true;
            point = dynamic_cast<GeoDataPoint *>(placemark->geometry());
        }
    }
    if (point) {
        d->m_correctAnswerPlacemark = placemark;
        d->m_correctAnswer = point->coordinates();
        if (gameObject) {
            QMetaObject::invokeMethod(gameObject, "clickOnThatQuestion", Q_ARG(QVariant, QVariant(placemark->name())));
        }
    }
}

void ClickOnThat::updateSelectPin(bool result, const GeoDataCoordinates &clickedPoint)
{
    QDir dir;
    QString iconPath = dir.absolutePath();
    if (result) {
        // iconPath = MarbleDirs::path("bitmaps/MapTackRoundHeadGreen.png");
        iconPath += QLatin1StringView("/MapTackRoundHeadGreen.png");
    } else {
        iconPath += QLatin1StringView("/MapTackRoundHeadRed.png");
    }

    GeoDataStyle::Ptr style = d->m_selectPinDocument->style(QStringLiteral("answer"));
    style->iconStyle().setIconPath(iconPath);
    d->m_selectPinDocument->addStyle(style);

    QList<GeoDataPlacemark *> placemarkList = d->m_selectPinDocument->placemarkList();
    if (placemarkList.size() > 0) {
        placemarkList[0]->setCoordinate(clickedPoint);
    }

    if (!d->m_selectPinDocument->isVisible()) {
        d->m_selectPinDocument->setVisible(true);
    }
    d->m_marbleWidget->model()->treeModel()->updateFeature(d->m_selectPinDocument);
}

void ClickOnThat::determineResult(qreal lon, qreal lat, GeoDataCoordinates::Unit unit)
{
    GeoDataCoordinates coord(lon, lat, 0, unit);

    Q_ASSERT_X(d->m_countryNames, "ClickOnThat::determineResult", "CountryByShapePrivate::m_countryBoundaries is NULL");
    QList<GeoDataFeature *>::Iterator i = d->m_countryBoundaries->begin();
    QList<GeoDataFeature *>::Iterator const end = d->m_countryBoundaries->end();

    bool foundStandardPoint = false;
    bool foundClickedPoint = false;
    for (; i != end; ++i) {
        GeoDataPlacemark *country = static_cast<GeoDataPlacemark *>(*i);

        GeoDataPolygon *polygon = dynamic_cast<GeoDataPolygon *>(country->geometry());
        GeoDataLinearRing *linearring = dynamic_cast<GeoDataLinearRing *>(country->geometry());
        GeoDataMultiGeometry *multigeom = dynamic_cast<GeoDataMultiGeometry *>(country->geometry());

        foundClickedPoint = false;
        foundStandardPoint = false;
        if (polygon && polygon->contains(coord) && polygon->contains(d->m_correctAnswer)) {
            foundClickedPoint = true;
            foundStandardPoint = true;
            d->m_correctAnswerPlacemark = country;
            break;
        }
        if (linearring && linearring->contains(coord) && linearring->contains(d->m_correctAnswer)) {
            foundClickedPoint = true;
            foundStandardPoint = true;
            d->m_correctAnswerPlacemark = country;
            break;
        }
        if (multigeom) {
            QList<GeoDataGeometry *>::Iterator iter = multigeom->begin();
            QList<GeoDataGeometry *>::Iterator const end = multigeom->end();

            for (; iter != end; ++iter) {
                GeoDataPolygon *poly = dynamic_cast<GeoDataPolygon *>(*iter);
                if (poly && poly->contains(coord)) {
                    foundClickedPoint = true;
                }
                if (poly && poly->contains(d->m_correctAnswer)) {
                    foundStandardPoint = true;
                    d->m_correctAnswerPlacemark = country;
                }
                if (foundClickedPoint && foundStandardPoint) {
                    break;
                }
            }
        }
        if (foundClickedPoint && foundStandardPoint) {
            break;
        }
    }

    if (foundClickedPoint && foundStandardPoint) {
        updateSelectPin(true, coord);
        emit updateResult(true);
    } else {
        updateSelectPin(false, coord);
        emit updateResult(false);
    }
}

void ClickOnThat::highlightCorrectAnswer()
{
    disconnect(d->m_marbleWidget,
               SIGNAL(highlightedPlacemarksChanged(qreal, qreal, GeoDataCoordinates::Unit)),
               this,
               SLOT(determineResult(qreal, qreal, GeoDataCoordinates::Unit)));

    emit announceHighlight(d->m_correctAnswer.longitude(GeoDataCoordinates::Degree),
                           d->m_correctAnswer.latitude(GeoDataCoordinates::Degree),
                           GeoDataCoordinates::Degree);
    updateSelectPin(true, d->m_correctAnswer);

    /**
     * Zoom to highlighted placemark
     * so that it fits the current
     * view port
     */
    d->m_marbleWidget->centerOn(*(d->m_correctAnswerPlacemark), true);

    connect(d->m_marbleWidget,
            SIGNAL(highlightedPlacemarksChanged(qreal, qreal, GeoDataCoordinates::Unit)),
            this,
            SLOT(determineResult(qreal, qreal, GeoDataCoordinates::Unit)));
}

} // namespace Marble

#include "moc_ClickOnThat.cpp"
