/*
    This file is part of Kig, a KDE program for Interactive Geometry.
    SPDX-FileCopyrightText: 2002-2003 Dominique Devriese <devriese@kde.org>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "kigpainter.h"

#include "../kig/kig_document.h"
#include "../kig/kig_view.h"
#include "../misc/goniometry.h"
#include "../objects/curve_imp.h"
#include "../objects/object_holder.h"
#include "../objects/point_imp.h"
#include "common.h"
#include "conic-common.h"
#include "coordinate_system.h"
#include "cubic-common.h"
#include "object_hierarchy.h"

#include <QPen>
#include <QPolygon>
#include <QTransform>

#include <algorithm>
#include <cmath>
#include <functional>
#include <stack>

using std::cos;
using std::fabs;
using std::sin;

KigPainter::KigPainter(const ScreenInfo &si, QPaintDevice *device, const KigDocument &doc, bool no)
    : mP(device)
    , color(Qt::blue)
    , style(Qt::SolidLine)
    , pointstyle(Kig::Round)
    , width(-1)
    , brushStyle(Qt::NoBrush)
    , brushColor(Qt::blue)
    , mdoc(doc)
    , msi(si)
    , mNeedOverlay(no)
    , overlayenlarge(0)
    , mSelected(false)
{
    mP.setBackground(QBrush(Qt::white));
}

KigPainter::~KigPainter()
{
}

void KigPainter::drawRect(const Rect &r)
{
    Rect rt = r.normalized();
    QRect qr = toScreen(rt).normalized();
    mP.drawRect(qr);
    if (mNeedOverlay)
        mOverlay.push_back(qr);
}

void KigPainter::drawRect(const QRect &r)
{
    mP.drawRect(r);
    if (mNeedOverlay)
        mOverlay.push_back(r);
}

void KigPainter::drawCircle(const Coordinate &center, double radius)
{
    Coordinate bottomLeft = center - Coordinate(radius, radius);
    Coordinate topRight = center + Coordinate(radius, radius);
    Rect r(bottomLeft, topRight);
    QRectF qr = toScreenF(r);
    mP.drawEllipse(qr);
    if (mNeedOverlay)
        circleOverlay(center, radius);
}

void KigPainter::drawSegment(const Coordinate &from, const Coordinate &to)
{
    QPointF tF = toScreenF(from), tT = toScreenF(to);
    mP.drawLine(tF, tT);
    if (mNeedOverlay)
        segmentOverlay(from, to);
}

void KigPainter::drawFatPoint(const Coordinate &p)
{
    int twidth = width == -1 ? 5 : width;
    mP.setPen(QPen(color, 1, style));
    switch (pointstyle) {
    case Kig::Round: {
        double radius = twidth * pixelWidth();
        setBrushStyle(Qt::SolidPattern);
        Coordinate rad(radius, radius);
        rad /= 2;
        Coordinate tl = p - rad;
        Coordinate br = p + rad;
        Rect r(tl, br);
        QRect qr = toScreen(r);
        mP.drawEllipse(qr);
        if (mNeedOverlay)
            mOverlay.push_back(qr);
        break;
    }
    case Kig::RoundEmpty: {
        double radius = twidth * pixelWidth();
        setBrushStyle(Qt::NoBrush);
        Coordinate rad(radius, radius);
        rad /= 2;
        Coordinate tl = p - rad;
        Coordinate br = p + rad;
        Rect r(tl, br);
        QRect qr = toScreen(r);
        mP.drawEllipse(qr);
        if (mNeedOverlay)
            mOverlay.push_back(qr);
        break;
    }
    case Kig::Rectangular: {
        double radius = twidth * pixelWidth();
        Coordinate rad(radius, radius);
        rad /= 2;
        Coordinate tl = p - rad;
        Coordinate br = p + rad;
        Rect r(tl, br);
        QRect qr = toScreen(r);
        mP.drawRect(qr);
        mP.fillRect(qr, QBrush(color, Qt::SolidPattern));
        if (mNeedOverlay)
            mOverlay.push_back(qr);
        break;
    }
    case Kig::RectangularEmpty: {
        double radius = twidth * pixelWidth();
        Coordinate rad(radius, radius);
        rad /= 2;
        Coordinate tl = p - rad;
        Coordinate br = p + rad;
        Rect r(tl, br);
        QRect qr = toScreen(r);
        mP.drawRect(qr);
        if (mNeedOverlay)
            mOverlay.push_back(qr);
        break;
    }
    case Kig::Cross: {
        double radius = twidth * pixelWidth();
        Coordinate rad(radius, radius);
        rad /= 2;
        Coordinate tl = p - rad;
        Coordinate br = p + rad;
        Rect r(tl, br);
        QRect qr = toScreen(r);
        mP.setPen(QPen(color, 2));
        mP.drawLine(qr.topLeft(), qr.bottomRight());
        mP.drawLine(qr.topRight(), qr.bottomLeft());
        if (mNeedOverlay)
            mOverlay.push_back(qr);
        break;
    }
    default: {
        break;
    }
    }
    mP.setPen(QPen(color, twidth, style));
}

void KigPainter::drawPoint(const Coordinate &p)
{
    mP.drawPoint(toScreen(p));
    if (mNeedOverlay)
        pointOverlay(p);
}

void KigPainter::drawLine(const Coordinate &p1, const Coordinate &p2)
{
    drawLine(LineData(p1, p2));
}

void KigPainter::drawText(const Rect &p, const QString &s, int textFlags)
{
    QRect t = toScreen(p);
    int tf = textFlags;
    t.translate(2, 2);
    t.setWidth(t.width() - 4);
    t.setHeight(t.height() - 4);
    mP.drawText(t, tf, s);
    if (mNeedOverlay)
        textOverlay(t, s, tf);
}

void KigPainter::textOverlay(const QRect &r, const QString &s, int textFlags)
{
    QRect newr(mP.boundingRect(r, textFlags, s));
    newr.setWidth(newr.width() + 4);
    newr.setHeight(newr.height() + 4);
    mOverlay.push_back(newr);
}

const Rect KigPainter::boundingRect(const Rect &r, const QString &s, int f) const
{
    QRect qr = mP.boundingRect(toScreen(r), f, s);
    qr.setWidth(qr.width() + 4);
    qr.setHeight(qr.height() + 4);
    return fromScreen(qr);
}

void KigPainter::setColor(const QColor &c)
{
    color = c;
    mP.setPen(QPen(color, width == -1 ? 1 : width, style));
}

void KigPainter::setStyle(Qt::PenStyle c)
{
    style = c;
    mP.setPen(QPen(color, width == -1 ? 1 : width, style));
}

void KigPainter::setWidth(int c)
{
    width = c;
    if (c > 0)
        overlayenlarge = c - 1;
    mP.setPen(QPen(color, width == -1 ? 1 : width, style));
}

void KigPainter::setPointStyle(Kig::PointStyle p)
{
    pointstyle = p;
}

void KigPainter::setPen(const QPen &p)
{
    color = p.color();
    width = p.width();
    style = p.style();
    mP.setPen(p);
}

void KigPainter::setBrush(const QBrush &b)
{
    brushStyle = b.style();
    brushColor = b.color();
    mP.setBrush(b);
}

void KigPainter::setBrushStyle(Qt::BrushStyle c)
{
    brushStyle = c;
    mP.setBrush(QBrush(brushColor, brushStyle));
}

void KigPainter::setBrushColor(const QColor &c)
{
    brushColor = c;
    mP.setBrush(QBrush(brushColor, brushStyle));
}

void KigPainter::setFont(const QFont &f)
{
    mP.setFont(f);
}

bool KigPainter::getNightVision() const
{
    return mdoc.getNightVision();
}

QColor KigPainter::getColor() const
{
    return color;
}

void KigPainter::setSelected(bool selected)
{
    mSelected = selected;
}

/*
static void setContains( QRect& r, const QPoint& p )
{
  if ( r.left() > p.x() ) r.setLeft( p.x() );
  if ( r.right() < p.x() ) r.setRight( p.x() );
  // this is correct, i think.  In qt the bottom has the highest y
  // coord...
  if ( r.bottom() > p.y() ) r.setBottom( p.y() );
  if ( r.top() < p.y() ) r.setTop( p.y() );
}
*/

void KigPainter::drawPolygon(const std::vector<QPoint> &pts, Qt::FillRule fillRule)
{
    QPen oldpen = mP.pen();
    QBrush oldbrush = mP.brush();
    QColor alphacolor = color;
    if (!mSelected)
        alphacolor.setAlpha(100);
    setBrush(QBrush(alphacolor, Qt::SolidPattern));
    setPen(Qt::NoPen);
    // i know this isn't really fast, but i blame it all on Qt with its
    // stupid container classes... what's wrong with the STL ?
    QPolygon t(pts.size());
    int c = 0;
    for (std::vector<QPoint>::const_iterator i = pts.begin(); i != pts.end(); ++i) {
        t.putPoints(c++, 1, i->x(), i->y());
    };
    mP.drawPolygon(t, fillRule);
    setPen(oldpen);
    setBrush(oldbrush);
    unsetSelected();
    if (mNeedOverlay)
        mOverlay.push_back(t.boundingRect());
}

void KigPainter::drawArea(const std::vector<Coordinate> &pts, bool border)
{
    QPen oldpen = mP.pen();
    QBrush oldbrush = mP.brush();
    setBrush(QBrush(color, Qt::SolidPattern));
    if (border)
        setPen(QPen(color, width == -1 ? 1 : width));
    else
        setPen(Qt::NoPen);
    QPolygon t(pts.size());
    int c = 0;
    for (std::vector<Coordinate>::const_iterator i = pts.begin(); i != pts.end(); ++i) {
        QPoint p = toScreen(*i);
        t.putPoints(c++, 1, p.x(), p.y());
    }
    mP.drawPolygon(t);
    setPen(oldpen);
    setBrush(oldbrush);
    if (mNeedOverlay)
        mOverlay.push_back(t.boundingRect());
}

Rect KigPainter::window()
{
    return msi.shownRect();
}

void KigPainter::circleOverlayRecurse(const Coordinate &centre, double radiussq, const Rect &cr)
{
    Rect currentRect = cr.normalized();

    if (!currentRect.intersects(window()))
        return;

    // this code is an adaptation of Marc Bartsch's code, from KGeo
    Coordinate tl = currentRect.topLeft();
    Coordinate br = currentRect.bottomRight();
    Coordinate tr = currentRect.topRight();
    Coordinate bl = currentRect.bottomLeft();
    Coordinate c = currentRect.center();

    // mp: we compute the minimum and maximum distance from the center
    // of the circle and this rect
    double distxmin = 0, distxmax = 0, distymin = 0, distymax = 0;
    if (centre.x >= tr.x)
        distxmin = centre.x - tr.x;
    if (centre.x <= bl.x)
        distxmin = bl.x - centre.x;
    if (centre.y >= tr.y)
        distymin = centre.y - tr.y;
    if (centre.y <= bl.y)
        distymin = bl.y - centre.y;
    distxmax = fabs(centre.x - c.x) + currentRect.width() / 2;
    distymax = fabs(centre.y - c.y) + currentRect.height() / 2;
    // this should take into account the thickness of the line...
    distxmin -= pixelWidth();
    if (distxmin < 0)
        distxmin = 0;
    distxmax += pixelWidth();
    distymin -= pixelWidth();
    if (distymin < 0)
        distymin = 0;
    distymax += pixelWidth();
    double distminsq = distxmin * distxmin + distymin * distymin;
    double distmaxsq = distxmax * distxmax + distymax * distymax;

    // if the circle doesn't touch this rect, we return
    // too far from the centre
    if (distminsq > radiussq)
        return;

    // too near to the centre
    if (distmaxsq < radiussq)
        return;

    // the rect contains some of the circle
    // -> if it's small enough, we keep it
    if (currentRect.width() < overlayRectSize()) {
        mOverlay.push_back(toScreenEnlarge(currentRect));
    } else {
        // this func works recursive: we subdivide the current rect, and if
        // it is of a good size, we keep it, otherwise we handle it again
        double width = currentRect.width() / 2;
        double height = currentRect.height() / 2;
        Rect r1(c, -width, -height);
        r1.normalize();
        circleOverlayRecurse(centre, radiussq, r1);
        Rect r2(c, width, -height);
        r2.normalize();
        circleOverlayRecurse(centre, radiussq, r2);
        Rect r3(c, -width, height);
        r3.normalize();
        circleOverlayRecurse(centre, radiussq, r3);
        Rect r4(c, width, height);
        r4.normalize();
        circleOverlayRecurse(centre, radiussq, r4);
    };
}

void KigPainter::circleOverlay(const Coordinate &centre, double radius)
{
    double t = radius + pixelWidth();
    Coordinate r(t, t);
    Coordinate bottomLeft = centre - r;
    Coordinate topRight = centre + r;
    Rect rect(bottomLeft, topRight);
    circleOverlayRecurse(centre, radius * radius, rect);
}

void KigPainter::segmentOverlay(const Coordinate &p1, const Coordinate &p2)
{
    // this code is based upon what Marc Bartsch wrote for KGeo

    // some stuff we may need:
    Coordinate p3 = p2 - p1;
    Rect border = window();
    //  double length = p3.length();
    // mp: using the l-infinity distance is more natural here
    double length = fabs(p3.x);
    if (fabs(p3.y) > length)
        length = fabs(p3.y);
    if (length < pixelWidth()) {
        // hopefully prevent SIGZERO's
        mOverlay.push_back(toScreen(Rect(p1, p2)));
        return;
    };
    p3 *= overlayRectSize();
    p3 /= length;

    int counter = 0;

    Rect r(p1, p2);
    r.normalize();

    for (;;) {
        Rect tR(Coordinate(0, 0), overlayRectSize(), overlayRectSize());
        Coordinate tP = p1 + p3 * counter;
        tR.setCenter(tP);
        if (!tR.intersects(r)) {
            break;
        }
        if (tR.intersects(border))
            mOverlay.push_back(toScreenEnlarge(tR));
        if (++counter > 100) {
            qDebug() << "counter got too big :( ";
            break;
        }
    }
}

double KigPainter::overlayRectSize()
{
    return 20 * pixelWidth();
}

void KigPainter::unsetSelected()
{
    mSelected = false;
}

void KigPainter::pointOverlay(const Coordinate &p1)
{
    Rect r(p1, 3 * pixelWidth(), 3 * pixelWidth());
    r.setCenter(p1);
    mOverlay.push_back(toScreen(r));
}

double KigPainter::pixelWidth()
{
    return msi.pixelWidth();
}

void KigPainter::setWholeWinOverlay()
{
    mOverlay.clear();
    mOverlay.push_back(mP.viewport());
    // don't accept any more overlay's...
    mNeedOverlay = false;
}

QPoint KigPainter::toScreen(const Coordinate &p) const
{
    return msi.toScreen(p);
}

QPointF KigPainter::toScreenF(const Coordinate &p) const
{
    return msi.toScreenF(p);
}

void KigPainter::drawGrid(const CoordinateSystem &c, bool showGrid, bool showAxes)
{
    c.drawGrid(*this, showGrid, showAxes);
    setWholeWinOverlay();
}

void KigPainter::drawObject(const ObjectHolder *o, bool ss)
{
    o->draw(*this, ss);
}

void KigPainter::drawObjects(const std::vector<ObjectHolder *> &os, bool sel)
{
    drawObjects(os.begin(), os.end(), sel);
}

void KigPainter::drawFilledRect(const QRect &r)
{
    QPen pen(Qt::black, 1, Qt::DotLine);
    setPen(pen);
    setBrush(QBrush(Qt::cyan, Qt::Dense6Pattern));
    drawRect(r.normalized());
}

void KigPainter::drawTextStd(const QPoint &p, const QString &s)
{
    if (s.isNull())
        return;
    // tf = text formatting flags
    int tf = Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip | Qt::TextWordWrap;
    // we need the rect where we're going to paint text
    setPen(QPen(Qt::blue, 1, Qt::SolidLine));
    setBrush(Qt::NoBrush);
    drawText(Rect(msi.fromScreen(p), window().bottomRight()).normalized(), s, tf);
}

QRect KigPainter::toScreen(const Rect &r) const
{
    return msi.toScreen(r);
}

QRectF KigPainter::toScreenF(const Rect &r) const
{
    return msi.toScreenF(r);
}

QRect KigPainter::toScreenEnlarge(const Rect &r) const
{
    if (overlayenlarge == 0)
        return msi.toScreen(r);

    QRect qr = msi.toScreen(r);
    qr.translate(-overlayenlarge, -overlayenlarge);
    int w = qr.width();
    int h = qr.height();
    qr.setWidth(w + 2 * overlayenlarge);
    qr.setHeight(h + 2 * overlayenlarge);
    return qr;
}

void KigPainter::drawSimpleText(const Coordinate &c, const QString &s)
{
    int tf = Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip | Qt::TextWordWrap;
    drawText(c, s, tf);
}

void KigPainter::drawText(const Coordinate &p, const QString &s, int textFlags)
{
    drawText(Rect(p, mP.window().right(), mP.window().top()), s, textFlags);
}
const Rect KigPainter::simpleBoundingRect(const Coordinate &c, const QString &s)
{
    int tf = Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip | Qt::TextWordWrap;
    return boundingRect(c, s, tf);
}

const Rect KigPainter::boundingRect(const Coordinate &c, const QString &s, int f) const
{
    return boundingRect(Rect(c, mP.window().right(), mP.window().top()), s, f);
}

Coordinate KigPainter::fromScreen(const QPoint &p) const
{
    return msi.fromScreen(p);
}

Rect KigPainter::fromScreen(const QRect &r) const
{
    return msi.fromScreen(r);
}

void KigPainter::drawRay(const Coordinate &a, const Coordinate &b)
{
    Coordinate tb = b;
    calcRayBorderPoints(a, tb, window());
    drawSegment(a, tb);
}

typedef std::pair<double, Coordinate> coordparampair;

struct workitem {
    workitem(const coordparampair &f, const coordparampair &s, Rect *o)
        : first(f)
        , second(s)
        , overlay(o)
    {
    }
    coordparampair first;
    coordparampair second;
    Rect *overlay;
};

void KigPainter::drawLine(const LineData &d)
{
    if (d.a != d.b) {
        LineData l = calcBorderPoints(d, window());
        drawSegment(l.a, l.b);
    }
}

void KigPainter::drawSegment(const LineData &d)
{
    drawSegment(d.a, d.b);
}

void KigPainter::drawRay(const LineData &d)
{
    drawRay(d.a, d.b);
}

void KigPainter::drawAngle(const Coordinate &point, double startangle, double angle, int radius)
{
    const int startangleDegrees = static_cast<int>(Goniometry::convert(startangle, Goniometry::Rad, Goniometry::Deg));
    const int angleDegrees = static_cast<int>(Goniometry::convert(angle, Goniometry::Rad, Goniometry::Deg));

    QPoint screenPoint = toScreen(point);
    QRect surroundingRect(0, 0, radius * 2, radius * 2);
    surroundingRect.moveCenter(screenPoint);

    mP.drawArc(surroundingRect, 16 * startangleDegrees, 16 * angleDegrees);

    // now for the arrow...
    QPoint end(static_cast<int>(screenPoint.x() + radius * cos(startangle + angle)), static_cast<int>(screenPoint.y() - radius * sin(startangle + angle)));
    QPoint vect = (end - screenPoint);
    double vectlen = std::sqrt(float(vect.x() * vect.x() + vect.y() * vect.y()));
    QPoint orthvect(-vect.y(), vect.x());
    vect = vect * 6 / vectlen;
    orthvect = orthvect * 6 / vectlen;

    QPolygon arrow(3);
    arrow.setPoint(0, end);
    arrow.setPoint(1, end + orthvect + vect);
    arrow.setPoint(2, end + orthvect - vect);

    setBrushStyle(Qt::SolidPattern);
    mP.drawPolygon(arrow);

    //  if ( mNeedOverlay ) mOverlay.push_back( toScreen( r ) );
    setWholeWinOverlay(); // mp: ugly! why not compute a correct overlay?
                          //     mOverlay.push_back( arrow.boundingRect() );
}

void KigPainter::drawRightAngle(const Coordinate &point, double startangle, int diagonal)
{
    const int startangleDegrees = static_cast<int>(Goniometry::convert(startangle, Goniometry::Rad, Goniometry::Deg));
    QPolygon rightAnglePolygon;
    QTransform rotationMatrix;
    int halfSide = diagonal * sin(M_PI / 4);
    const QPoint screenPoint = toScreen(point);

    rightAnglePolygon << QPoint(halfSide, 0) << QPoint(halfSide, -halfSide) << QPoint(0, -halfSide);

    rotationMatrix.rotate(-startangleDegrees);
    rightAnglePolygon = rotationMatrix.map(rightAnglePolygon);
    rightAnglePolygon.translate(screenPoint);

    mP.drawPolyline(rightAnglePolygon);

    setWholeWinOverlay();
}

void KigPainter::drawPolygon(const std::vector<Coordinate> &pts, Qt::FillRule fillRule)
{
    using namespace std;
    vector<QPoint> points;
    for (uint i = 0; i < pts.size(); ++i)
        points.push_back(toScreen(pts[i]));
    drawPolygon(points, fillRule);
}

void KigPainter::drawVector(const Coordinate &a, const Coordinate &b)
{
    // bugfix...
    if (a == b)
        return;
    // the segment
    drawSegment(a, b);
    // the arrows...
    Coordinate dir = b - a;
    Coordinate perp(dir.y, -dir.x);
    double length = perp.length();
    perp *= 10 * pixelWidth();
    perp /= length;
    dir *= 10 * pixelWidth();
    dir /= length;
    Coordinate c = b - dir + perp;
    Coordinate d = b - dir - perp;
    // draw the arrow lines with a normal style
    mP.setPen(QPen(color, width == -1 ? 1 : width, Qt::SolidLine));
    drawSegment(b, c);
    drawSegment(b, d);
    // setting again the original style
    mP.setPen(QPen(color, width == -1 ? 1 : width, style));
}

/* *** this function is commented out ***
inline Coordinate locusGetCoord( double p, const CurveImp* curve, const ObjectHierarchy& h,
                                 bool& valid, const KigDocument& doc )
{
  Coordinate pt = curve->getPoint( p, valid, doc );
  if ( ! valid ) return Coordinate();
  PointImp pimp( pt );
  Args args;
  args.push_back( &pimp );
  std::vector<ObjectImp*> calced = h.calc( args, doc );
  assert( calced.size() == 1 );
  ObjectImp* o = calced.front();
  Coordinate ret;
  if ( o->inherits( ObjectImp::ID_PointImp ) )
  {
    valid = true;
    ret = static_cast<PointImp*>( o )->coordinate();
  }
  else
    valid = false;
  delete o;
  return ret;
};
*/

class CurveImpPointCalcer
{
    const CurveImp *curve;

public:
    CurveImpPointCalcer(const CurveImp *c)
        : curve(c)
    {
    }
    static const double endinterval;
    inline const Coordinate getPoint(double param, const KigDocument &d) const
    {
        return curve->getPoint(param, d);
    }
};

const double CurveImpPointCalcer::endinterval = 1.;

void KigPainter::drawCurve(const CurveImp *curve)
{
    // we manage our own overlay
    bool tNeedOverlay = mNeedOverlay;
    mNeedOverlay = false;

    QPen pen = mP.pen();

    // this stack contains pairs of Coordinates ( parameter intervals )
    // that we still need to process:
    std::stack<workitem> workstack;
    // mp: this stack contains all the generated overlays:
    // the strategy for generating the overlay structure is the same
    // recursive-like used to draw the segments: a new rectangle is
    // generated whenever the length of a segment becomes lower than
    // overlayRectSize(), or if the segment would be drawn anyway
    // to avoid strange things from happening we impose that the distance
    // in parameter space be less than a threshold before generating
    // any overlay.
    //
    // The third parameter in workitem is a pointer into a stack of
    // all generated rectangles (in real coordinate space); if 0
    // there is no rectangles associated to that segment yet.
    //
    // Using the final mOverlay stack would be much more efficient, but
    // 1. needs transformations into window space
    // 2. would be more difficult to drop rectangles not intersecting
    //    the window.
    std::stack<Rect> overlaystack;

    // mp: the original version in which an initial set of 20 intervals
    // were pushed onto the stack is replaced by a single interval and
    // by forcing subdivision till h < hmax (with more or less the same
    // final result).
    // First push the [0,1] interval into the stack:

    Coordinate coo1 = curve->getPoint(0., mdoc);
    Coordinate coo2 = curve->getPoint(1., mdoc);
    workstack.push(workitem(coordparampair(0., coo1), coordparampair(1., coo2), nullptr));

    // maxlength is the square of the maximum size that we allow
    // between two points..
    double maxlength = 1.5 * pixelWidth();
    maxlength *= maxlength;
    // error squared is required to be less that sigma (half pixel)
    double sigma = maxlength / 4;
    // distance between two parameter values cannot be too small
    double hmin = 3e-5;
    // distance between two parameter values cannot be too large
    double hmax = 1. / 40;
    double hmaxoverlay = 1. / 8;

    int count = 1; // the number of segments we've already
                   // visited...
    static const int maxnumberofpoints = 1000;

    const Rect &sr = window();

    // what this algorithm does is approximating the curve with a set of
    // segments.  we don't draw the individual segments, but use
    // QPainter::drawPolyline() so that the line styles work properly.
    // Possibly there are performance advantages as well ?  this array
    // is a buffer of the polyline approximation of the part of the
    // curve that we are currently processing.
    QPolygon curpolyline(1000);
    int curpolylinenextfree = 0;

    // we don't use recursion, but a stack based approach for efficiency
    // concerns...
    while (!workstack.empty() && count < maxnumberofpoints) {
        workitem curitem = workstack.top();
        workstack.pop();
        bool curitemok = true;
        while (curitemok && count++ < maxnumberofpoints) {
            double t0 = curitem.first.first;
            double t1 = curitem.second.first;
            Coordinate p0 = curitem.first.second;
            bool valid0 = p0.valid();
            Coordinate p1 = curitem.second.second;
            bool valid1 = p1.valid();

            // we take the middle parameter of the two previous points...
            double t2 = (t0 + t1) / 2;
            double h = fabs(t1 - t0) / 2;

            // if exactly one of the two endpoints is invalid, then
            // we prefer to find an internal value of the parameter
            // separating valid points from invalid points.  We use
            // a bisection strategy (this is not implemented yet!)
            //      if ( ( valid0 && ! valid1 ) || ( valid1 && ! valid0 ) )
            //      {
            //	while ( h >= hmin )
            //	{
            //	  .......................................
            //	}
            //      }

            Rect *overlaypt = curitem.overlay;
            Coordinate p2 = curve->getPoint(t2, mdoc);
            bool allvalid = p2.valid() && valid0 && valid1;
            bool dooverlay =
                !overlaypt && h < hmaxoverlay && valid0 && valid1 && fabs(p0.x - p1.x) <= overlayRectSize() && fabs(p0.y - p1.y) <= overlayRectSize();
            bool addn = sr.contains(p2) || h >= hmax;
            // estimated error between the curve and the segments
            double errsq = 1e21;
            if (allvalid)
                errsq = (0.5 * p0 + 0.5 * p1 - p2).squareLength();
            errsq /= 4;
            curitemok = false;
            //      bool dodraw = allvalid && h < hmax && ( errsq < sigma || h < hmin );
            bool dodraw = allvalid && h < hmax && errsq < sigma;
            if (tNeedOverlay && (dooverlay || dodraw)) {
                Rect newoverlay(p0, p1);
                overlaystack.push(newoverlay);
                overlaypt = &overlaystack.top();
            }
            if (overlaypt)
                overlaypt->setContains(p2);
            if (dodraw) {
                // draw the two segments
                QPoint tp0 = toScreen(p0);
                QPoint tp1 = toScreen(p1);
                QPoint tp2 = toScreen(p2);
                if (curpolylinenextfree > 0 && curpolyline[curpolylinenextfree - 1] != tp1) {
                    // flush the current part of the curve
                    mP.drawPolyline(curpolyline.constData(), curpolylinenextfree);
                    curpolylinenextfree = 0;
                }
                if (curpolylinenextfree == 0)
                    curpolyline[curpolylinenextfree++] = tp1;
                curpolyline[curpolylinenextfree++] = tp2;
                curpolyline[curpolylinenextfree++] = tp0;
            } else if (h >= hmin) // we do not continue to subdivide indefinitely!
            {
                // push into stack in order to process both subintervals
                if (addn || (valid0 && sr.contains(p0)))
                    workstack.push(workitem(curitem.first, coordparampair(t2, p2), overlaypt));
                if (addn || (valid1 && sr.contains(p1))) {
                    curitem = workitem(coordparampair(t2, p2), curitem.second, overlaypt);
                    curitemok = true;
                }
            }
        }
    }
    // flush the rest of the curve
    mP.drawPolyline(curpolyline.constData(), curpolylinenextfree);
    curpolylinenextfree = 0;

    if (!workstack.empty())
        qDebug() << "Stack not empty in KigPainter::drawCurve!\n";
    assert(tNeedOverlay || overlaystack.empty());
    if (tNeedOverlay) {
        Rect border = window();
        while (!overlaystack.empty()) {
            Rect overlay = overlaystack.top();
            overlaystack.pop();
            if (overlay.intersects(border))
                mOverlay.push_back(toScreenEnlarge(overlay));
        }
    }
    mNeedOverlay = tNeedOverlay;
}

void KigPainter::drawTextFrame(const Rect &frame, const QString &s, bool needframe)
{
    QPen oldpen = mP.pen();
    QBrush oldbrush = mP.brush();
    if (needframe) {
        // inspired upon kgeo, thanks to Marc Bartsch, i've taken some of
        // his code too..
        setPen(QPen(Qt::black, 1));
        setBrush(QBrush(QColor(255, 255, 222)));
        drawRect(frame);
        setPen(QPen(QColor(197, 194, 197), 1, Qt::SolidLine));

        QRect qr = toScreen(frame);

        mP.drawLine(qr.topLeft(), qr.topRight());
        mP.drawLine(qr.topLeft(), qr.bottomLeft());
    };
    setPen(oldpen);
    setBrush(oldbrush);
    drawText(frame, s, Qt::AlignVCenter | Qt::AlignLeft);
}

void KigPainter::drawArc(const Coordinate &center, double radius, double dstartangle, double dangle)
{
    // convert to 16th of degrees...
    const int startangle = static_cast<int>(Goniometry::convert(16 * dstartangle, Goniometry::Rad, Goniometry::Deg));
    const int angle = static_cast<int>(Goniometry::convert(16 * dangle, Goniometry::Rad, Goniometry::Deg));

    if (angle <= 16) // in this case just draw a segment
    {
        Coordinate a = center + radius * Coordinate(cos(dstartangle), sin(dstartangle));
        Coordinate b = center + radius * Coordinate(cos(dstartangle + dangle), sin(dstartangle + dangle));
        drawSegment(a, b);
    } else {
        Rect krect(0, 0, 2 * radius, 2 * radius);
        krect.setCenter(center);
        QRectF rect = toScreenF(krect);

        mP.drawArc(rect, startangle, angle);
        setWholeWinOverlay();
    }
}
