/*!
    \internal
*/
void QGeoMapPolygonGeometry::updateSourcePoints(const QGeoMap &map,
                                                const QList<QGeoCoordinate> &path)
{
    if (!sourceDirty_)
        return;

    qreal minX = -1.0;

    // build the actual path
    QPointF origin;
    QPointF lastPoint;
    srcPath_ = QPainterPath();

    double unwrapBelowX = 0;
    if (preserveGeometry_ )
        unwrapBelowX = map.coordinateToScreenPosition(geoLeftBound_, false).x();

    for (int i = 0; i < path.size(); ++i) {
        const QGeoCoordinate &coord = path.at(i);

        if (!coord.isValid())
            continue;

        QPointF point = map.coordinateToScreenPosition(coord, false);

        // We can get NaN if the map isn't set up correctly, or the projection
        // is faulty -- probably best thing to do is abort
        if (!qIsFinite(point.x()) || !qIsFinite(point.y()))
            return;

        // unwrap x to preserve geometry if moved to border of map
        if (preserveGeometry_ && point.x() < unwrapBelowX && !qFuzzyCompare(point.x(), unwrapBelowX))
            point.setX(unwrapBelowX + geoDistanceToScreenWidth(map, geoLeftBound_, coord));

        if (i == 0) {
            origin = point;
            minX = point.x();
            srcOrigin_ = coord;
            srcPath_.moveTo(point - origin);
            lastPoint = point;
        } else {
            if (point.x() <= minX)
                minX = point.x();
            const QPointF diff = (point - lastPoint);
            if (diff.x() * diff.x() + diff.y() * diff.y() >= 3.0) {
                srcPath_.lineTo(point - origin);
                lastPoint = point;
            }
        }
    }

    srcPath_.closeSubpath();

    if (!assumeSimple_)
        srcPath_ = srcPath_.simplified();

    sourceBounds_ = srcPath_.boundingRect();
    geoLeftBound_ = map.screenPositionToCoordinate(QPointF(minX, 0), false);
}
/*!
    \internal
*/
void QGeoMapRectangleGeometry::updatePoints(const QGeoMap &map,
                                            const QGeoCoordinate &topLeft,
                                            const QGeoCoordinate &bottomRight)
{
    if (!screenDirty_ && !sourceDirty_)
        return;

    QPointF tl = map.coordinateToScreenPosition(topLeft, false);
    QPointF br = map.coordinateToScreenPosition(bottomRight, false);

    // We can get NaN if the map isn't set up correctly, or the projection
    // is faulty -- probably best thing to do is abort
    if (!qIsFinite(tl.x()) || !qIsFinite(tl.y()))
        return;
    if (!qIsFinite(br.x()) || !qIsFinite(br.y()))
        return;

    if ( preserveGeometry_ ) {
        double unwrapBelowX = map.coordinateToScreenPosition(geoLeftBound_, false).x();
        if (br.x() < unwrapBelowX)
            br.setX(tl.x() + screenBounds_.width());
    }


    QRectF re(tl, br);
    re.translate(-1 * tl);

    clear();
    screenVertices_.reserve(6);

    screenVertices_ << re.topLeft();
    screenVertices_ << re.topRight();
    screenVertices_ << re.bottomLeft();

    screenVertices_ << re.topRight();
    screenVertices_ << re.bottomLeft();
    screenVertices_ << re.bottomRight();

    firstPointOffset_ = QPointF(0,0);
    srcOrigin_ = topLeft;
    screenBounds_ = re;

    screenOutline_ = QPainterPath();
    screenOutline_.addRect(re);

    geoLeftBound_ = topLeft;
}
/*!
    \internal
*/
void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map,
                                                 qreal strokeWidth)
{
    if (!screenDirty_)
        return;

    QPointF origin = map.coordinateToScreenPosition(srcOrigin_, false).toPointF();

    if (!qIsFinite(origin.x()) || !qIsFinite(origin.y())) {
        clear();
        return;
    }

    // Create the viewport rect in the same coordinate system
    // as the actual points
    QRectF viewport(0, 0, map.width(), map.height());
    viewport.adjust(-strokeWidth, -strokeWidth, strokeWidth, strokeWidth);
    viewport.translate(-1 * origin);

    // Perform clipping to the viewport limits
    QVector<qreal> points;
    QVector<QPainterPath::ElementType> types;

    if (clipToViewport_) {
        clipPathToRect(srcPoints_, srcPointTypes_, viewport, points, types);
    } else {
        points = srcPoints_;
        types = srcPointTypes_;
    }

    QVectorPath vp(points.data(), types.size(), types.data());
    QTriangulatingStroker ts;
    ts.process(vp, QPen(QBrush(Qt::black), strokeWidth), viewport, QPainter::Qt4CompatiblePainting);

    clear();

    // Nothing is on the screen
    if (ts.vertexCount() == 0)
        return;

    // QTriangulatingStroker#vertexCount is actually the length of the array,
    // not the number of vertices
    screenVertices_.reserve(ts.vertexCount());

    screenOutline_ = QPainterPath();

    QPolygonF tri;
    const float *vs = ts.vertices();
    for (int i = 0; i < (ts.vertexCount()/2*2); i += 2) {
        screenVertices_ << QPointF(vs[i], vs[i + 1]);

        if (!qIsFinite(vs[i]) || !qIsFinite(vs[i + 1]))
            break;

        tri << QPointF(vs[i], vs[i + 1]);
        if (tri.size() == 4) {
            tri.remove(0);
            screenOutline_.addPolygon(tri);
        }
    }

    QRectF bb = screenOutline_.boundingRect();
    screenBounds_ = bb;
    this->translate( -1 * sourceBounds_.topLeft());
}
/*!
    \internal
*/
void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map,
                                                 const QList<QGeoCoordinate> &path)
{
    bool foundValid = false;
    double minX = -1.0;
    double minY = -1.0;
    double maxX = -1.0;
    double maxY = -1.0;

    if (!sourceDirty_)
        return;

    // clear the old data and reserve enough memory
    srcPoints_.clear();
    srcPoints_.reserve(path.size() * 2);
    srcPointTypes_.clear();
    srcPointTypes_.reserve(path.size());

    QDoubleVector2D origin, lastPoint, lastAddedPoint;

    double unwrapBelowX = 0;
    if (preserveGeometry_)
        unwrapBelowX = map.coordinateToScreenPosition(geoLeftBound_, false).x();

    for (int i = 0; i < path.size(); ++i) {
        const QGeoCoordinate &coord = path.at(i);

        if (!coord.isValid())
            continue;

        QDoubleVector2D point = map.coordinateToScreenPosition(coord, false);

        // We can get NaN if the map isn't set up correctly, or the projection
        // is faulty -- probably best thing to do is abort
        if (!qIsFinite(point.x()) || !qIsFinite(point.y()))
            return;

        // unwrap x to preserve geometry if moved to border of map
        if (preserveGeometry_ && point.x() < unwrapBelowX && !qFuzzyCompare(point.x(), unwrapBelowX))
            point.setX(unwrapBelowX + geoDistanceToScreenWidth(map, geoLeftBound_, coord));

        if (!foundValid) {
            foundValid = true;
            srcOrigin_ = coord;
            origin = point;
            point = QDoubleVector2D(0,0);

            minX = point.x();
            maxX = minX;
            minY = point.y();
            maxY = minY;

            srcPoints_ << point.x() << point.y();
            srcPointTypes_ << QPainterPath::MoveToElement;
            lastAddedPoint = point;
        } else {
            point -= origin;

            minX = qMin(point.x(), minX);
            minY = qMin(point.y(), minY);
            maxX = qMax(point.x(), maxX);
            maxY = qMax(point.y(), maxY);

            if ((point - lastAddedPoint).manhattanLength() > 3 ||
                    i == path.size() - 1) {
                srcPoints_ << point.x() << point.y();
                srcPointTypes_ << QPainterPath::LineToElement;
                lastAddedPoint = point;
            }
        }

        lastPoint = point;
    }

    sourceBounds_ = QRectF(QPointF(minX, minY), QPointF(maxX, maxY));
    geoLeftBound_ = map.screenPositionToCoordinate(
                                    QDoubleVector2D(minX + origin.x(), minY + origin.y()), false);
}
/*!
    \internal
*/
void QGeoMapPolygonGeometry::updateScreenPoints(const QGeoMap &map)
{
    if (!screenDirty_)
        return;

    if (map.width() == 0 || map.height() == 0) {
        clear();
        return;
    }

    QPointF origin = map.coordinateToScreenPosition(srcOrigin_, false);

    // Create the viewport rect in the same coordinate system
    // as the actual points
    QRectF viewport(0, 0, map.width(), map.height());
    viewport.translate(-1 * origin);

    QPainterPath vpPath;
    vpPath.addRect(viewport);

    QPainterPath ppi;
    if (clipToViewport_)
        ppi = srcPath_.intersected(vpPath); // get the clipped version of the path
    else ppi = srcPath_;

    clear();

    // a polygon requires at least 3 points;
    if (ppi.elementCount() < 3)
        return;

    // Intersection between the viewport and a concave polygon can create multiple polygons
    // joined by a line at the viewport border, and poly2tri does not triangulate this very well
    // so use the full src path if the resulting polygon is concave.
    if (clipToViewport_) {
        int changeInX = 0;
        int changeInY = 0;
        QPainterPath::Element e1 = ppi.elementAt(1);
        QPainterPath::Element e = ppi.elementAt(0);
        QVector2D edgeA(e1.x - e.x ,e1.y - e.y);
        for (int i = 2; i <= ppi.elementCount(); ++i) {
            e = ppi.elementAt(i % ppi.elementCount());
            if (e.x == e1.x && e.y == e1.y)
                continue;
            QVector2D edgeB(e.x - e1.x, e.y - e1.y);
            if ((edgeA.x() < 0) == (edgeB.x() >= 0))
                changeInX++;
            if ((edgeA.y() < 0) == (edgeB.y() >= 0))
                changeInY++;
            edgeA = edgeB;
            e1 = e;
        }
        if (changeInX > 2 || changeInY > 2) // polygon is concave
            ppi = srcPath_;
    }

    // translate the path into top-left-centric coordinates
    QRectF bb = ppi.boundingRect();
    ppi.translate(-bb.left(), -bb.top());
    firstPointOffset_ = -1 * bb.topLeft();

    ppi.closeSubpath();

    screenOutline_ = ppi;

    std::vector<p2t::Point*> curPts;
    curPts.reserve(ppi.elementCount());
    for (int i = 0; i < ppi.elementCount(); ++i) {
        const QPainterPath::Element e = ppi.elementAt(i);
        if (e.isMoveTo() || i == ppi.elementCount() - 1
                || (qAbs(e.x - curPts.front()->x) < 0.1
                    && qAbs(e.y - curPts.front()->y) < 0.1)) {
            if (curPts.size() > 2) {
                p2t::CDT *cdt = new p2t::CDT(curPts);
                cdt->Triangulate();
                std::vector<p2t::Triangle*> tris = cdt->GetTriangles();
                screenVertices_.reserve(screenVertices_.size() + int(tris.size()));
                for (size_t i = 0; i < tris.size(); ++i) {
                    p2t::Triangle *t = tris.at(i);
                    for (int j = 0; j < 3; ++j) {
                        p2t::Point *p = t->GetPoint(j);
                        screenVertices_ << Point(p->x, p->y);
                    }
                }
                delete cdt;
            }
            curPts.clear();
            curPts.reserve(ppi.elementCount() - i);
            curPts.push_back(new p2t::Point(e.x, e.y));
        } else if (e.isLineTo()) {
            curPts.push_back(new p2t::Point(e.x, e.y));
        } else {
            qWarning("Unhandled element type in polygon painterpath");
        }
    }

    if (curPts.size() > 0) {
        qDeleteAll(curPts.begin(), curPts.end());
        curPts.clear();
    }

    screenBounds_ = ppi.boundingRect();

}
/*!
    \internal
*/
void QGeoMapCircleGeometry::updateScreenPointsInvert(const QGeoMap &map)
{
    if (!screenDirty_)
        return;

    if (map.width() == 0 || map.height() == 0) {
        clear();
        return;
    }

    QPointF origin = map.coordinateToItemPosition(srcOrigin_, false).toPointF();

    QPainterPath ppi = srcPath_;

    clear();

    // a circle requires at least 3 points;
    if (ppi.elementCount() < 3)
        return;

    // translate the path into top-left-centric coordinates
    QRectF bb = ppi.boundingRect();
    ppi.translate(-bb.left(), -bb.top());
    firstPointOffset_ = -1 * bb.topLeft();

    ppi.closeSubpath();

    // calculate actual width of map on screen in pixels
    QGeoCoordinate mapCenter(0, map.cameraData().center().longitude());
    QDoubleVector2D midPoint = map.coordinateToItemPosition(mapCenter, false);
    QDoubleVector2D midPointPlusOne = QDoubleVector2D(midPoint.x() + 1.0, midPoint.y());
    QGeoCoordinate coord1 = map.itemPositionToCoordinate(midPointPlusOne, false);
    double geoDistance = coord1.longitude() - map.cameraData().center().longitude();
    if ( geoDistance < 0 )
        geoDistance += 360.0;
    double mapWidth = 360.0 / geoDistance;

    qreal leftOffset = origin.x() - (map.width()/2.0 - mapWidth/2.0) - firstPointOffset_.x();
    qreal topOffset = origin.y() - (midPoint.y() - mapWidth/2.0) - firstPointOffset_.y();
    QPainterPath ppiBorder;
    ppiBorder.moveTo(QPointF(-leftOffset, -topOffset));
    ppiBorder.lineTo(QPointF(mapWidth - leftOffset, -topOffset));
    ppiBorder.lineTo(QPointF(mapWidth - leftOffset, mapWidth - topOffset));
    ppiBorder.lineTo(QPointF(-leftOffset, mapWidth - topOffset));

    screenOutline_ = ppiBorder;

    std::vector<p2t::Point*> borderPts;
    borderPts.reserve(4);

    std::vector<p2t::Point*> curPts;
    curPts.reserve(ppi.elementCount());
    for (int i = 0; i < ppi.elementCount(); ++i) {
        const QPainterPath::Element e = ppi.elementAt(i);
        if (e.isMoveTo() || i == ppi.elementCount() - 1
                || (qAbs(e.x - curPts.front()->x) < 0.1
                    && qAbs(e.y - curPts.front()->y) < 0.1)) {
            if (curPts.size() > 2) {
                for (int j = 0; j < 4; ++j) {
                    const QPainterPath::Element e2 = ppiBorder.elementAt(j);
                    borderPts.push_back(new p2t::Point(e2.x, e2.y));
                }
                p2t::CDT *cdt = new p2t::CDT(borderPts);
                cdt->AddHole(curPts);
                cdt->Triangulate();
                std::vector<p2t::Triangle*> tris = cdt->GetTriangles();
                screenVertices_.reserve(screenVertices_.size() + int(tris.size()));
                for (size_t i = 0; i < tris.size(); ++i) {
                    p2t::Triangle *t = tris.at(i);
                    for (int j = 0; j < 3; ++j) {
                        p2t::Point *p = t->GetPoint(j);
                        screenVertices_ << QPointF(p->x, p->y);
                    }
                }
                delete cdt;
            }
            curPts.clear();
            curPts.reserve(ppi.elementCount() - i);
            curPts.push_back(new p2t::Point(e.x, e.y));
        } else if (e.isLineTo()) {
            curPts.push_back(new p2t::Point(e.x, e.y));
        } else {
            qWarning("Unhandled element type in circle painterpath");
        }
    }

    if (curPts.size() > 0) {
        qDeleteAll(curPts.begin(), curPts.end());
        curPts.clear();
    }

    if (borderPts.size() > 0) {
        qDeleteAll(borderPts.begin(), borderPts.end());
        borderPts.clear();
    }

    screenBounds_ = ppiBorder.boundingRect();

}