/****************************************************************************
**
** Copyright (C) 2020 Paolo Angelelli <paolo.angelelli@gmail.com>
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtLocation module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL3$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#ifndef QDECLARATIVEPOLYLINEMAPITEM_P_P_H
#define QDECLARATIVEPOLYLINEMAPITEM_P_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <QtLocation/private/qlocationglobal_p.h>
#include <QtLocation/private/qdeclarativepolylinemapitem_p.h>
#include <QtLocation/private/qdeclarativegeomapitemutils_p.h>
#include <QtLocation/private/qdeclarativepolylinemapitem_p.h>
#include <QtLocation/private/qgeomapitemgeometry_p.h>
#include <QSGGeometryNode>
#include <QSGFlatColorMaterial>
#include <QtPositioning/QGeoPath>
#include <QtPositioning/QGeoPolygon>
#include <QtPositioning/QGeoRectangle>
#include <QtPositioning/QGeoCircle>
#include <QtPositioning/private/qdoublevector2d_p.h>
#include <QtCore/QScopedValueRollback>
#include <QSharedPointer>
#include <array>

QT_BEGIN_NAMESPACE

class Q_LOCATION_PRIVATE_EXPORT QGeoMapPolylineGeometry : public QGeoMapItemGeometry
{
public:
    QGeoMapPolylineGeometry();

    void updateSourcePoints(const QGeoMap &map,
                            const QList<QDoubleVector2D> &path,
                            const QGeoCoordinate geoLeftBound);

    void updateScreenPoints(const QGeoMap &map,
                            qreal strokeWidth,
                            bool adjustTranslation = true);

    void clearSource();

    bool contains(const QPointF &point) const override;

    QList<QList<QDoubleVector2D> > clipPath(const QGeoMap &map,
                    const QList<QDoubleVector2D> &path,
                    QDoubleVector2D &leftBoundWrapped);

    void pathToScreen(const QGeoMap &map,
                      const QList<QList<QDoubleVector2D> > &clippedPaths,
                      const QDoubleVector2D &leftBoundWrapped);

public:
    QVector<qreal> srcPoints_;
    QVector<QPainterPath::ElementType> srcPointTypes_;

#ifdef QT_LOCATION_DEBUG
    QList<QDoubleVector2D> m_wrappedPath;
    QList<QList<QDoubleVector2D>> m_clippedPaths;
#endif

    friend class QDeclarativeCircleMapItem;
    friend class QDeclarativePolygonMapItem;
    friend class QDeclarativeRectangleMapItem;
};

class Q_LOCATION_PRIVATE_EXPORT VisibleNode
{
public:
    VisibleNode();
    virtual ~VisibleNode();

    bool subtreeBlocked() const;
    void setSubtreeBlocked(bool blocked);
    bool visible() const;
    void setVisible(bool visible);

    bool m_blocked : 1;
    bool m_visible : 1;
};

class Q_LOCATION_PRIVATE_EXPORT MapItemGeometryNode : public QSGGeometryNode, public VisibleNode
{
public:
    ~MapItemGeometryNode() override;
    bool isSubtreeBlocked() const override;
};

class Q_LOCATION_PRIVATE_EXPORT MapPolylineMaterial : public QSGFlatColorMaterial
{
public:
    MapPolylineMaterial()
        : QSGFlatColorMaterial()
    {
        // Passing RequiresFullMatrix is essential in order to prevent the
        // batch renderer from baking in simple, translate-only transforms into
        // the vertex data. The shader will rely on the fact that
        // vertexCoord.xy is the Shape-space coordinate and so no modifications
        // are welcome.
        setFlag(Blending | RequiresFullMatrix | CustomCompileStep);
    }

    QSGMaterialShader *createShader() const override;

    void setGeoProjection(const QMatrix4x4 &p)
    {
        m_geoProjection = p;
    }

    QMatrix4x4 geoProjection() const
    {
        return m_geoProjection;
    }

    void setCenter(const QDoubleVector3D &c)
    {
        m_center = c;
    }

    QDoubleVector3D center() const
    {
        return m_center;
    }

    void setColor(const QColor &color)
    {
        QSGFlatColorMaterial::setColor(color);
        setFlag(Blending, true); // ToDo: Needed only temporarily, can be removed after debugging
    }

    int wrapOffset() const
    {
        return m_wrapOffset;
    }

    void setWrapOffset(int wrapOffset)
    {
        m_wrapOffset = wrapOffset;
    }

    void setLineWidth(const float lw)
    {
        m_lineWidth = lw;
    }

    float lineWidth() const
    {
        return m_lineWidth;
    }

    QSGMaterialType *type() const override;
    int compare(const QSGMaterial *other) const override;

protected:
    QMatrix4x4 m_geoProjection;
    QDoubleVector3D m_center;
    int m_wrapOffset = 0;
    float m_lineWidth = 1.0;
};

class Q_LOCATION_PRIVATE_EXPORT MapPolylineNode : public MapItemGeometryNode
{
public:
    MapPolylineNode();
    ~MapPolylineNode() override;

    void update(const QColor &fillColor, const QGeoMapItemGeometry *shape);

protected:
    QSGFlatColorMaterial fill_material_;
    QSGGeometry geometry_;
};

class Q_LOCATION_PRIVATE_EXPORT QGeoMapItemLODGeometry
{
public:
    mutable std::array<QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>, 7> m_verticesLOD; // fix it to 7,
                                                                             // do not allow simplifications beyond ZL 20. This could actually be limited even further
    mutable QVector<QDeclarativeGeoMapItemUtils::vec2> *m_screenVertices;
    mutable QSharedPointer<unsigned int> m_working;

    QGeoMapItemLODGeometry()
    {
        resetLOD();
    }

    void resetLOD()
    {
        // New pointer, some old LOD task might still be running and operating on the old pointers.
        m_verticesLOD[0] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>(
                            new QVector<QDeclarativeGeoMapItemUtils::vec2>);
        for (unsigned int i = 1; i < m_verticesLOD.size(); ++i)
            m_verticesLOD[i] = nullptr; // allocate on first use
        m_screenVertices = m_verticesLOD.front().data(); // resetting pointer to data to be LOD 0
    }

    static unsigned int zoomToLOD(unsigned int zoom);

    static unsigned int zoomForLOD(unsigned int zoom);

    bool isLODActive(unsigned int lod) const;

    void selectLOD(unsigned int zoom, double leftBound, bool /*closed*/);

    static QVector<QDeclarativeGeoMapItemUtils::vec2> getSimplified (
            QVector<QDeclarativeGeoMapItemUtils::vec2> &wrappedPath,
                              double leftBoundWrapped,
                              unsigned int zoom);

    static void enqueueSimplificationTask(const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &input, // reference as it gets copied in the nested call
                              const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &output,
                              double leftBound,
                              unsigned int zoom,
                              QSharedPointer<unsigned int> &working);

    void selectLODOnDataChanged(unsigned int zoom, double leftBound) const;

    bool selectLODOnLODMismatch(unsigned int zoom, double leftBound, bool closed) const
    {
        if (*m_working > 0) {
            return false;
        }
        const_cast<QGeoMapItemLODGeometry *>(this)->selectLOD(zoom,
                 leftBound,
                 closed);
        return true;
    }
};

class Q_LOCATION_PRIVATE_EXPORT QGeoMapPolylineGeometryOpenGL : public QGeoMapItemGeometry, public QGeoMapItemLODGeometry
{
public:
    typedef struct {
        QList<QDoubleVector2D> wrappedBboxes;
    } WrappedPolyline;

    QGeoMapPolylineGeometryOpenGL()
    {
        m_working = QSharedPointer<unsigned int>(new unsigned int(0));
    }

    void updateSourcePoints(const QGeoMap &map,
                            const QGeoPolygon &poly);

    void updateSourcePoints(const QGeoMap &map,
                            const QGeoPath &poly);

    void updateSourcePoints(const QGeoProjectionWebMercator &p,
                            const QList<QDoubleVector2D> &wrappedPath,
                            const QGeoRectangle &boundingRectangle);

    void updateSourcePoints(const QGeoMap &map,
                            const QGeoRectangle &rect);

    void updateSourcePoints(const QGeoMap &map,
                            const QGeoCircle &circle);

    void updateScreenPoints(const QGeoMap &map,
                            qreal strokeWidth,
                            bool adjustTranslation = true);

    void updateQuickGeometry(const QGeoProjectionWebMercator &p, qreal strokeWidth = 0.0);

    bool allocateAndFillEntries(QSGGeometry *geom,
                                bool closed = false,
                                unsigned int zoom = 0) const;
    void allocateAndFillLineStrip(QSGGeometry *geom,
                                  int lod = 0) const;

    bool contains(const QPointF &point) const override
    {
        Q_UNUSED(point)
        return false;
    }

    static double distanceTo(const QDoubleVector2D &a, const QDoubleVector2D &b, const QDoubleVector2D &p)
    {
        double u = ((p.x() - a.x()) * (b.x() - a.x()) + (p.y() - a.y()) * (b.y() - a.y()) ) / (b - a).lengthSquared();
        QDoubleVector2D intersection(a.x() + u * (b.x() - a.x()) , a.y() + u * (b.y() - a.y()) );

        QDoubleVector2D candidate = ( (p-a).length() < (p-b).length() ) ? a : b;

        if (u > 0 && u < 1
            && (p-intersection).length() < (p-candidate).length()  ) // And it falls in the segment
                candidate = intersection;

        return qAbs((candidate - p).length());
    }
    // Note: this is also slightly incorrect on joins and in the beginning/end of the line
    bool contains(const QPointF &point, qreal lineWidth, const QGeoProjectionWebMercator &p) const
    {
        const double lineHalfWidth = lineWidth * 0.5;
        const QDoubleVector2D pt(point);
        QDoubleVector2D a;
        if (m_screenVertices->size())
            a = p.wrappedMapProjectionToItemPosition(p.wrapMapProjection(m_screenVertices->first().toDoubleVector2D()));
        QDoubleVector2D b;
        for (int i = 1; i < m_screenVertices->size(); ++i)
        {
            if (!a.isFinite()) {
                a = p.wrappedMapProjectionToItemPosition(p.wrapMapProjection(m_screenVertices->at(i).toDoubleVector2D()));
                continue;
            }

            b = p.wrappedMapProjectionToItemPosition(p.wrapMapProjection(m_screenVertices->at(i).toDoubleVector2D()));
            if (!b.isFinite()) {
                a = b;
                continue;
            }

            if (b == a)
                continue;

            // Heavily simplifying it here: if a point is not projectable, skip the segment.
            // For a correct solution, the segment should be clipped instead.
            if (distanceTo(a, b, pt) <= lineHalfWidth)
                return true;

            a = b;
        }
        return false;
    }

public:
    QDoubleVector2D m_bboxLeftBoundWrapped;
    QVector<WrappedPolyline> m_wrappedPolygons;
    int m_wrapOffset;

    friend class QDeclarativeCircleMapItem;
    friend class QDeclarativePolygonMapItem;
    friend class QDeclarativeRectangleMapItem;
};

class Q_LOCATION_PRIVATE_EXPORT MapPolylineShaderLineStrip : public QSGMaterialShader
{
public:
    MapPolylineShaderLineStrip();

    const char *vertexShader() const override {
        return
        "attribute highp vec4 vertex;               \n"
        "uniform highp mat4 qt_Matrix;              \n"
        "uniform highp mat4 mapProjection;          \n"
        "uniform highp vec3 center;                 \n"
        "uniform highp vec3 center_lowpart;         \n"
        "uniform lowp float wrapOffset;             \n"
        "vec4 wrapped(in vec4 v) { return vec4(v.x + wrapOffset, v.y, 0.0, 1.0); }\n"
        "void main() {                              \n"
        "    vec4 vtx = wrapped(vertex) - vec4(center, 0.0);   \n"
        "    vtx = vtx - vec4(center_lowpart, 0.0);   \n"
        "    gl_Position = qt_Matrix * mapProjection * vtx;      \n"
        "}";
    }

    const char *fragmentShader() const {
        return
        "uniform lowp vec4 color;                   \n"
        "void main() {                              \n"
        "    gl_FragColor = color;                  \n"
        "}";
    }

    void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
    char const *const *attributeNames() const override;

protected:
    void initialize() override
    {
        m_matrix_id = program()->uniformLocation("qt_Matrix");
        m_color_id = program()->uniformLocation("color");
        m_mapProjection_id = program()->uniformLocation("mapProjection");
        m_center_id = program()->uniformLocation("center");
        m_center_lowpart_id = program()->uniformLocation("center_lowpart");
        m_wrapOffset_id = program()->uniformLocation("wrapOffset");
    }
    int m_center_id;
    int m_center_lowpart_id;
    int m_mapProjection_id;
    int m_matrix_id;
    int m_color_id;
    int m_wrapOffset_id;
};

class Q_LOCATION_PRIVATE_EXPORT MapPolylineShaderExtruded : public QSGMaterialShader
{
public:
    MapPolylineShaderExtruded();

    // Heavily adapted from https://github.com/mattdesl/webgl-lines/blob/master/projected/vert.glsl,
    // that is (c) Matt DesLauriers, and released under the MIT license.
    const char *vertexShaderMiteredSegments() const;

    const char *vertexShader() const override
    {
        return vertexShaderMiteredSegments();
    }

    const char *fragmentShader() const override
    {
        return
        "varying vec4 primitivecolor;           \n"
        "void main() {                          \n"
        "    gl_FragColor = primitivecolor;     \n"
        "}";
    }

    void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
    char const *const *attributeNames() const override;

protected:
    void initialize() override
    {
        m_matrix_id = program()->uniformLocation("qt_Matrix");
        m_color_id = program()->uniformLocation("color");
        m_mapProjection_id = program()->uniformLocation("mapProjection");
        m_center_id = program()->uniformLocation("center");
        m_center_lowpart_id = program()->uniformLocation("center_lowpart");
        m_lineWidth_id = program()->uniformLocation("lineWidth");
        m_aspect_id = program()->uniformLocation("aspect");
        m_miter_id = program()->uniformLocation("miter");
        m_wrapOffset_id = program()->uniformLocation("wrapOffset");
    }
    int m_center_id;
    int m_center_lowpart_id;
    int m_mapProjection_id;
    int m_matrix_id;
    int m_color_id;
    int m_lineWidth_id;
    int m_aspect_id;
    int m_miter_id;
    int m_wrapOffset_id;
};

class Q_LOCATION_PRIVATE_EXPORT MapPolylineNodeOpenGLLineStrip : public MapItemGeometryNode
{
public:
    MapPolylineNodeOpenGLLineStrip();
    ~MapPolylineNodeOpenGLLineStrip() override;

    void update(const QColor &fillColor,
                const qreal lineWidth,
                const QGeoMapPolylineGeometryOpenGL *shape,
                const QMatrix4x4 &geoProjection,
                const QDoubleVector3D &center,
                const Qt::PenCapStyle capStyle = Qt::SquareCap);

protected:
    MapPolylineMaterial fill_material_;
    QSGGeometry geometry_;
};

class Q_LOCATION_PRIVATE_EXPORT MapPolylineMaterialExtruded : public MapPolylineMaterial
{
public:
    MapPolylineMaterialExtruded() : MapPolylineMaterial()
    {

    }
    QSGMaterialShader *createShader() const override;

    void setMiter(const int m)
    {
        m_miter = m;
    }

    int miter() const
    {
        return m_miter;
    }

    QSGMaterialType *type() const override;
    int compare(const QSGMaterial *other) const override;

    int m_miter = 0;
};

class Q_LOCATION_PRIVATE_EXPORT MapPolylineNodeOpenGLExtruded : public MapItemGeometryNode
{
public:

    typedef struct {
         QDeclarativeGeoMapItemUtils::vec2 pos;
         QDeclarativeGeoMapItemUtils::vec2 prev;
         QDeclarativeGeoMapItemUtils::vec2 next;
         float direction;
         float triangletype; // es2 does not support int attribs
         float vertextype;

         static const char * const *attributeNames()
         {
             static char const *const attr[] = { "vertex", "previous", "next", "direction", "triangletype", "vertextype", nullptr };
             return attr;
         }
         static const QSGGeometry::AttributeSet &attributes()
         {
             static const QSGGeometry::Attribute dataTri[] = {
                 QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute) // pos
                 ,QSGGeometry::Attribute::createWithAttributeType(1, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute) // next
                 ,QSGGeometry::Attribute::createWithAttributeType(2, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute) // previous
                 ,QSGGeometry::Attribute::createWithAttributeType(3, 1, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute)  // direction
                 ,QSGGeometry::Attribute::createWithAttributeType(4, 1, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute)  // triangletype
                 ,QSGGeometry::Attribute::createWithAttributeType(5, 1, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute)  // vertextype
             };
             static const QSGGeometry::AttributeSet attrsTri = { 6, sizeof(MapPolylineNodeOpenGLExtruded::MapPolylineEntry), dataTri };
             return attrsTri;
         }
    } MapPolylineEntry;

    MapPolylineNodeOpenGLExtruded();
    ~MapPolylineNodeOpenGLExtruded() override;

    void update(const QColor &fillColor,
                const float lineWidth,
                const QGeoMapPolylineGeometryOpenGL *shape,
                const QMatrix4x4 geoProjection,
                const QDoubleVector3D center,
                const Qt::PenCapStyle capStyle = Qt::FlatCap,
                bool closed = false,
                unsigned int zoom = 30);

    static const QSGGeometry::AttributeSet &attributesMapPolylineTriangulated();

protected:
    MapPolylineMaterialExtruded fill_material_;
    QSGGeometry m_geometryTriangulating;
};

class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolylineMapItemPrivate
{
public:
    QDeclarativePolylineMapItemPrivate(QDeclarativePolylineMapItem &poly) : m_poly(poly)
    {

    }
    QDeclarativePolylineMapItemPrivate(QDeclarativePolylineMapItemPrivate &other) : m_poly(other.m_poly)
    {
    }

    virtual ~QDeclarativePolylineMapItemPrivate();
    virtual void markSourceDirtyAndUpdate() = 0;
    virtual void onMapSet() = 0;
    virtual void onLinePropertiesChanged() = 0;
    virtual void onGeoGeometryChanged() = 0;
    virtual void onGeoGeometryUpdated() = 0;
    virtual void onItemGeometryChanged() = 0;
    virtual void updatePolish() = 0;
    virtual void afterViewportChanged() = 0;
    virtual QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) = 0;
    virtual bool contains(const QPointF &point) const = 0;

    QDeclarativePolylineMapItem &m_poly;
    Qt::PenStyle m_penStyle = Qt::SolidLine;
    Qt::PenCapStyle m_penCapStyle = Qt::SquareCap;
};

class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolylineMapItemPrivateCPU: public QDeclarativePolylineMapItemPrivate
{
public:
    QDeclarativePolylineMapItemPrivateCPU(QDeclarativePolylineMapItem &poly) : QDeclarativePolylineMapItemPrivate(poly)
    {
    }

    QDeclarativePolylineMapItemPrivateCPU(QDeclarativePolylineMapItemPrivate &other)
    : QDeclarativePolylineMapItemPrivate(other)
    {
    }

    ~QDeclarativePolylineMapItemPrivateCPU() override;
    void onLinePropertiesChanged() override
    {
        // mark dirty just in case we're a width change
        markSourceDirtyAndUpdate();
    }
    void markSourceDirtyAndUpdate() override
    {
        m_geometry.markSourceDirty();
        m_poly.polishAndUpdate();
    }
    void regenerateCache()
    {
        if (!m_poly.map() || m_poly.map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator)
            return;
        const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_poly.map()->geoProjection());
        m_geopathProjected.clear();
        m_geopathProjected.reserve(m_poly.m_geopath.size());
        for (const QGeoCoordinate &c : m_poly.m_geopath.path())
            m_geopathProjected << p.geoToMapProjection(c);
    }
    void updateCache()
    {
        if (!m_poly.map() || m_poly.map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator)
            return;
        const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_poly.map()->geoProjection());
        m_geopathProjected << p.geoToMapProjection(m_poly.m_geopath.path().last());
    }
    void preserveGeometry()
    {
        m_geometry.setPreserveGeometry(true, m_poly.m_geopath.boundingGeoRectangle().topLeft());
    }
    void afterViewportChanged() override
    {
        // preserveGeometry is cleared in updateMapItemPaintNode
        preserveGeometry();
        markSourceDirtyAndUpdate();
    }
    void onMapSet() override
    {
        regenerateCache();
        markSourceDirtyAndUpdate();
    }
    void onGeoGeometryChanged() override
    {
        regenerateCache();
        preserveGeometry();
        markSourceDirtyAndUpdate();
    }
    void onGeoGeometryUpdated() override
    {
        updateCache();
        preserveGeometry();
        markSourceDirtyAndUpdate();
    }
    void onItemGeometryChanged() override
    {
        onGeoGeometryChanged();
    }
    void updatePolish() override
    {
        if (m_poly.m_geopath.path().length() < 2) { // Possibly cleared
            m_geometry.clear();
            m_poly.setWidth(0);
            m_poly.setHeight(0);
            return;
        }
        QScopedValueRollback<bool> rollback(m_poly.m_updatingGeometry);
        m_poly.m_updatingGeometry = true;

        const QGeoMap *map = m_poly.map();
        const qreal borderWidth = m_poly.m_line.width();

        m_geometry.updateSourcePoints(*map, m_geopathProjected, m_poly.m_geopath.boundingGeoRectangle().topLeft());
        m_geometry.updateScreenPoints(*map, borderWidth);

        m_poly.setWidth(m_geometry.sourceBoundingBox().width() + borderWidth);
        m_poly.setHeight(m_geometry.sourceBoundingBox().height() + borderWidth);

        m_poly.setPositionOnMap(m_geometry.origin(), -1 * m_geometry.sourceBoundingBox().topLeft()
                                + QPointF(borderWidth, borderWidth) * 0.5 ); // it has to be shifted so that the center of the line is on the correct geocoord
    }
    QSGNode *updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData * /*data*/) override
    {
        if (!m_node || !oldNode) {
            m_node = new MapPolylineNode();
            if (oldNode) {
                delete oldNode;
                oldNode = nullptr;
            }
        } else {
            m_node = static_cast<MapPolylineNode *>(oldNode);
        }

        //TODO: update only material
        if (m_geometry.isScreenDirty() || m_poly.m_dirtyMaterial || !oldNode) {
            m_node->update(m_poly.m_line.color(), &m_geometry);
            m_geometry.setPreserveGeometry(false);
            m_geometry.markClean();
            m_poly.m_dirtyMaterial = false;
        }
        return m_node;
    }
    bool contains(const QPointF &point) const override
    {
        return m_geometry.contains(point);
    }

    QList<QDoubleVector2D> m_geopathProjected;
    QGeoMapPolylineGeometry m_geometry;
    MapPolylineNode *m_node = nullptr;
};

class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolylineMapItemPrivateOpenGLLineStrip: public QDeclarativePolylineMapItemPrivate
{
public:

    QDeclarativePolylineMapItemPrivateOpenGLLineStrip(QDeclarativePolylineMapItem &poly) : QDeclarativePolylineMapItemPrivate(poly)
    {
    }

    QDeclarativePolylineMapItemPrivateOpenGLLineStrip(QDeclarativePolylineMapItemPrivate &other)
    : QDeclarativePolylineMapItemPrivate(other)
    {
    }

    ~QDeclarativePolylineMapItemPrivateOpenGLLineStrip() override;
    void onLinePropertiesChanged() override
    {
        afterViewportChanged();
    }
    void markSourceDirtyAndUpdate() override
    {
        m_geometry.markSourceDirty();
        m_poly.polishAndUpdate();
    }
    void preserveGeometry()
    {
        m_geometry.setPreserveGeometry(true, m_poly.m_geopath.boundingGeoRectangle().topLeft());
    }
    void onMapSet() override
    {
        markSourceDirtyAndUpdate();
    }
    void onGeoGeometryChanged() override
    {
        preserveGeometry();
        markSourceDirtyAndUpdate();
    }
    void onGeoGeometryUpdated() override
    {
        preserveGeometry();
        markSourceDirtyAndUpdate();
    }
    void onItemGeometryChanged() override
    {
        onGeoGeometryChanged();
    }
    void afterViewportChanged() override
    {
        preserveGeometry();
        m_poly.polishAndUpdate();
    }
    bool contains(const QPointF &point) const override
    {
        return m_geometry.contains(m_poly.mapToItem(m_poly.quickMap(), point),
                                   m_poly.line()->width(),
                                   static_cast<const QGeoProjectionWebMercator&>(m_poly.map()->geoProjection()));
    }
    void updatePolish() override
    {
        if (m_poly.m_geopath.path().length() == 0) { // Possibly cleared
            m_geometry.clear();
            m_geometry.clear();
            m_poly.setWidth(0);
            m_poly.setHeight(0);
            return;
        }

        QScopedValueRollback<bool> rollback(m_poly.m_updatingGeometry);
        m_poly.m_updatingGeometry = true;
        const qreal lineWidth = m_poly.m_line.width();
        m_geometry.updateSourcePoints(*m_poly.map(), m_poly.m_geopath);
        m_geometry.markScreenDirty();
        m_geometry.updateScreenPoints(*m_poly.map(), lineWidth);

        m_poly.setWidth(m_geometry.sourceBoundingBox().width());
        m_poly.setHeight(m_geometry.sourceBoundingBox().height());
        m_poly.setPosition(1.0 * m_geometry.firstPointOffset() - QPointF(lineWidth * 0.5,lineWidth * 0.5));
    }
    QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override
    {
        Q_UNUSED(data);

        if (!m_node || !oldNode) {
            m_node = new MapPolylineNodeOpenGLLineStrip();
            if (oldNode)
                delete oldNode;
        } else {
            m_node = static_cast<MapPolylineNodeOpenGLLineStrip *>(oldNode);
        }

        if (m_geometry.isScreenDirty() || m_poly.m_dirtyMaterial) {
            const QGeoMap *map = m_poly.map();
            const QMatrix4x4 &combinedMatrix = map->geoProjection().qsgTransform();
            const QDoubleVector3D &cameraCenter = map->geoProjection().centerMercator();
            m_node->update(m_poly.m_line.color(), // This updates only the material if the geometry is unchanged
                           m_poly.m_line.width(),
                         &m_geometry,
                         combinedMatrix,
                         cameraCenter);
            m_geometry.setPreserveGeometry(false);
            m_geometry.markClean();
            m_poly.m_dirtyMaterial = false;
        }
        return m_node;
    }

    QGeoMapPolylineGeometryOpenGL m_geometry;
    MapPolylineNodeOpenGLLineStrip *m_node = nullptr;
};

class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolylineMapItemPrivateOpenGLExtruded: public QDeclarativePolylineMapItemPrivateOpenGLLineStrip
{
public:

    QDeclarativePolylineMapItemPrivateOpenGLExtruded(QDeclarativePolylineMapItem &poly)
    : QDeclarativePolylineMapItemPrivateOpenGLLineStrip(poly)
    {
    }

    QDeclarativePolylineMapItemPrivateOpenGLExtruded(QDeclarativePolylineMapItemPrivate &other)
    : QDeclarativePolylineMapItemPrivateOpenGLLineStrip(other)
    {
    }

    ~QDeclarativePolylineMapItemPrivateOpenGLExtruded() override;

    QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override
    {
        Q_UNUSED(data);
        const QGeoMap *map = m_poly.map();
        const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map->geoProjection());
        const QMatrix4x4 &combinedMatrix = p.qsgTransform();
        const QDoubleVector3D &cameraCenter = p.centerMercator();
        const QColor &color = m_poly.m_line.color();
        const float lineWidth = m_poly.m_line.width();

        MapPolylineNodeOpenGLExtruded *nodeTri = nullptr;
        if (!m_nodeTri || !oldNode) {
            if (oldNode)
                delete oldNode;
            nodeTri = new MapPolylineNodeOpenGLExtruded();
        } else {
            nodeTri = static_cast<MapPolylineNodeOpenGLExtruded *>(oldNode);
        }

        //TODO: update only material
        if (m_geometry.isScreenDirty() || m_poly.m_dirtyMaterial) {
            nodeTri->update(color,
                         lineWidth  ,
                         &m_geometry,
                         combinedMatrix,
                         cameraCenter,
                         m_penCapStyle,
                         false,
                         m_poly.zoomForLOD(int(map->cameraData().zoomLevel())));
            m_geometry.setPreserveGeometry(false);
            m_geometry.markClean();
            m_poly.m_dirtyMaterial = false;
        }
        m_nodeTri = nodeTri;
        return nodeTri;
    }

    MapPolylineNodeOpenGLExtruded *m_nodeTri = nullptr;
};

QT_END_NAMESPACE

#endif // QDECLARATIVEPOLYLINEMAPITEM_P_P_H
