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

// Self
#include "OsmDocumentTagTranslator.h"

// Marble
#include "GeoDataBuilding.h"
#include "GeoDataDocument.h"
#include "GeoDataGeometry.h"
#include "GeoDataLinearRing.h"
#include "GeoDataMultiGeometry.h"
#include "GeoDataPlacemark.h"
#include "GeoDataPoint.h"
#include "GeoDataPolygon.h"
#include "GeoDataRelation.h"
#include "OsmNodeTagWriter.h"
#include "OsmRelationTagWriter.h"
#include "OsmWayTagWriter.h"
#include "osm/OsmObjectManager.h"
#include "osm/OsmPlacemarkData.h"

#include <QDebug>

namespace Marble
{

void OsmConverter::read(const GeoDataDocument *document)
{
    m_nodes.clear();
    m_ways.clear();
    m_relations.clear();

    // Writing all the component nodes ( points, nodes of polylines, nodes of polygons )
    for (auto feature : document->featureList()) {
        if (auto placemark = geodata_cast<GeoDataPlacemark>(feature)) {
            // If the placemark's osmData is not complete, it is initialized by the OsmObjectManager
            OsmObjectManager::initializeOsmData(placemark);
            const OsmPlacemarkData &osmData = placemark->osmData();

            if (geodata_cast<GeoDataPoint>(placemark->geometry())) {
                m_nodes << OsmConverter::Node(placemark->coordinate(), osmData);
            } else if (const auto lineString = geodata_cast<GeoDataLineString>(placemark->geometry())) {
                for (auto const &coordinates : *lineString) {
                    m_nodes << OsmConverter::Node(coordinates, osmData.nodeReference(coordinates));
                }
                m_ways << OsmConverter::Way(lineString, osmData);
            } else if (const auto linearRing = geodata_cast<GeoDataLinearRing>(placemark->geometry())) {
                processLinearRing(linearRing, osmData);
            } else if (const auto polygon = geodata_cast<GeoDataPolygon>(placemark->geometry())) {
                processPolygon(polygon, osmData, placemark);
            } else if (const auto building = geodata_cast<GeoDataBuilding>(placemark->geometry())) {
                if (const auto linearRing = geodata_cast<GeoDataLinearRing>(&building->multiGeometry()->at(0))) {
                    processLinearRing(linearRing, osmData);
                } else if (const auto polygon = geodata_cast<GeoDataPolygon>(&building->multiGeometry()->at(0))) {
                    processPolygon(polygon, osmData, placemark);
                }
            }
        } else if (const auto placemark = geodata_cast<GeoDataRelation>(feature)) {
            m_relations.append(OsmConverter::Relation(placemark, placemark->osmData()));
        }
    }

    // Sort by id ascending since some external tools rely on that
    std::sort(m_nodes.begin(), m_nodes.end(), [](const Node &a, const Node &b) {
        return a.second.id() < b.second.id();
    });
    std::sort(m_ways.begin(), m_ways.end(), [](const Way &a, const Way &b) {
        return a.second.id() < b.second.id();
    });
    std::sort(m_relations.begin(), m_relations.end(), [](const Relation &a, const Relation &b) {
        return a.second.id() < b.second.id();
    });
}

const OsmConverter::Nodes &OsmConverter::nodes() const
{
    return m_nodes;
}

const OsmConverter::Ways &OsmConverter::ways() const
{
    return m_ways;
}

const OsmConverter::Relations &OsmConverter::relations() const
{
    return m_relations;
}

void OsmConverter::processLinearRing(GeoDataLinearRing *linearRing, const OsmPlacemarkData &osmData)
{
    for (auto const &coordinates : *linearRing) {
        m_nodes << OsmConverter::Node(coordinates, osmData.nodeReference(coordinates));
    }
    m_ways << OsmConverter::Way(linearRing, osmData);
}

void OsmConverter::processPolygon(GeoDataPolygon *polygon, const OsmPlacemarkData &osmData, GeoDataPlacemark *placemark)
{
    int index = -1;

    // Writing all the outerRing's nodes
    const GeoDataLinearRing &outerRing = polygon->outerBoundary();
    const OsmPlacemarkData outerRingOsmData = osmData.memberReference(index);
    for (auto const &coordinates : outerRing) {
        m_nodes << OsmConverter::Node(coordinates, outerRingOsmData.nodeReference(coordinates));
    }
    m_ways << OsmConverter::Way(&outerRing, outerRingOsmData);

    // Writing all nodes for each innerRing
    for (auto const &innerRing : polygon->innerBoundaries()) {
        ++index;
        const OsmPlacemarkData innerRingOsmData = osmData.memberReference(index);
        for (auto const &coordinates : innerRing) {
            m_nodes << OsmConverter::Node(coordinates, innerRingOsmData.nodeReference(coordinates));
        }
        m_ways << OsmConverter::Way(&innerRing, innerRingOsmData);
    }
    m_relations.append(OsmConverter::Relation(placemark, osmData));
}

}
