/* ============================================================
 *
 * 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 "OsmPlacemarkData.h"

// Qt includes

#include <QXmlStreamAttributes>

// Local includes

#include "GeoDataExtendedData.h"

namespace Marble
{

inline uint qHash(Marble::OsmIdentifier ident, uint seed)
{
    return (::qHash(ident.id, seed) ^ ::qHash((int)ident.type, seed));
}

OsmPlacemarkData::OsmPlacemarkData()
    : m_id  (0),
      m_href(new OsmPlacemarkDataHashRef)
{
    // nothing to do
}

OsmPlacemarkData::~OsmPlacemarkData()
{
    // nothing to do
}

OsmPlacemarkDataHashRef* OsmPlacemarkData::hRef() const
{
    return m_href.data();
}

qint64 OsmPlacemarkData::id() const
{
    return m_id;
}

qint64 OsmPlacemarkData::oid() const
{
    auto const value = m_tags.value(QStringLiteral("mx:oid")).toLong();

    return ((value > 0) ? value : m_id);
}

QString OsmPlacemarkData::changeset() const
{
    return m_tags.value(QStringLiteral("mx:changeset"));
}

QString OsmPlacemarkData::version() const
{
    return m_tags.value(QStringLiteral("mx:version"));
}

QString OsmPlacemarkData::uid() const
{
    return m_tags.value(QStringLiteral("mx:uid"));
}

QString OsmPlacemarkData::isVisible() const
{
    return m_tags.value(QStringLiteral("mx:visible"));
}

QString OsmPlacemarkData::user() const
{
    return m_tags.value(QStringLiteral("mx:user"));
}

QString OsmPlacemarkData::timestamp() const
{
    return m_tags.value(QStringLiteral("mx:timestamp"));
}

QString OsmPlacemarkData::action() const
{
    return m_tags.value(QStringLiteral("mx:action"));
}

void OsmPlacemarkData::setId(qint64 id)
{
    m_id = id;
}

void OsmPlacemarkData::setVersion(const QString& version)
{
    m_tags[QStringLiteral("mx:version")] = version;
}

void OsmPlacemarkData::setChangeset(const QString& changeset)
{
    m_tags[QStringLiteral("mx:changeset")] = changeset;
}

void OsmPlacemarkData::setUid(const QString& uid)
{
    m_tags[QStringLiteral("mx:uid")] = uid;
}

void OsmPlacemarkData::setVisible(const QString& visible)
{
    m_tags[QStringLiteral("mx:visible")] = visible;
}

void OsmPlacemarkData::setUser(const QString& user)
{
    m_tags[QStringLiteral("mx:user")] = user;
}

void OsmPlacemarkData::setTimestamp(const QString& timestamp)
{
    m_tags[QStringLiteral("mx:timestamp")] = timestamp;
}

void OsmPlacemarkData::setAction(const QString& action)
{
    m_tags[QStringLiteral("mx:action")] = action;
}

QString OsmPlacemarkData::tagValue(const QString& key) const
{
    return m_tags.value(key);
}

void OsmPlacemarkData::addTag(const QString& key, const QString& value)
{
    m_tags.insert(key, value);
}

void OsmPlacemarkData::removeTag(const QString& key)
{
    m_tags.remove(key);
}

bool OsmPlacemarkData::containsTag(const QString& key, const QString& value) const
{
    auto const iter = m_tags.constFind(key);

    return ((iter == m_tags.constEnd()) ? false : (iter.value() == value));
}

bool OsmPlacemarkData::containsTagKey(const QString& key) const
{
    return m_tags.contains(key);
}

QHash<QString, QString>::const_iterator OsmPlacemarkData::findTag(const QString& key) const
{
    return m_tags.constFind(key);
}

QHash< QString, QString >::const_iterator OsmPlacemarkData::tagsBegin() const
{
    return m_tags.begin();
}

QHash< QString, QString >::const_iterator OsmPlacemarkData::tagsEnd() const
{
    return m_tags.constEnd();
}

OsmPlacemarkData& OsmPlacemarkData::nodeReference(const GeoDataCoordinates& coordinates)
{
    return m_href->m_nodeReferences[coordinates];
}

OsmPlacemarkData OsmPlacemarkData::nodeReference(const GeoDataCoordinates& coordinates) const
{
    return m_href->m_nodeReferences.value(coordinates);
}

void OsmPlacemarkData::addNodeReference(const GeoDataCoordinates& key, const OsmPlacemarkData& value)
{
    m_href->m_nodeReferences.insert(key, value);
}

void OsmPlacemarkData::removeNodeReference(const GeoDataCoordinates& key)
{
    m_href->m_nodeReferences.remove(key);
}

bool OsmPlacemarkData::containsNodeReference(const GeoDataCoordinates& key) const
{
    return m_href->m_nodeReferences.contains(key);
}

void OsmPlacemarkData::changeNodeReference(const GeoDataCoordinates& oldKey, const GeoDataCoordinates& newKey)
{
    m_href->m_nodeReferences.insert(newKey, m_href->m_nodeReferences.value(oldKey));
    m_href->m_nodeReferences.remove(oldKey);
}

OsmPlacemarkData& OsmPlacemarkData::memberReference(int key)
{
    return m_href->m_memberReferences[key];
}

OsmPlacemarkData OsmPlacemarkData::memberReference(int key) const
{
    return m_href->m_memberReferences.value(key);
}

void OsmPlacemarkData::addMemberReference(int key, const OsmPlacemarkData& value)
{
    m_href->m_memberReferences.insert(key, value);
}

void OsmPlacemarkData::removeMemberReference(int key)
{
    // If an inner boundary is deleted, all indexes higher than the deleted one
    // must be lowered by 1 to keep order.

    QHash< int, OsmPlacemarkData > newHash;
    QHash< int, OsmPlacemarkData >::iterator it  = m_href->m_memberReferences.begin();
    QHash< int, OsmPlacemarkData >::iterator end = m_href->m_memberReferences.end();

    for ( ; it != end ; ++it)
    {
        if      (it.key() > key)
        {
            newHash.insert(it.key() - 1, it.value());
        }
        else if (it.key() < key)
        {
            newHash.insert(it.key(), it.value());
        }
    }

    m_href->m_memberReferences = newHash;
}

bool OsmPlacemarkData::containsMemberReference(int key) const
{
    return m_href->m_memberReferences.contains(key);
}

void OsmPlacemarkData::addRelation(qint64 id, OsmType type, const QString& role)
{
    m_relationReferences.insert({ id, type }, role);
}

void OsmPlacemarkData::removeRelation(qint64 id)
{
    /// ### this is wrong and just done this way for backward behavior compatible
    /// ### this method should probably take type as an additional argument

    m_relationReferences.remove({ id, OsmType::Node });
    m_relationReferences.remove({ id, OsmType::Way });
    m_relationReferences.remove({ id, OsmType::Relation });
}

bool OsmPlacemarkData::containsRelation(qint64 id) const
{
    /// ### this is wrong and just done this way for backward behavior compatible
    /// ### this method should probably take type as an additional argument

    return (
            m_relationReferences.contains({ id, OsmType::Node })
            || m_relationReferences.contains({ id, OsmType::Way })
            || m_relationReferences.contains({ id, OsmType::Relation })
           );
}

QHash< OsmIdentifier, QString >::const_iterator OsmPlacemarkData::relationReferencesBegin() const
{
    return m_relationReferences.begin();
}

QHash< OsmIdentifier, QString >::const_iterator OsmPlacemarkData::relationReferencesEnd() const
{
    return m_relationReferences.constEnd();
}

bool OsmPlacemarkData::isNull() const
{
    return !m_id;
}

bool OsmPlacemarkData::isEmpty() const
{
    return (
            m_tags.isEmpty()                     &&
            m_href->m_nodeReferences.isEmpty()   &&
            m_href->m_memberReferences.isEmpty() &&
            m_relationReferences.isEmpty()
           );
}

OsmPlacemarkData OsmPlacemarkData::fromParserAttributes(const QXmlStreamAttributes& attributes)
{
    OsmPlacemarkData osmData;
    osmData.setId(attributes.value(QLatin1String("id")).toLongLong());
    osmData.setVersion(attributes.value(QLatin1String("version")).toString());
    osmData.setChangeset(attributes.value(QLatin1String("changeset")).toString());
    osmData.setUser(attributes.value(QLatin1String("user")).toString());
    osmData.setUid(attributes.value(QLatin1String("uid")).toString());
    osmData.setVisible(attributes.value(QLatin1String("visible")).toString());
    osmData.setTimestamp(attributes.value(QLatin1String("timestamp")).toString());
    osmData.setAction(attributes.value(QLatin1String("action")).toString());

    return osmData;
}

const char* OsmPlacemarkData::nodeType() const
{
    return "OsmPlacemarkDataType";
}

// ---------------------------------------------------------------------------------------------------------

OsmPlacemarkDataHashRef::OsmPlacemarkDataHashRef()
{
}

QHash<GeoDataCoordinates, OsmPlacemarkData>& OsmPlacemarkDataHashRef::nodeReferences()
{
    return m_nodeReferences;
}

QHash< GeoDataCoordinates, OsmPlacemarkData >::const_iterator OsmPlacemarkDataHashRef::nodeReferencesBegin() const
{
    return m_nodeReferences.begin();
}

QHash< GeoDataCoordinates, OsmPlacemarkData >::const_iterator OsmPlacemarkDataHashRef::nodeReferencesEnd() const
{
    return m_nodeReferences.constEnd();
}

QHash<int, OsmPlacemarkData>& OsmPlacemarkDataHashRef::memberReferences()
{
    return m_memberReferences;
}

QHash< int, OsmPlacemarkData >::const_iterator OsmPlacemarkDataHashRef::memberReferencesBegin() const
{
    return m_memberReferences.begin();
}

QHash< int, OsmPlacemarkData >::const_iterator OsmPlacemarkDataHashRef::memberReferencesEnd() const
{
    return m_memberReferences.constEnd();
}

} // namespace Marble
