/* ============================================================
 *
 * This file is a part of digiKam project
 * https://www.digikam.org
 *
 * Date        : 2023-05-15
 * Description : geolocation engine based on Marble.
 *               (c) 2007-2022 Marble Team
 *               https://invent.kde.org/education/marble/-/raw/master/data/credits_authors.html
 *
 * SPDX-FileCopyrightText: 2023-2026 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * ============================================================ */

#include "GeoDataTreeModel.h"

// C++includes

#include <algorithm>

// Qt includes

#include <QBrush>
#include <QModelIndex>
#include <QList>
#include <QItemSelectionModel>

// KDE includes

#include <klocalizedstring.h>

// Local includes

#include "GeoDataObject.h"
#include "GeoDataDocument.h"
#include "GeoDataContainer.h"
#include "GeoDataExtendedData.h"
#include "GeoDataFolder.h"
#include "GeoDataPlacemark.h"
#include "GeoDataPoint.h"
#include "GeoDataPolygon.h"
#include "GeoDataLinearRing.h"
#include "GeoDataLookAt.h"
#include "GeoDataMultiGeometry.h"
#include "GeoDataPlaylist.h"
#include "GeoDataTour.h"
#include "GeoDataWait.h"
#include "GeoDataFlyTo.h"
#include "GeoDataCamera.h"
#include "GeoDataStyle.h"
#include "GeoDataIconStyle.h"
#include "GeoDataListStyle.h"
#include "FileManager.h"
#include "MarblePlacemarkModel.h"
#include "digikam_debug.h"

namespace Marble
{

class Q_DECL_HIDDEN GeoDataTreeModel::Private
{
public:

    Private(QAbstractItemModel* model);
    ~Private();

public:

    static void checkParenting(GeoDataObject* object);

public:

    GeoDataDocument*        m_rootDocument      = nullptr;
    bool                    m_ownsRootDocument  = true;
    QItemSelectionModel     m_selectionModel;
    QHash<int, QByteArray>  m_roleNames;
};

GeoDataTreeModel::Private::Private(QAbstractItemModel* model)
    : m_rootDocument    (new GeoDataDocument),
      m_ownsRootDocument(true),
      m_selectionModel  (model)
{
    m_roleNames[MarblePlacemarkModel::DescriptionRole]      = "description";
    m_roleNames[MarblePlacemarkModel::IconPathRole]         = "iconPath";
    m_roleNames[MarblePlacemarkModel::PopularityIndexRole]  = "zoomLevel";
    m_roleNames[MarblePlacemarkModel::VisualCategoryRole]   = "visualCategory";
    m_roleNames[MarblePlacemarkModel::AreaRole]             = "area";
    m_roleNames[MarblePlacemarkModel::PopulationRole]       = "population";
    m_roleNames[MarblePlacemarkModel::CountryCodeRole]      = "countryCode";
    m_roleNames[MarblePlacemarkModel::StateRole]            = "state";
    m_roleNames[MarblePlacemarkModel::PopularityRole]       = "popularity";
    m_roleNames[MarblePlacemarkModel::GeoTypeRole]          = "role";
    m_roleNames[MarblePlacemarkModel::CoordinateRole]       = "coordinate";
    m_roleNames[MarblePlacemarkModel::StyleRole]            = "style";
    m_roleNames[MarblePlacemarkModel::GmtRole]              = "gmt";
    m_roleNames[MarblePlacemarkModel::DstRole]              = "dst";
    m_roleNames[MarblePlacemarkModel::GeometryRole]         = "geometry";
    m_roleNames[MarblePlacemarkModel::ObjectPointerRole]    = "objectPointer";
    m_roleNames[MarblePlacemarkModel::LongitudeRole]        = "longitude";
    m_roleNames[MarblePlacemarkModel::LatitudeRole]         = "latitude";
}

GeoDataTreeModel::Private::~Private()
{
    if (m_ownsRootDocument)
    {
        delete m_rootDocument;
    }
}

void GeoDataTreeModel::Private::checkParenting(GeoDataObject* object)
{
    if (const auto container = dynamic_cast<const GeoDataContainer*>(object))
    {
        for (GeoDataFeature* child : container->featureList())
        {
            if (child->parent() != container)
            {
                qCWarning(DIGIKAM_GEOENGINE_LOG) << "Parenting mismatch for " << child->name();

                Q_ASSERT(0);
            }
        }
    }
}

GeoDataTreeModel::GeoDataTreeModel(QObject* parent)
    : QAbstractItemModel(parent),
      d                 (new Private(this))
{
    auto const roleNames = QAbstractItemModel::roleNames();

    for (auto iter = roleNames.constBegin() ; iter != roleNames.constEnd() ; ++iter)
    {
        d->m_roleNames[iter.key()] = iter.value();
    }
}

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

int GeoDataTreeModel::rowCount(const QModelIndex& parent) const
{
    //qCDebug(DIGIKAM_GEOENGINE_LOG) << "rowCount";

    const GeoDataObject* parentItem;

    if (parent.column() > 0)
    {
        //qCDebug(DIGIKAM_GEOENGINE_LOG) << "rowCount bad column";

        return 0;
    }

    if (!parent.isValid())
    {
        //qCDebug(DIGIKAM_GEOENGINE_LOG) << "rowCount root parent";

        parentItem = d->m_rootDocument;
    }
    else
    {
        parentItem = static_cast<const GeoDataObject*>(parent.internalPointer());
    }

    if (!parentItem)
    {
        //qCDebug(DIGIKAM_GEOENGINE_LOG) << "rowCount bad parent";

        return 0;
    }

    if (const GeoDataContainer* container = dynamic_cast<const GeoDataContainer*>(parentItem))
    {
        //qCDebug(DIGIKAM_GEOENGINE_LOG) << "rowCount " << type << "(" << parentItem << ") =" << container->size();

        return container->size();

        //} else { qCDebug(DIGIKAM_GEOENGINE_LOG) << "rowCount bad container " << container;
    }

    // cppcheck-suppress constVariablePointer
    if (const auto placemark = geodata_cast<GeoDataPlacemark>(parentItem))
    {
        if (geodata_cast<GeoDataMultiGeometry>(placemark->geometry()))
        {
            //qCDebug(DIGIKAM_GEOENGINE_LOG) << "rowCount " << type << "(" << parentItem << ") = 1";

            return 1;
        }
    }

    // cppcheck-suppress constVariablePointer
    if (const auto geometry = geodata_cast<GeoDataMultiGeometry>(parentItem))
    {
        //qCDebug(DIGIKAM_GEOENGINE_LOG) << "rowCount " << parent << " " << type << " " << geometry->size();

        return geometry->size();

        //} else { qCDebug(DIGIKAM_GEOENGINE_LOG) << "rowCount bad geometry " << geometry;
    }

    // cppcheck-suppress constVariablePointer
    if (const auto tour = geodata_cast<GeoDataTour>(parentItem))
    {
        const GeoDataPlaylist* playlist = tour->playlist();

        if (playlist)
        {
            //qCDebug(DIGIKAM_GEOENGINE_LOG) << "rowCount " << parent << " Playlist " << 1;

            return 1;
        }
    }

    // cppcheck-suppress constVariablePointer
    if (const auto playlist = geodata_cast<GeoDataPlaylist>(parentItem))
    {
        //qCDebug(DIGIKAM_GEOENGINE_LOG) << "rowCount " << parent << " Playlist " << playlist->size();

        return playlist->size();
    }

    //qCDebug(DIGIKAM_GEOENGINE_LOG) << "rowcount end";

    return 0;
    //return parentItem->childCount();
}

QVariant GeoDataTreeModel::headerData(int section, Qt::Orientation orientation,
                                      int role) const
{
    if ((role == Qt::DisplayRole) && (orientation == Qt::Horizontal))
    {
        switch (section)
        {
            case 0:
            {
                return i18n("Name");
            }

            case 1:
            {
                return i18n("Type");
            }

            case 2:
            {
                return i18n("Popularity");
            }

            case 3:
            {
                return i18nc("Popularity index", "PopIndex");
            }
        }
    }

    return QVariant();
}

QHash<int, QByteArray> GeoDataTreeModel::roleNames() const
{
    return d->m_roleNames;
}

QVariant GeoDataTreeModel::data(const QModelIndex& index, int role) const
{
    //qCDebug(DIGIKAM_GEOENGINE_LOG) << "data";

    if (!index.isValid())
    {
        return QVariant();
    }

    GeoDataObject* object = static_cast<GeoDataObject*>(index.internalPointer());

    if (role == Qt::DisplayRole)
    {
        // cppcheck-suppress constVariablePointer
        if (const auto placemark = geodata_cast<GeoDataPlacemark>(object))
        {
            if (index.column() == 0)
            {
                if (placemark->countryCode().isEmpty())
                {
                    return QVariant(placemark->name());
                }
                else
                {
                    return QVariant(
                                    placemark->name() +
                                    QLatin1String(" (") + placemark->countryCode() + QLatin1Char(')')
                                   );
                }
            }
            else if (index.column() == 1)
            {
                return QVariant(QString::fromUtf8(placemark->nodeType()));
            }
            else if (index.column() == 2)
            {
                return QVariant(placemark->popularity());
            }
            else if (index.column() == 3)
            {
                return QVariant(placemark->zoomLevel());
            }
        }

        // cppcheck-suppress constVariablePointer
        if (const auto feature = dynamic_cast<const GeoDataFeature*>(object))
        {
            if      (index.column() == 0)
            {
                return QVariant(feature->name());
            }
            else if (index.column() == 1)
            {
                return QVariant(QString::fromUtf8(feature->nodeType()));
            }
        }

        const GeoDataGeometry* geometry = dynamic_cast<GeoDataGeometry*>(object);

        if (geometry && (index.column() == 1))
        {
            return QVariant(QString::fromUtf8(geometry->nodeType()));
        }

        const GeoDataPlaylist* playlist = geodata_cast<GeoDataPlaylist>(object);

        if (playlist && (index.column() == 0))
        {
            return i18n("Playlist");
        }

        if (object && (index.column() == 1))
        {
            return QVariant(QString::fromUtf8(object->nodeType()));
        }
    }
    else if (
                (role == Qt::CheckStateRole)
             && (index.column() == 0)
            )
    {
        if (const auto feature = geodata_cast<GeoDataPlacemark>(object))
        {
            // cppcheck-suppress constVariablePointer
            if (const auto folder = geodata_cast<GeoDataFolder>(feature->parent()))
            {
                if (
                       (folder->style()->listStyle().listItemType() == GeoDataListStyle::RadioFolder)
                    || (folder->style()->listStyle().listItemType() == GeoDataListStyle::CheckOffOnly)
                   )
                {
                    if (feature->isVisible())
                    {
                        return QVariant(Qt::Checked);
                    }
                    else
                    {
                        return QVariant(Qt::Unchecked);
                    }
                }
            }

            if (feature->isGloballyVisible())
            {
                return QVariant(Qt::Checked);
            }

            if (feature->isVisible())
            {
                return QVariant(Qt::PartiallyChecked);
            }

            return QVariant(Qt::Unchecked);
        }
        // cppcheck-suppress constVariablePointer
        else if (const auto feature = dynamic_cast<GeoDataContainer*>(object))
        {
            if (auto folder = geodata_cast<GeoDataFolder>(object))
            {
                if (folder->style()->listStyle().listItemType() == GeoDataListStyle::RadioFolder)
                {
                    bool anyVisible                      = false;
                    QVector<GeoDataFeature*>::Iterator i = folder->begin();

                    for ( ; i < folder->end() ; ++i)
                    {
                        if ((*i)->isVisible())
                        {
                            anyVisible = true;
                            break;
                        }
                    }

                    if (anyVisible)
                    {
                        return QVariant(Qt::PartiallyChecked);
                    }
                    else
                    {
                        return QVariant(Qt::Unchecked);
                    }
                }
                else if (folder->style()->listStyle().listItemType() == GeoDataListStyle::CheckOffOnly)
                {
                    QVector<GeoDataFeature*>::Iterator i = folder->begin();
                    bool anyVisible                      = false;
                    bool allVisible                      = true;

                    for ( ; i < folder->end() ; ++i)
                    {
                        if ((*i)->isVisible())
                        {
                            anyVisible = true;
                        }
                        else
                        {
                            allVisible = false;
                        }
                    }

                    if      (allVisible)
                    {
                        return QVariant(Qt::Checked);
                    }
                    else if (anyVisible)
                    {
                        return QVariant(Qt::PartiallyChecked);
                    }
                    else
                    {
                        return QVariant(Qt::Unchecked);
                    }
                }
            }

            if      (feature->isGloballyVisible())
            {
                return QVariant(Qt::Checked);
            }
            else if (feature->isVisible())
            {
                return QVariant(Qt::PartiallyChecked);
            }
            else
            {
                return QVariant(Qt::Unchecked);
            }
        }
    }
    else if (
                (role == Qt::DecorationRole)
             && (index.column() == 0)
            )
    {
        // cppcheck-suppress constVariablePointer
        if (const auto feature = dynamic_cast<const GeoDataFeature*>(object))
        {
            if (feature->style()->iconStyle().icon().isNull())
            {
                return QImage();
            }

            return QVariant(feature->style()->iconStyle().icon().scaled(QSize(16, 16), Qt::KeepAspectRatio, Qt::SmoothTransformation));
        }
    }
    else if (
                (role == Qt::ToolTipRole)
             && (index.column() == 0)
            )
    {
        // cppcheck-suppress constVariablePointer
        if (const auto feature = dynamic_cast<const GeoDataFeature*>(object))
        {
            return QVariant(feature->description());
        }
    }
    else if (role == MarblePlacemarkModel::ObjectPointerRole)
    {
        return QVariant::fromValue(object);
    }
    else if (role == MarblePlacemarkModel::PopularityIndexRole)
    {
        // cppcheck-suppress constVariablePointer
        if (const auto placemark = geodata_cast<GeoDataPlacemark>(object))
        {
            return QVariant(placemark->zoomLevel());
        }
    }
    else if (role == MarblePlacemarkModel::PopularityRole)
    {
        // cppcheck-suppress constVariablePointer
        if (const auto placemark = geodata_cast<GeoDataPlacemark>(object))
        {
            return QVariant(placemark->popularity());
        }
    }
    else if (role == MarblePlacemarkModel::CoordinateRole)
    {
        // cppcheck-suppress constVariablePointer
        if      (const auto placemark = geodata_cast<GeoDataPlacemark>(object))
        {
            return QVariant::fromValue(placemark->coordinate());
        }
        else if (const auto flyTo = geodata_cast<GeoDataFlyTo>(object))
        {
            // cppcheck-suppress constVariablePointer
            if      (const auto camera = geodata_cast<GeoDataCamera>(flyTo->view()))
            {
                return QVariant::fromValue<GeoDataCoordinates>(camera->coordinates());
            }
            else if (const auto lookAt = (flyTo->view() ? geodata_cast<GeoDataLookAt>(flyTo->view()) : nullptr))
            {
                return QVariant::fromValue<GeoDataCoordinates>(lookAt->coordinates());
            }
        }
    }
    else if (role == Qt::BackgroundRole)
    {
        // cppcheck-suppress constVariablePointer
        if (const auto placemark = geodata_cast<GeoDataPlacemark>(object))
        {
            if (const GeoDataContainer* container = dynamic_cast<const GeoDataContainer*>(placemark->parent()))
            {
                return container->customStyle() ? QVariant(QBrush(container->customStyle()->listStyle().backgroundColor())) : QVariant();
            }
        }
    }
    else if (role == MarblePlacemarkModel::IconPathRole)
    {
        // cppcheck-suppress constVariablePointer
        if (const auto placemark = geodata_cast<GeoDataPlacemark>(object))
        {
            return placemark->style()->iconStyle().iconPath();
        }
    }

    return QVariant();
}

QModelIndex GeoDataTreeModel::index(int row, int column, const QModelIndex& parent)             const
{
    //qCDebug(DIGIKAM_GEOENGINE_LOG) << "index";

    if (!hasIndex(row, column, parent))
    {
        //qCDebug(DIGIKAM_GEOENGINE_LOG) << "index bad index";

        return QModelIndex();
    }

    GeoDataObject* parentItem;

    if (!parent.isValid())
    {
        parentItem = d->m_rootDocument;
    }
    else
    {
        parentItem = static_cast<GeoDataObject*>(parent.internalPointer());
    }

    if (!parentItem)
    {
        //qCDebug(DIGIKAM_GEOENGINE_LOG) << "index bad parent";
        return QModelIndex();
    }

    GeoDataObject* childItem = nullptr;

    if (auto container = dynamic_cast<GeoDataContainer*>(parentItem))
    {
        childItem = container->child(row);

        return createIndex(row, column, childItem);
    }

    if (const auto placemark = geodata_cast<GeoDataPlacemark>(parentItem))
    {
        childItem = placemark->geometry();

        if (geodata_cast<GeoDataMultiGeometry>(childItem))
        {
            return createIndex(row, column, childItem);
        }
    }

    if (const auto geometry = geodata_cast<GeoDataMultiGeometry>(parentItem))
    {
        childItem = geometry->child(row);

        return createIndex(row, column, childItem);
    }

    if (const auto tour = geodata_cast<GeoDataTour>(parentItem))
    {
        childItem = tour->playlist();

        return createIndex(row, column, childItem);
    }

    if (const auto playlist = geodata_cast<GeoDataPlaylist>(parentItem))
    {
        childItem = playlist->primitive(row);

        return createIndex(row, column, childItem);
    }

    return QModelIndex();
}

QModelIndex GeoDataTreeModel::parent(const QModelIndex& index) const
{
    //qCDebug(DIGIKAM_GEOENGINE_LOG) << "parent";

    if (!index.isValid())
    {
        //qCDebug(DIGIKAM_GEOENGINE_LOG) << "parent bad index";

        return QModelIndex();
    }

    GeoDataObject* childObject = static_cast<GeoDataObject*>(index.internalPointer());

    if (childObject)
    {
        /// parentObject can be a container, placemark, multigeometry or playlist

        GeoDataObject* parentObject = childObject->parent();

        if (parentObject == d->m_rootDocument)
        {
            return QModelIndex();
        }

        GeoDataObject* greatParentObject = parentObject->parent();

        // Avoid crashing when there is no grandparent

        if (greatParentObject == nullptr)
        {
            return QModelIndex();
        }

        // greatParent can be a container

        // cppcheck-suppress constVariablePointer
        if (auto greatparentContainer = dynamic_cast<GeoDataContainer*>(greatParentObject))
        {
            const GeoDataFeature* parentFeature = static_cast<GeoDataFeature*>(parentObject);

            //qCDebug(DIGIKAM_GEOENGINE_LOG) << "parent " << childObject->nodeType() << "(" << childObject << ") = "
            //                               << parentObject->nodeType() << "[" << greatparentContainer->childPosition( parentFeature ) << "](" << parentObject << ")";

            return createIndex(greatparentContainer->childPosition(parentFeature), 0, parentObject);
        }

        // greatParent can be a placemark

        if (geodata_cast<GeoDataPlacemark>(greatParentObject))
        {

            //GeoDataPlacemark *greatparentPlacemark = static_cast<GeoDataPlacemark*>( greatParentObject );
            //qCDebug(DIGIKAM_GEOENGINE_LOG) << "parent " << childObject->nodeType() << "(" << childObject << ") = "
            //                               << parentObject->nodeType() << "[0](" << parentObject << ")";

            return createIndex(0, 0, parentObject);
        }

        // greatParent can be a multigeometry

        if (const GeoDataMultiGeometry* greatparentMultiGeo = geodata_cast<GeoDataMultiGeometry>(greatParentObject))
        {
            const GeoDataGeometry* parentGeometry = static_cast<GeoDataGeometry*>(parentObject);

            //qCDebug(DIGIKAM_GEOENGINE_LOG) << "parent " << childObject->nodeType() << "(" << childObject << ") = "
            //                               << parentObject->nodeType() << "[" << greatParentItem->childPosition( parentGeometry ) << "](" << parentObject << ")";

            return createIndex(greatparentMultiGeo->childPosition(parentGeometry), 0, parentObject);
        }

        if (GeoDataTour* tour = geodata_cast<GeoDataTour>(greatParentObject))
        {
            return createIndex(0, 0, tour->playlist());
        }
    }

    //qCDebug(DIGIKAM_GEOENGINE_LOG) << "parent unknown index";

    return QModelIndex();
}

int GeoDataTreeModel::columnCount(const QModelIndex&) const
{
    return 4;
}

bool GeoDataTreeModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
    if (!index.isValid())
    {
        return false;
    }

    GeoDataObject* object = static_cast<GeoDataObject*>(index.internalPointer());

    if      (role == Qt::CheckStateRole)
    {
        if (auto feature = dynamic_cast<GeoDataFeature*>(object))
        {
            bool bValue = value.toBool();

            if (auto pfolder = geodata_cast<GeoDataFolder>(feature->parent()))
            {
                if (pfolder->style()->listStyle().listItemType() == GeoDataListStyle::RadioFolder)
                {
                    if (bValue)
                    {
                        QVector< GeoDataFeature* >::Iterator i = pfolder->begin();

                        for ( ; i < pfolder->end() ; ++i)
                        {
                            (*i)->setVisible(false);
                        }
                    }
                }
            }

            if (auto folder = geodata_cast<GeoDataFolder>(object))
            {
                if (!bValue)
                {
                    if (
                           (folder->style()->listStyle().listItemType() == GeoDataListStyle::RadioFolder)
                        || (folder->style()->listStyle().listItemType() == GeoDataListStyle::CheckOffOnly)
                       )
                    {
                        QVector< GeoDataFeature* >::Iterator i = folder->begin();

                        for ( ; i < folder->end() ; ++i)
                        {
                            (*i)->setVisible(false);
                        }

                        folder->setVisible(false);
                    }
                }
            }

            feature->setVisible(bValue);
            qCDebug(DIGIKAM_GEOENGINE_LOG) << "setData " << feature->name();
            updateFeature(feature);

            return true;
        }
    }
    else if (role == Qt::EditRole)
    {
        if (auto feature = dynamic_cast<GeoDataFeature*>(object))
        {
            feature->setName(value.toString());
            qCDebug(DIGIKAM_GEOENGINE_LOG) << "setData " << feature->name() << " " << value.toString();
            updateFeature(feature);

            return true;
        }
    }

    return false;
}

Qt::ItemFlags GeoDataTreeModel::flags(const QModelIndex& index) const
{
    if (!index.isValid())
    {
        return Qt::NoItemFlags;
    }

    const GeoDataObject* object = static_cast<const GeoDataObject*>(index.internalPointer());

    if (const auto feature = geodata_cast<GeoDataPlacemark>(object))
    {
        const GeoDataObject* parent = feature->parent();

        // cppcheck-suppress constVariablePointer
        if (const auto parentfolder = geodata_cast<GeoDataFolder>(parent))
        {
            if      (parentfolder->style()->listStyle().listItemType() == GeoDataListStyle::RadioFolder)
            {
                return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEditable);
            }
            else if (parentfolder->style()->listStyle().listItemType() == GeoDataListStyle::CheckHideChildren)
            {
                return Qt::NoItemFlags;
            }
        }
    }

    // cppcheck-suppress constVariablePointer
    if (const auto folder = geodata_cast<GeoDataFolder>(object))
    {
        if      (folder->style()->listStyle().listItemType() == GeoDataListStyle::RadioFolder)
        {
            return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEditable);
        }
        else if (folder->style()->listStyle().listItemType() == GeoDataListStyle::CheckOffOnly)
        {
            QVector<GeoDataFeature*>::ConstIterator i = folder->constBegin();
            bool allVisible = true;

            for ( ; i < folder->constEnd() ; ++i)
            {
                if (!(*i)->isVisible())
                {
                    allVisible = false;
                    break;
                }
            }

            if (allVisible)
            {
                return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEditable);
            }
            else
            {
                return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
            }
        }
        else if (folder->style()->listStyle().listItemType() == GeoDataListStyle::CheckHideChildren)
        {
            return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEditable);
        }
    }

    if (geodata_cast<GeoDataTour>(object))
    {
        return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
    }

    if (dynamic_cast<const GeoDataFeature*>(object))
    {
        const GeoDataObject* parent = object;

        while (!geodata_cast<GeoDataDocument>(parent))
        {
            parent = parent->parent();
        }

        const GeoDataDocument* document = static_cast<const GeoDataDocument*>(parent);

        if (document->documentRole() == UserDocument)
        {
            return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEditable);
        }

        return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable);
    }

    if (   geodata_cast<GeoDataWait>(object)
        || geodata_cast<GeoDataFlyTo>(object)
        || geodata_cast<GeoDataPlaylist>(object)
       )
    {
        return (Qt::ItemIsSelectable | Qt::ItemIsEnabled);
    }

    return (Qt::ItemIsEnabled | Qt::ItemIsSelectable);
}

QModelIndex GeoDataTreeModel::index(const GeoDataObject* object) const
{
    if (object == nullptr)
    {
        return QModelIndex();
    }

    // It first runs bottom-top, storing every ancestor of the object, and
    // then goes top-down retrieving the QModelIndex of every ancestor until reaching the
    // index of the requested object.
    // The TreeModel contains: Documents, Folders, Placemarks, MultiGeometries
    // and Geometries that are children of MultiGeometries
    // You can not call this function with an element that does not belong to the tree

    Q_ASSERT(   geodata_cast<GeoDataFolder>(object)
             || geodata_cast<GeoDataDocument>(object)
             || geodata_cast<GeoDataPlacemark>(object)
             || geodata_cast<GeoDataTour>(object)
             || (   geodata_cast<GeoDataPlaylist>(object)
                 && geodata_cast<GeoDataTour>(object->parent()))
             || (   geodata_cast<GeoDataWait>(object)
                 && geodata_cast<GeoDataPlaylist>(object->parent()))
             || (   geodata_cast<GeoDataFlyTo>(object)
                 && geodata_cast<GeoDataPlaylist>(object->parent()))
             || (   geodata_cast<GeoDataLineString>(object)
                 && geodata_cast<GeoDataMultiGeometry>(object->parent()))
             || (   geodata_cast<GeoDataLinearRing>(object)
                 && geodata_cast<GeoDataMultiGeometry>(object->parent()))
             || (   geodata_cast<GeoDataPoint>(object)
                 && geodata_cast<GeoDataMultiGeometry>(object->parent()))
             || (   geodata_cast<GeoDataPolygon>(object)
                 && geodata_cast<GeoDataMultiGeometry>(object->parent()))
             || geodata_cast<GeoDataMultiGeometry>(object)
            );

    QList<const GeoDataObject*> ancestors;

    const GeoDataObject* itup = object;         // Iterator to reach the top of the GeoDataDocument (bottom-up)

    while (itup && (itup != d->m_rootDocument)) // We reach up to the rootDocument
    {
        ancestors.append(itup);
        itup = itup->parent() ;
    }

    QModelIndex itdown;

    if (!ancestors.isEmpty())
    {
        itdown = index(d->m_rootDocument->childPosition(static_cast<const GeoDataFeature*>(ancestors.last())),
                                                        0, QModelIndex()); // Iterator to go top down

        while ((ancestors.size() > 1))
        {
            const GeoDataObject* parent = static_cast<const GeoDataObject*>(ancestors.last());

            if      (const auto container = dynamic_cast<const GeoDataContainer*>(parent))
            {
                ancestors.removeLast();
                itdown = index(container->childPosition(static_cast<const GeoDataFeature*>(ancestors.last())), 0, itdown);
            }
            else if (geodata_cast<GeoDataPlacemark>(parent))
            {
                // The only child of the model is a Geometry or MultiGeometry object
                // If it is a geometry object, we should be on the bottom of the list

                ancestors.removeLast();

                if (geodata_cast<GeoDataMultiGeometry>(ancestors.last()))
                {
                    itdown = index(0, 0, itdown);
                }
                else
                {
                    itdown = QModelIndex();
                }
            }
            // cppcheck-suppress constVariablePointer
            else if (auto multiGeometry = geodata_cast<GeoDataMultiGeometry>(parent))
            {
                // The child is one of the geometry children of MultiGeometry

                ancestors.removeLast();
                itdown = index(multiGeometry->childPosition(static_cast<const GeoDataGeometry*>(ancestors.last())), 0, itdown);
            }
            else if (geodata_cast<GeoDataTour>(parent))
            {
                ancestors.removeLast();
                itdown = index(0, 0, itdown);
            }
            else if (auto playlist = geodata_cast<GeoDataPlaylist>(parent))
            {
                for (int i = 0 ; i < playlist->size() ; i++)
                {
                    if (playlist->primitive(i) == ancestors.last())
                    {
                        ancestors.removeLast();
                        itdown = index(i, 0, itdown);

                        break;
                    }
                }
            }
            else // If the element is not found on the tree, it will be added under m_rootDocument
            {
                itdown = QModelIndex();
                break;
            }
        }
    }

    return itdown;
}

QItemSelectionModel* GeoDataTreeModel::selectionModel()
{
    return &d->m_selectionModel;
}

int GeoDataTreeModel::addFeature(GeoDataContainer* parent, GeoDataFeature* feature, int row)
{
    if (parent && feature)
    {
        QModelIndex modelindex = index(parent);

        // index(GeoDataObject*) returns QModelIndex() if parent == m_rootDocument
        // or if parent is not found on the tree.
        // We must check that we are in top of the tree (then QModelIndex() is
        // the right parent to insert the child object) or that we have a valid QModelIndex

        if ((parent == d->m_rootDocument) || modelindex.isValid())
        {
            if ((row < 0) || (row > parent->size()))
            {
                row = parent->size();
            }

            beginInsertRows(modelindex, row, row);
            parent->insert(row, feature);
            d->checkParenting(parent);
            endInsertRows();

            Q_EMIT added(feature);
        }
        else
        {
            qCWarning(DIGIKAM_GEOENGINE_LOG) << "GeoDataTreeModel::addFeature (parent "
                                             << parent << " - feature" << feature
                                             << ") : parent not found on the TreeModel";
        }
    }
    else
    {
        qCWarning(DIGIKAM_GEOENGINE_LOG) << "Null pointer in call to GeoDataTreeModel::addFeature (parent "
                                         << parent << " - feature" << feature << ")";
    }

    return row; // -1 if it failed, the relative index otherwise.
}

int GeoDataTreeModel::addDocument(GeoDataDocument* document)
{
    return addFeature(d->m_rootDocument, document);
}

bool GeoDataTreeModel::removeFeature(GeoDataContainer* parent, int row)
{
    if (row < parent->size())
    {
        beginRemoveRows(index(parent), row, row);
        GeoDataFeature* feature = parent->child(row);
        parent->remove(row);

        Q_EMIT removed(feature);

        endRemoveRows();

        return true;
    }

    return false; // Tried to remove a row that is not contained in the parent.
}

int GeoDataTreeModel::removeFeature(GeoDataFeature* feature)
{
    if (feature && (feature != d->m_rootDocument))
    {

        if (!feature->parent())
        {
            return -1;
        }

        // We check to see we are not removing the
        // top level element m_rootDocument

        GeoDataObject* parent = static_cast< GeoDataObject* >(feature->parent());

        if (dynamic_cast<const GeoDataContainer*>(parent))
        {
            int row = static_cast< GeoDataContainer* >(feature->parent())->childPosition(feature);

            if (row != -1)
            {
                bool removed = removeFeature(static_cast< GeoDataContainer* >(feature->parent()), row);

                if (removed)
                {
                    return row;
                }
            }

            // The feature is not contained in the parent it points to
        }
    }

    return -1; // We can not remove the rootDocument
}

void GeoDataTreeModel::updateFeature(GeoDataFeature* feature)
{
    GeoDataContainer* container = static_cast<GeoDataContainer*>(feature->parent());
    int index = removeFeature(feature);

    Q_ASSERT(index != -1);

    addFeature(container, feature, index);
}

void GeoDataTreeModel::removeDocument(int index)
{
    removeFeature(d->m_rootDocument, index);
}

void GeoDataTreeModel::removeDocument(GeoDataDocument* document)
{
    removeFeature(document);
}

void GeoDataTreeModel::setRootDocument(GeoDataDocument* document)
{
    beginResetModel();

    if (d->m_ownsRootDocument)
    {
        delete d->m_rootDocument;
    }

    d->m_ownsRootDocument = (document == nullptr);
    d->m_rootDocument     = document ? document : new GeoDataDocument;
    endResetModel();
}

GeoDataDocument* GeoDataTreeModel::rootDocument()
{
    return d->m_rootDocument;
}

int GeoDataTreeModel::addTourPrimitive(const QModelIndex& parent, GeoDataTourPrimitive* primitive, int row)
{
    GeoDataObject* parentObject = static_cast<GeoDataObject*>(parent.internalPointer());

    if (auto playlist = geodata_cast<GeoDataPlaylist>(parentObject))
    {
        if (row == -1)
        {
            row = playlist->size();
        }

        beginInsertRows(parent, row, row);
        playlist->insertPrimitive(row, primitive);
        endInsertRows();

        return row;
    }

    return -1;
}

bool GeoDataTreeModel::removeTourPrimitive(const QModelIndex& parent, int index)
{
    GeoDataObject* parentObject = static_cast<GeoDataObject*>(parent.internalPointer());

    if (auto playlist = (parent.isValid() ? geodata_cast<GeoDataPlaylist>(parentObject) : nullptr))
    {
        if (playlist->size() > index)
        {
            beginRemoveRows(parent, index, index);
            playlist->removePrimitiveAt(index);
            endRemoveRows();

            return true;
        }
    }

    return false;
}

bool GeoDataTreeModel::swapTourPrimitives(const QModelIndex& parent, int indexA, int indexB)
{
    GeoDataObject* parentObject = static_cast<GeoDataObject*>(parent.internalPointer());

    if (auto playlist = (parent.isValid() ? geodata_cast<GeoDataPlaylist>(parentObject) : nullptr))
    {
        if (indexA > indexB)
        {
            std::swap(indexA, indexB);
        }

        if ((indexB - indexA) == 1)
        {
            beginMoveRows(parent, indexA, indexA, parent, indexB + 1);
        }
        else
        {
            beginMoveRows(parent, indexA, indexA, parent, indexB);
            beginMoveRows(parent, indexB, indexB, parent, indexA);
        }

        playlist->swapPrimitives(indexA, indexB);

        if (indexB - indexA != 1)
        {
            endMoveRows();
        }

        endMoveRows();

        return true;
    }

    return false;
}

} // namespace Marble

#include "moc_GeoDataTreeModel.cpp"
