/* ============================================================
 *
 * 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-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * ============================================================ */

#include "GeoDataLineString.h"
#include "GeoDataLineString_p.h"

// Qt includes

#include <QDataStream>
#include <QVariant>

// Local includes

#include "GeoDataLinearRing.h"
#include "GeoDataTypes.h"
#include "Quaternion.h"
#include "digikam_debug.h"

namespace Marble
{

GeoDataLineString::GeoDataLineString(TessellationFlags f)
    : GeoDataGeometry(new GeoDataLineStringPrivate(f))
{
    //    qCDebug(DIGIKAM_MARBLE_LOG) << "1) GeoDataLineString created:" << p();
}

GeoDataLineString::GeoDataLineString(GeoDataLineStringPrivate* priv)
    : GeoDataGeometry(priv)
{
    //    qCDebug(DIGIKAM_MARBLE_LOG) << "2) GeoDataLineString created:" << p();
}

GeoDataLineString::GeoDataLineString(const GeoDataGeometry& other)
    : GeoDataGeometry(other)
{
    //    qCDebug(DIGIKAM_MARBLE_LOG) << "3) GeoDataLineString created:" << p();
}

GeoDataLineString::~GeoDataLineString()
{
#ifdef DEBUG_GEODATA
    qCDebug(DIGIKAM_MARBLE_LOG) << "delete Linestring";
#endif
}

const char* GeoDataLineString::nodeType() const
{
    return GeoDataTypes::GeoDataLineStringType;
}

EnumGeometryId GeoDataLineString::geometryId() const
{
    return GeoDataLineStringId;
}

GeoDataGeometry* GeoDataLineString::copy() const
{
    return new GeoDataLineString(*this);
}

void GeoDataLineStringPrivate::interpolateDateLine(const GeoDataCoordinates& previousCoords,
                                                   const GeoDataCoordinates& currentCoords,
                                                   GeoDataCoordinates& previousAtDateLine,
                                                   GeoDataCoordinates& currentAtDateLine,
                                                   TessellationFlags f) const
{
    GeoDataCoordinates dateLineCoords;

    //    qCDebug(DIGIKAM_MARBLE_LOG) << Q_FUNC_INFO;

    if (f.testFlag(RespectLatitudeCircle) && previousCoords.latitude() == currentCoords.latitude())
    {
        dateLineCoords = currentCoords;
    }

    else
    {
        int recursionCounter = 0;
        dateLineCoords = findDateLine(previousCoords, currentCoords, recursionCounter);
    }

    previousAtDateLine = dateLineCoords;
    currentAtDateLine = dateLineCoords;

    if (previousCoords.longitude() < 0)
    {
        previousAtDateLine.setLongitude(-M_PI);
        currentAtDateLine.setLongitude(+M_PI);
    }

    else
    {
        previousAtDateLine.setLongitude(+M_PI);
        currentAtDateLine.setLongitude(-M_PI);
    }
}

GeoDataCoordinates GeoDataLineStringPrivate::findDateLine(const GeoDataCoordinates& previousCoords,
                                                          const GeoDataCoordinates& currentCoords,
                                                          int recursionCounter) const
{
    int currentSign = (currentCoords.longitude() < 0.0) ? -1 : +1 ;
    int previousSign = (previousCoords.longitude() < 0.0) ? -1 : +1 ;

    qreal longitudeDiff =   fabs(previousSign * M_PI  - previousCoords.longitude())
                            + fabs(currentSign * M_PI - currentCoords.longitude());

    if (longitudeDiff < 0.001 || recursionCounter == 100)
    {
        //        qCDebug(DIGIKAM_MARBLE_LOG) << "stopped at recursion" << recursionCounter << " and longitude difference " << longitudeDiff;
        return currentCoords;
    }

    ++recursionCounter;

    const GeoDataCoordinates interpolatedCoords = previousCoords.nlerp(currentCoords, 0.5);

    int interpolatedSign = (interpolatedCoords.longitude() < 0.0) ? -1 : +1 ;

    /*
        qCDebug(DIGIKAM_MARBLE_LOG) << "SRC" << previousCoords.toString();
        qCDebug(DIGIKAM_MARBLE_LOG) << "TAR" << currentCoords.toString();
        qCDebug(DIGIKAM_MARBLE_LOG) << "IPC" << interpolatedCoords.toString();
    */

    if (interpolatedSign != currentSign)
    {
        return findDateLine(interpolatedCoords, currentCoords, recursionCounter);
    }

    return findDateLine(previousCoords, interpolatedCoords, recursionCounter);
}

quint8 GeoDataLineStringPrivate::levelForResolution(qreal resolution) const
{
    if (m_previousResolution == resolution)
    {
        return m_level;
    }

    m_previousResolution = resolution;

    if (resolution < 0.0000005)
    {
        m_level = 17;
    }

    else if (resolution < 0.0000010)
    {
        m_level = 16;
    }

    else if (resolution < 0.0000020)
    {
        m_level = 15;
    }

    else if (resolution < 0.0000040)
    {
        m_level = 14;
    }

    else if (resolution < 0.0000080)
    {
        m_level = 13;
    }

    else if (resolution < 0.0000160)
    {
        m_level = 12;
    }

    else if (resolution < 0.0000320)
    {
        m_level = 11;
    }

    else if (resolution < 0.0000640)
    {
        m_level = 10;
    }

    else if (resolution < 0.0001280)
    {
        m_level = 9;
    }

    else if (resolution < 0.0002560)
    {
        m_level = 8;
    }

    else if (resolution < 0.0005120)
    {
        m_level = 7;
    }

    else if (resolution < 0.0010240)
    {
        m_level = 6;
    }

    else if (resolution < 0.0020480)
    {
        m_level = 5;
    }

    else if (resolution < 0.0040960)
    {
        m_level = 4;
    }

    else if (resolution < 0.0081920)
    {
        m_level = 3;
    }

    else if (resolution < 0.0163840)
    {
        m_level = 2;
    }

    else
    {
        m_level =  1;
    }

    return m_level;
}

qreal GeoDataLineStringPrivate::resolutionForLevel(int level)
{
    switch (level)
    {
        case 0:
            return 0.0655360;

        case 1:
            return 0.0327680;

        case 2:
            return 0.0163840;

        case 3:
            return 0.0081920;

        case 4:
            return 0.0040960;

        case 5:
            return 0.0020480;

        case 6:
            return 0.0010240;

        case 7:
            return 0.0005120;

        case 8:
            return 0.0002560;

        case 9:
            return 0.0001280;

        case 10:
            return 0.0000640;

        case 11:
            return 0.0000320;

        case 12:
            return 0.0000160;

        case 13:
            return 0.0000080;

        case 14:
            return 0.0000040;

        case 15:
            return 0.0000020;

        case 16:
            return 0.0000010;

        default:
        case 17:
            return 0.0000005;
    }
}

void GeoDataLineStringPrivate::optimize(GeoDataLineString& lineString) const
{
    QVector<GeoDataCoordinates>::iterator itCoords = lineString.begin();
    QVector<GeoDataCoordinates>::iterator itEnd    = lineString.end();

    if (lineString.size() < 2)
    {
        return;
    }

    // Calculate the least non-zero detail-level by checking the bounding box

    quint8 startLevel   = levelForResolution((lineString.latLonAltBox().width() + lineString.latLonAltBox().height()) / 2);
    quint8 currentLevel = startLevel;
    quint8 maxLevel     = startLevel;
    GeoDataCoordinates currentCoords;
    lineString.first().setDetail(startLevel);

    // Iterate through the linestring to assign different detail levels to the nodes.
    // In general the first and last node should have the start level assigned as
    // a detail level.
    // Starting from the first node the algorithm picks those nodes which
    // have a distance from each other that is just above the resolution that is
    // associated with the start level (which we use as a "current level").
    // Each of those nodes get the current level assigned as the detail level.
    // After iterating through the linestring we increment the current level value
    // and starting again with the first node we assign detail values in a similar way
    // to the remaining nodes which have no final detail level assigned yet.
    // We do as many iterations through the lineString as needed and bump up the
    // current level until all nodes have a non-zero detail level assigned.

    while ((currentLevel  < 16) && (currentLevel <= maxLevel + 1))
    {
        itCoords      = lineString.begin();
        currentCoords = *itCoords;
        ++itCoords;

        for ( ; itCoords != itEnd ; ++itCoords)
        {
            if ((itCoords->detail() != 0) && (itCoords->detail() < currentLevel))
            {
                continue;
            }

            if (currentLevel == startLevel && ((itCoords->longitude() == -M_PI)       ||
                                               (itCoords->longitude() ==  M_PI)       ||
                                               (itCoords->latitude() < -89 * DEG2RAD) ||
                                               (itCoords->latitude() >  89 * DEG2RAD)))
            {
                itCoords->setDetail(startLevel);
                currentCoords = *itCoords;
                maxLevel      = currentLevel;

                continue;
            }

            if (currentCoords.sphericalDistanceTo(*itCoords) < resolutionForLevel(currentLevel + 1))
            {
                itCoords->setDetail(currentLevel + 1);
            }
            else
            {
                itCoords->setDetail(currentLevel);
                currentCoords = *itCoords;
                maxLevel      = currentLevel;
            }
        }

        ++currentLevel;
    }

    lineString.last().setDetail(startLevel);
}

bool GeoDataLineString::isEmpty() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.isEmpty();
}

int GeoDataLineString::size() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.size();
}

GeoDataCoordinates& GeoDataLineString::at(int pos)
{
    detach();

    Q_D(GeoDataLineString);
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    return d->m_vector[pos];
}

const GeoDataCoordinates& GeoDataLineString::at(int pos) const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.at(pos);
}

GeoDataCoordinates& GeoDataLineString::operator[](int pos)
{
    detach();

    Q_D(GeoDataLineString);
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    return d->m_vector[pos];
}

GeoDataLineString GeoDataLineString::mid(int pos, int length) const
{
    GeoDataLineString substring;
    auto d = substring.d_func();
    d->m_vector = d_func()->m_vector.mid(pos, length);
    d->m_dirtyBox = true;
    d->m_dirtyRange = true;
    d->m_tessellationFlags = d_func()->m_tessellationFlags;
    d->m_extrude = d_func()->m_extrude;
    return substring;
}

const GeoDataCoordinates& GeoDataLineString::operator[](int pos) const
{
    Q_D(const GeoDataLineString);
    return d->m_vector[pos];
}

GeoDataCoordinates& GeoDataLineString::last()
{
    detach();

    Q_D(GeoDataLineString);
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    return d->m_vector.last();
}

GeoDataCoordinates& GeoDataLineString::first()
{
    detach();

    Q_D(GeoDataLineString);
    return d->m_vector.first();
}

const GeoDataCoordinates& GeoDataLineString::last() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.last();
}

const GeoDataCoordinates& GeoDataLineString::first() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.first();
}

QVector<GeoDataCoordinates>::Iterator GeoDataLineString::begin()
{
    detach();

    Q_D(GeoDataLineString);
    return d->m_vector.begin();
}

QVector<GeoDataCoordinates>::ConstIterator GeoDataLineString::begin() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.constBegin();
}

QVector<GeoDataCoordinates>::Iterator GeoDataLineString::end()
{
    detach();

    Q_D(GeoDataLineString);
    return d->m_vector.end();
}

QVector<GeoDataCoordinates>::ConstIterator GeoDataLineString::end() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.constEnd();
}

QVector<GeoDataCoordinates>::ConstIterator GeoDataLineString::constBegin() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.constBegin();
}

QVector<GeoDataCoordinates>::ConstIterator GeoDataLineString::constEnd() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.constEnd();
}

void GeoDataLineString::insert(int index, const GeoDataCoordinates& value)
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    d->m_vector.insert(index, value);
}

void GeoDataLineString::append(const GeoDataCoordinates& value)
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    d->m_vector.append(value);
}

void GeoDataLineString::reserve(int size)
{
    Q_D(GeoDataLineString);
    d->m_vector.reserve(size);
}

void GeoDataLineString::append(const QVector<GeoDataCoordinates>& values)
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;

    d->m_vector.append(values);
}

GeoDataLineString& GeoDataLineString::operator << (const GeoDataCoordinates& value)
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    d->m_vector.append(value);
    return *this;
}

GeoDataLineString& GeoDataLineString::operator << (const GeoDataLineString& value)
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;

    QVector<GeoDataCoordinates>::const_iterator itCoords = value.constBegin();
    QVector<GeoDataCoordinates>::const_iterator itEnd = value.constEnd();

    d->m_vector.reserve(d->m_vector.size() + value.size());

    for (; itCoords != itEnd; ++itCoords)
    {
        d->m_vector.append(*itCoords);
    }

    return *this;
}

bool GeoDataLineString::operator==(const GeoDataLineString& other) const
{
    if (!GeoDataGeometry::equals(other) ||
        size() != other.size() ||
        tessellate() != other.tessellate())
    {
        return false;
    }

    Q_D(const GeoDataLineString);
    const GeoDataLineStringPrivate* other_d = other.d_func();

    QVector<GeoDataCoordinates>::const_iterator itCoords = d->m_vector.constBegin();
    QVector<GeoDataCoordinates>::const_iterator otherItCoords = other_d->m_vector.constBegin();
    QVector<GeoDataCoordinates>::const_iterator itEnd = d->m_vector.constEnd();
    QVector<GeoDataCoordinates>::const_iterator otherItEnd = other_d->m_vector.constEnd();

    for (; itCoords != itEnd && otherItCoords != otherItEnd; ++itCoords, ++otherItCoords)
    {
        if (*itCoords != *otherItCoords)
        {
            return false;
        }
    }

    Q_ASSERT(itCoords == itEnd && otherItCoords == otherItEnd);
    return true;
}

bool GeoDataLineString::operator!=(const GeoDataLineString& other) const
{
    return !this->operator==(other);
}

void GeoDataLineString::clear()
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;

    d->m_vector.clear();
}

bool GeoDataLineString::isClosed() const
{
    return false;
}

bool GeoDataLineString::tessellate() const
{
    Q_D(const GeoDataLineString);
    return d->m_tessellationFlags.testFlag(Tessellate);
}

void GeoDataLineString::setTessellate(bool tessellate)
{
    detach();

    Q_D(GeoDataLineString);
    // According to the KML reference the tesselation of line strings in Google Earth
    // is generally done along great circles. However for subsequent points that share
    // the same latitude the latitude circles are followed. Our Tesselate and RespectLatitude
    // Flags provide this behaviour. For true polygons the latitude circles don't get considered.

    if (tessellate)
    {
        d->m_tessellationFlags |= (Tessellate | RespectLatitudeCircle);
    }

    else
    {
        d->m_tessellationFlags &= ~(Tessellate | RespectLatitudeCircle);
    }
}

TessellationFlags GeoDataLineString::tessellationFlags() const
{
    Q_D(const GeoDataLineString);
    return d->m_tessellationFlags;
}

void GeoDataLineString::setTessellationFlags(TessellationFlags f)
{
    detach();

    Q_D(GeoDataLineString);
    d->m_tessellationFlags = f;
}

void GeoDataLineString::reverse()
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    std::reverse(begin(), end());
}

GeoDataLineString GeoDataLineString::toNormalized() const
{
    Q_D(const GeoDataLineString);

    GeoDataLineString normalizedLineString;

    normalizedLineString.setTessellationFlags(tessellationFlags());

    qreal lon;
    qreal lat;

    // FIXME: Think about how we can avoid unnecessary copies
    //        if the linestring stays the same.
    QVector<GeoDataCoordinates>::const_iterator end = d->m_vector.constEnd();

    for (QVector<GeoDataCoordinates>::const_iterator itCoords
         = d->m_vector.constBegin();
         itCoords != end;
         ++itCoords)
    {

        itCoords->geoCoordinates(lon, lat);
        qreal alt = itCoords->altitude();
        GeoDataCoordinates::normalizeLonLat(lon, lat);

        GeoDataCoordinates normalizedCoords(*itCoords);
        normalizedCoords.set(lon, lat, alt);
        normalizedLineString << normalizedCoords;
    }

    return normalizedLineString;
}

GeoDataLineString GeoDataLineString::toRangeCorrected() const
{
    Q_D(const GeoDataLineString);

    if (d->m_dirtyRange)
    {

        delete d->m_rangeCorrected;

        if (isClosed())
        {
            d->m_rangeCorrected = new GeoDataLinearRing(toPoleCorrected());
        }

        else
        {
            d->m_rangeCorrected = new GeoDataLineString(toPoleCorrected());
        }

        d->m_dirtyRange = false;
    }

    return *d->m_rangeCorrected;
}

QVector<GeoDataLineString*> GeoDataLineString::toDateLineCorrected() const
{
    Q_D(const GeoDataLineString);

    QVector<GeoDataLineString*> lineStrings;

    d->toDateLineCorrected(*this, lineStrings);

    return lineStrings;
}

GeoDataLineString GeoDataLineString::toPoleCorrected() const
{
    Q_D(const GeoDataLineString);

    if (isClosed())
    {
        GeoDataLinearRing poleCorrected;
        d->toPoleCorrected(*this, poleCorrected);
        return poleCorrected;
    }

    else
    {
        GeoDataLineString poleCorrected;
        d->toPoleCorrected(*this, poleCorrected);
        return poleCorrected;
    }
}

void GeoDataLineStringPrivate::toPoleCorrected(const GeoDataLineString& q, GeoDataLineString& poleCorrected) const
{
    poleCorrected.setTessellationFlags(q.tessellationFlags());

    GeoDataCoordinates previousCoords;
    GeoDataCoordinates currentCoords;

    if (q.isClosed())
    {
        if (!(m_vector.first().isPole()) &&
            (m_vector.last().isPole()))
        {
            qreal firstLongitude = (m_vector.first()).longitude();
            GeoDataCoordinates modifiedCoords(m_vector.last());
            modifiedCoords.setLongitude(firstLongitude);
            poleCorrected << modifiedCoords;
        }
    }

    QVector<GeoDataCoordinates>::const_iterator itCoords = m_vector.constBegin();
    QVector<GeoDataCoordinates>::const_iterator itEnd = m_vector.constEnd();

    for (; itCoords != itEnd; ++itCoords)
    {

        currentCoords  = *itCoords;

        if (itCoords == m_vector.constBegin())
        {
            previousCoords = currentCoords;
        }

        if (currentCoords.isPole())
        {
            if (previousCoords.isPole())
            {
                continue;
            }

            else
            {
                qreal previousLongitude = previousCoords.longitude();
                GeoDataCoordinates currentModifiedCoords(currentCoords);
                currentModifiedCoords.setLongitude(previousLongitude);
                poleCorrected << currentModifiedCoords;
            }
        }

        else
        {
            if (previousCoords.isPole())
            {
                qreal currentLongitude = currentCoords.longitude();
                GeoDataCoordinates previousModifiedCoords(previousCoords);
                previousModifiedCoords.setLongitude(currentLongitude);
                poleCorrected << previousModifiedCoords;
                poleCorrected << currentCoords;
            }

            else
            {
                // No poles at all. Nothing special to handle
                poleCorrected << currentCoords;
            }
        }

        previousCoords = currentCoords;
    }

    if (q.isClosed())
    {
        if ((m_vector.first().isPole()) &&
            !(m_vector.last().isPole()))
        {
            qreal lastLongitude = (m_vector.last()).longitude();
            GeoDataCoordinates modifiedCoords(m_vector.first());
            modifiedCoords.setLongitude(lastLongitude);
            poleCorrected << modifiedCoords;
        }
    }
}

void GeoDataLineStringPrivate::toDateLineCorrected(
    const GeoDataLineString& q,
    QVector<GeoDataLineString*>& lineStrings
) const
{
    const bool isClosed = q.isClosed();

    const QVector<GeoDataCoordinates>::const_iterator itStartPoint = q.constBegin();
    const QVector<GeoDataCoordinates>::const_iterator itEndPoint = q.constEnd();
    QVector<GeoDataCoordinates>::const_iterator itPoint = itStartPoint;
    QVector<GeoDataCoordinates>::const_iterator itPreviousPoint = itPoint;

    TessellationFlags f = q.tessellationFlags();

    GeoDataLineString* unfinishedLineString = nullptr;

    GeoDataLineString* dateLineCorrected = isClosed ? new GeoDataLinearRing(f)
                                           : new GeoDataLineString(f);

    qreal previousLon = 0.0;
    int previousSign = 1;

    bool unfinished = false;

    for (; itPoint != itEndPoint; ++itPoint)
    {
        const qreal currentLon = itPoint->longitude();

        int currentSign = (currentLon < 0.0) ? -1 : +1 ;

        if (itPoint == q.constBegin())
        {
            previousSign = currentSign;
            previousLon  = currentLon;
        }

        // If we are crossing the date line ...
        if (previousSign != currentSign && fabs(previousLon) + fabs(currentLon) > M_PI)
        {

            unfinished = !unfinished;

            GeoDataCoordinates previousTemp;
            GeoDataCoordinates currentTemp;

            interpolateDateLine(*itPreviousPoint, *itPoint,
                                previousTemp, currentTemp, q.tessellationFlags());

            *dateLineCorrected << previousTemp;

            if (isClosed && unfinished)
            {
                // If it's a linear ring and if it crossed the IDL only once then
                // store the current string inside the unfinishedLineString for later use ...
                unfinishedLineString = dateLineCorrected;
                // ... and start a new linear ring for now.
                dateLineCorrected = new GeoDataLinearRing(f);
            }

            else
            {
                // Now it can only be a (finished) line string or a finished linear ring.
                // Store it in the vector  if the size is not zero.
                if (dateLineCorrected->size() > 0)
                {
                    lineStrings << dateLineCorrected;
                }

                else
                {
                    // Or delete it.
                    delete dateLineCorrected;
                }

                // If it's a finished linear ring restore the "remembered" unfinished String
                if (isClosed && !unfinished && unfinishedLineString)
                {
                    dateLineCorrected = unfinishedLineString;
                }

                else
                {
                    // if it's a line string just create a new line string.
                    dateLineCorrected = new GeoDataLineString(f);
                }
            }

            *dateLineCorrected << currentTemp;
            *dateLineCorrected << *itPoint;

        }

        else
        {
            *dateLineCorrected << *itPoint;
        }

        previousSign = currentSign;
        previousLon  = currentLon;
        itPreviousPoint = itPoint;
    }

    // If the line string doesn't cross the dateline an even number of times
    // then need to take care of the data stored in the unfinishedLineString
    if (unfinished && unfinishedLineString && !unfinishedLineString->isEmpty())
    {
        *dateLineCorrected << *unfinishedLineString;
        delete unfinishedLineString;
    }

    lineStrings << dateLineCorrected;
}

const GeoDataLatLonAltBox& GeoDataLineString::latLonAltBox() const
{
    Q_D(const GeoDataLineString);

    // GeoDataLatLonAltBox::fromLineString is very expensive
    // that's why we recreate it only if the m_dirtyBox
    // is TRUE.
    // DO NOT REMOVE THIS CONSTRUCT OR MARBLE WILL BE SLOW.
    if (d->m_dirtyBox)
    {
        d->m_latLonAltBox = GeoDataLatLonAltBox::fromLineString(*this);
        d->m_dirtyBox = false;
    }

    return d->m_latLonAltBox;
}

qreal GeoDataLineString::length(qreal planetRadius, int offset) const
{
    if (offset < 0 || offset >= size())
    {
        return 0;
    }

    Q_D(const GeoDataLineString);
    qreal length = 0.0;
    QVector<GeoDataCoordinates> const& vector = d->m_vector;
    int const start = qMax(offset + 1, 1);
    int const end = d->m_vector.size();

    for (int i = start; i < end; ++i)
    {
        length += vector[i - 1].sphericalDistanceTo(vector[i]);
    }

    return planetRadius * length;
}

QVector<GeoDataCoordinates>::Iterator GeoDataLineString::erase(const QVector<GeoDataCoordinates>::Iterator& pos)
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    return d->m_vector.erase(pos);
}

QVector<GeoDataCoordinates>::Iterator GeoDataLineString::erase(const QVector<GeoDataCoordinates>::Iterator& begin,
                                                               const QVector<GeoDataCoordinates>::Iterator& end)
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    return d->m_vector.erase(begin, end);
}

void GeoDataLineString::remove(int i)
{
    detach();

    Q_D(GeoDataLineString);
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    d->m_vector.remove(i);
}

GeoDataLineString GeoDataLineString::optimized() const
{
    Q_D(const GeoDataLineString);

    if (isClosed())
    {
        GeoDataLinearRing linearRing(*this);
        d->optimize(linearRing);
        return linearRing;
    }

    else
    {
        GeoDataLineString lineString(*this);
        d->optimize(lineString);
        return lineString;
    }
}

QVariantList GeoDataLineString::toVariantList() const
{
    Q_D(const GeoDataLineString);

    QVariantList variantList;

    for (const GeoDataCoordinates& itCoords : std::as_const(d->m_vector))
    {
        QVariantMap map;
        map.insert(QLatin1String("lon"), itCoords.longitude(GeoDataCoordinates::Degree));
        map.insert(QLatin1String("lat"), itCoords.latitude(GeoDataCoordinates::Degree));
        map.insert(QLatin1String("alt"), itCoords.altitude());
        variantList << map;
    }

    if (isClosed())
    {
        QVariantMap map;
        map.insert(QLatin1String("lon"), d->m_vector.first().longitude(GeoDataCoordinates::Degree));
        map.insert(QLatin1String("lat"), d->m_vector.first().latitude(GeoDataCoordinates::Degree));
        map.insert(QLatin1String("alt"), d->m_vector.first().altitude());
        variantList << map;
    }

    return variantList;
}

void GeoDataLineString::pack(QDataStream& stream) const
{
    Q_D(const GeoDataLineString);

    GeoDataGeometry::pack(stream);

    stream << size();
    stream << (qint32)(d->m_tessellationFlags);

    for (QVector<GeoDataCoordinates>::const_iterator iterator
         = d->m_vector.constBegin();
         iterator != d->m_vector.constEnd();
         ++iterator)
    {
        qCDebug(DIGIKAM_MARBLE_LOG) << "innerRing: size" << d->m_vector.size();
        GeoDataCoordinates coord = (*iterator);
        coord.pack(stream);
    }

}

void GeoDataLineString::unpack(QDataStream& stream)
{
    detach();

    Q_D(GeoDataLineString);

    GeoDataGeometry::unpack(stream);
    qint32 size;
    qint32 tessellationFlags;

    stream >> size;
    stream >> tessellationFlags;

    d->m_tessellationFlags = (TessellationFlags)(tessellationFlags);

    d->m_vector.reserve(d->m_vector.size() + size);

    for (qint32 i = 0; i < size; i++)
    {
        GeoDataCoordinates coord;
        coord.unpack(stream);
        d->m_vector.append(coord);
    }
}

} // namespace Marble
