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

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

#include "common.h"

#include "../kig/kig_view.h"
#include "../objects/object_imp.h"

#include <cmath>
#include <limits>

#include <QInputDialog>

Coordinate calcPointOnPerpend(const LineData &l, const Coordinate &t)
{
    return calcPointOnPerpend(l.b - l.a, t);
}

Coordinate calcPointOnPerpend(const Coordinate &dir, const Coordinate &t)
{
    return t + (dir).orthogonal();
}

Coordinate calcPointOnParallel(const LineData &l, const Coordinate &t)
{
    return calcPointOnParallel(l.b - l.a, t);
}

Coordinate calcPointOnParallel(const Coordinate &dir, const Coordinate &t)
{
    return t + dir * 5;
}

Coordinate calcIntersectionPoint(const LineData &l1, const LineData &l2)
{
    const Coordinate &pa = l1.a;
    const Coordinate &pb = l1.b;
    const Coordinate &pc = l2.a;
    const Coordinate &pd = l2.b;

    double xab = pb.x - pa.x, xdc = pd.x - pc.x, xac = pc.x - pa.x, yab = pb.y - pa.y, ydc = pd.y - pc.y, yac = pc.y - pa.y;

    double det = xab * ydc - xdc * yab;
    double detn = xac * ydc - xdc * yac;

    // test for parallelism
    if (fabs(det) < 1e-6)
        return Coordinate::invalidCoord();
    double t = detn / det;

    return pa + t * (pb - pa);
}

void calcBorderPoints(Coordinate &p1, Coordinate &p2, const Rect &r)
{
    calcBorderPoints(p1.x, p1.y, p2.x, p2.y, r);
}

const LineData calcBorderPoints(const LineData &l, const Rect &r)
{
    LineData ret(l);
    calcBorderPoints(ret.a.x, ret.a.y, ret.b.x, ret.b.y, r);
    return ret;
}

void calcBorderPoints(double &xa, double &ya, double &xb, double &yb, const Rect &r)
{
    // we calc where the line through a(xa,ya) and b(xb,yb) intersects with r:
    double left = (xa == xb) ? -std::numeric_limits<double>::infinity() : (r.left() - xa) * (yb - ya) / (xb - xa) + ya;
    double right = (xa == xb) ? std::numeric_limits<double>::infinity() : (r.right() - xa) * (yb - ya) / (xb - xa) + ya;
    double top = (ya == yb) ? std::numeric_limits<double>::infinity() : (r.top() - ya) * (xb - xa) / (yb - ya) + xa;
    double bottom = (ya == yb) ? -std::numeric_limits<double>::infinity() : (r.bottom() - ya) * (xb - xa) / (yb - ya) + xa;

    // now we go looking for valid points
    int novp = 0; // number of valid points we have already found

    if (!(top < r.left() || top > r.right())) {
        // the line intersects with the top side of the rect.
        ++novp;
        xa = top;
        ya = r.top();
    };
    if (!(left < r.bottom() || left > r.top())) {
        // the line intersects with the left side of the rect.
        if (novp++) {
            xb = r.left();
            yb = left;
        } else {
            xa = r.left();
            ya = left;
        };
    };
    if (!(right < r.bottom() || right > r.top())) {
        // the line intersects with the right side of the rect.
        if (novp++) {
            xb = r.right();
            yb = right;
        } else {
            xa = r.right();
            ya = right;
        };
    };
    if (!(bottom < r.left() || bottom > r.right())) {
        // the line intersects with the bottom side of the rect.
        ++novp;
        xb = bottom;
        yb = r.bottom();
    };
    if (novp < 2) {
        // line is completely outside of the window...
        xa = ya = xb = yb = 0;
    };
}

void calcRayBorderPoints(const Coordinate &a, Coordinate &b, const Rect &r)
{
    calcRayBorderPoints(a.x, a.y, b.x, b.y, r);
}

void calcRayBorderPoints(const double xa, const double ya, double &xb, double &yb, const Rect &r)
{
    // we calc where the line through a(xa,ya) and b(xb,yb) intersects with r:
    double left = (r.left() - xa) * (yb - ya) / (xb - xa) + ya;
    double right = (r.right() - xa) * (yb - ya) / (xb - xa) + ya;
    double top = (r.top() - ya) * (xb - xa) / (yb - ya) + xa;
    double bottom = (r.bottom() - ya) * (xb - xa) / (yb - ya) + xa;

    // now we see which we can use...
    if (
        // the ray intersects with the top side of the rect..
        top >= r.left()
        && top <= r.right()
        // and b is above a
        && yb > ya) {
        xb = top;
        yb = r.top();
        return;
    };
    if (
        // the ray intersects with the left side of the rect...
        left >= r.bottom()
        && left <= r.top()
        // and b is on the left of a..
        && xb < xa) {
        xb = r.left();
        yb = left;
        return;
    };
    if (
        // the ray intersects with the right side of the rect...
        right >= r.bottom()
        && right <= r.top()
        // and b is to the right of a..
        && xb > xa) {
        xb = r.right();
        yb = right;
        return;
    };
    if (
        // the ray intersects with the bottom side of the rect...
        bottom >= r.left()
        && bottom <= r.right()
        // and b is under a..
        && yb < ya) {
        xb = bottom;
        yb = r.bottom();
        return;
    };
    qCritical() << "damn";
}

bool isOnLine(const Coordinate &o, const Coordinate &a, const Coordinate &b, const double fault)
{
    double x1 = a.x;
    double y1 = a.y;
    double x2 = b.x;
    double y2 = b.y;

    // check your math theory ( homogeneous coordinates ) for this
    double tmp = fabs(o.x * (y1 - y2) + o.y * (x2 - x1) + (x1 * y2 - y1 * x2));
    return tmp < (fault * (b - a).length());
    // if o is on the line ( if the determinant of the matrix
    //       |---|---|---|
    //       | x | y | z |
    //       |---|---|---|
    //       | x1| y1| z1|
    //       |---|---|---|
    //       | x2| y2| z2|
    //       |---|---|---|
    // equals 0, then p(x,y,z) is on the line containing points
    // p1(x1,y1,z1) and p2 here, we're working with normal coords, no
    // homogeneous ones, so all z's equal 1
}

bool isOnSegment(const Coordinate &o, const Coordinate &a, const Coordinate &b, const double fault)
{
    return isOnLine(o, a, b, fault)
        // not too far to the right
        && (o.x - kigMax(a.x, b.x) < fault)
        // not too far to the left
        && (kigMin(a.x, b.x) - o.x < fault)
        // not too high
        && (kigMin(a.y, b.y) - o.y < fault)
        // not too low
        && (o.y - kigMax(a.y, b.y) < fault);
}

bool isOnRay(const Coordinate &o, const Coordinate &a, const Coordinate &b, const double fault)
{
    return isOnLine(o, a, b, fault)
        // not too far in front of a horizontally..
        //    && ( a.x - b.x < fault ) == ( a.x - o.x < fault )
        && ((a.x < b.x) ? (a.x - o.x < fault) : (a.x - o.x > -fault))
        // not too far in front of a vertically..
        //    && ( a.y - b.y < fault ) == ( a.y - o.y < fault );
        && ((a.y < b.y) ? (a.y - o.y < fault) : (a.y - o.y > -fault));
}

bool isOnArc(const Coordinate &o, const Coordinate &c, const double r, const double sa, const double a, const double fault)
{
    if (fabs((c - o).length() - r) > fault)
        return false;
    Coordinate d = o - c;
    double angle = atan2(d.y, d.x);

    if (angle < sa)
        angle += 2 * M_PI;
    return angle - sa - a < 1e-4;
}

const Coordinate calcMirrorPoint(const LineData &l, const Coordinate &p)
{
    Coordinate m = calcIntersectionPoint(l, LineData(p, calcPointOnPerpend(l, p)));
    return 2 * m - p;
}

const Coordinate calcCircleLineIntersect(const Coordinate &c, const double sqr, const LineData &l, int side)
{
    Coordinate proj = calcPointProjection(c, l);
    Coordinate hvec = proj - c;
    Coordinate lvec = -l.dir();

    double sqdist = hvec.squareLength();
    double sql = sqr - sqdist;
    if (sql < 0.0)
        return Coordinate::invalidCoord();
    else {
        double l = sqrt(sql);
        lvec = lvec.normalize(l);
        lvec *= side;

        return proj + lvec;
    };
}

const Coordinate calcArcLineIntersect(const Coordinate &c, const double sqr, const double sa, const double angle, const LineData &l, int side)
{
    const Coordinate possiblepoint = calcCircleLineIntersect(c, sqr, l, side);
    if (isOnArc(possiblepoint, c, sqrt(sqr), sa, angle, test_threshold))
        return possiblepoint;
    else
        return Coordinate::invalidCoord();
}

const Coordinate calcPointProjection(const Coordinate &p, const LineData &l)
{
    Coordinate orth = l.dir().orthogonal();
    return p + orth.normalize(calcDistancePointLine(p, l));
}

double calcDistancePointLine(const Coordinate &p, const LineData &l)
{
    double xa = l.a.x;
    double ya = l.a.y;
    double xb = l.b.x;
    double yb = l.b.y;
    double x = p.x;
    double y = p.y;
    double norm = l.dir().length();
    return (yb * x - ya * x - xb * y + xa * y + xb * ya - yb * xa) / norm;
}

Coordinate calcRotatedPoint(const Coordinate &a, const Coordinate &c, const double arc)
{
    // we take a point p on a line through c and parallel with the
    // X-axis..
    Coordinate p(c.x + 5, c.y);
    // we then calc the arc that ac forms with cp...
    Coordinate d = a - c;
    d = d.normalize();
    double aarc = std::acos(d.x);
    if (d.y < 0)
        aarc = 2 * M_PI - aarc;

    // we now take the sum of the two arcs to find the arc between
    // pc and ca
    double asum = aarc + arc;

    Coordinate ret(std::cos(asum), std::sin(asum));
    ret = ret.normalize((a - c).length());
    return ret + c;
}

Coordinate calcCircleRadicalStartPoint(const Coordinate &ca, const Coordinate &cb, double sqra, double sqrb)
{
    Coordinate direc = cb - ca;
    Coordinate m = (ca + cb) / 2;

    double dsq = direc.squareLength();
    double lambda = dsq == 0.0 ? 0.0 : (sqra - sqrb) / (2 * dsq);

    direc *= lambda;
    return m + direc;
}

// TODO Decide whether we need to reimplement locale handling or if we can just remove this
double getDoubleFromUser(const QString &caption, const QString &label, double value, QWidget *parent, bool *ok, double min, double max, int decimals)
{
    double ret = QInputDialog::getDouble(parent, caption, label, value, min, max, decimals, ok);

    return ret;
}

const Coordinate calcCenter(const Coordinate &a, const Coordinate &b, const Coordinate &c)
{
    // this algorithm is written by my brother, Christophe Devriese
    // <oelewapperke@ulyssis.org> ...
    // I don't get it myself :)

    double xdo = b.x - a.x;
    double ydo = b.y - a.y;

    double xao = c.x - a.x;
    double yao = c.y - a.y;

    double a2 = xdo * xdo + ydo * ydo;
    double b2 = xao * xao + yao * yao;

    double numerator = (xdo * yao - xao * ydo);
    /* mp: note that we should never compare with zero due to floating-point arithmetic */
    if (isSingular(xdo, ydo, xao, yao)) {
        // problem:  xdo * yao == xao * ydo <=> xdo/ydo == xao / yao
        // this means that the lines ac and ab have the same direction,
        // which means they're the same line..
        // FIXME: i would normally throw an error here, but KDE doesn't
        // use exceptions, so I'm returning a bogus point :(
        return a.invalidCoord();
        /* return (a+c)/2; */
    };
    double denominator = 0.5 / numerator;

    double centerx = a.x - (ydo * b2 - yao * a2) * denominator;
    double centery = a.y + (xdo * b2 - xao * a2) * denominator;

    return Coordinate(centerx, centery);
}

bool lineInRect(const Rect &r, const Coordinate &a, const Coordinate &b, const int width, const ObjectImp *imp, const KigWidget &w)
{
    double miss = w.screenInfo().normalMiss(width);

    // mp: the following test didn't work for vertical segments;
    //  fortunately the ieee floating point standard allows us to avoid
    //  the test altogether, since it would produce an infinity value that
    //  makes the final r.contains to fail
    //  in any case the corresponding test for a.y - b.y was missing.

    //  if ( fabs( a.x - b.x ) <= 1e-7 )
    //  {
    //    // too small to be useful..
    //    return r.contains( Coordinate( a.x, r.center().y ), miss );
    //  }

    // in case we have a segment we need also to check for the case when
    // the segment is entirely contained in the rect, in which case the
    // final tests all fail.
    // it is ok to just check for the midpoint in the rect since:
    // - if we have a segment completely contained in the rect this is true
    // - if the midpoint is in the rect than returning true is safe (also
    //   in the cases where we have a ray or a line)

    if (r.contains(0.5 * (a + b), miss))
        return true;

    Coordinate dir = b - a;
    double m = dir.y / dir.x;
    double lefty = a.y + m * (r.left() - a.x);
    double righty = a.y + m * (r.right() - a.x);
    double minv = dir.x / dir.y;
    double bottomx = a.x + minv * (r.bottom() - a.y);
    double topx = a.x + minv * (r.top() - a.y);

    // these are the intersections between the line, and the lines
    // defined by the sides of the rectangle.
    Coordinate leftint(r.left(), lefty);
    Coordinate rightint(r.right(), righty);
    Coordinate bottomint(bottomx, r.bottom());
    Coordinate topint(topx, r.top());

    // For each intersection, we now check if we contain the
    // intersection ( this might not be the case for a segment, when the
    // intersection is not between the begin and end point. ) and if
    // the rect contains the intersection.  If it does, we have a winner.
    return (imp->contains(leftint, width, w) && r.contains(leftint, miss)) || (imp->contains(rightint, width, w) && r.contains(rightint, miss))
        || (imp->contains(bottomint, width, w) && r.contains(bottomint, miss)) || (imp->contains(topint, width, w) && r.contains(topint, miss));
}

bool operator==(const LineData &l, const LineData &r)
{
    return l.a == r.a && l.b == r.b;
}

bool LineData::isParallelTo(const LineData &l) const
{
    const Coordinate &p1 = a;
    const Coordinate &p2 = b;
    const Coordinate &p3 = l.a;
    const Coordinate &p4 = l.b;

    double dx1 = p2.x - p1.x;
    double dy1 = p2.y - p1.y;
    double dx2 = p4.x - p3.x;
    double dy2 = p4.y - p3.y;

    return isSingular(dx1, dy1, dx2, dy2);
}

bool LineData::isOrthogonalTo(const LineData &l) const
{
    const Coordinate &p1 = a;
    const Coordinate &p2 = b;
    const Coordinate &p3 = l.a;
    const Coordinate &p4 = l.b;

    double dx1 = p2.x - p1.x;
    double dy1 = p2.y - p1.y;
    double dx2 = p4.x - p3.x;
    double dy2 = p4.y - p3.y;

    return isSingular(dx1, dy1, -dy2, dx2);
}

bool areCollinear(const Coordinate &p1, const Coordinate &p2, const Coordinate &p3)
{
    return isSingular(p2.x - p1.x, p2.y - p1.y, p3.x - p1.x, p3.y - p1.y);
}

bool isSingular(const double &a, const double &b, const double &c, const double &d)
{
    double det = a * d - b * c;
    double norm1 = std::fabs(a) + std::fabs(b);
    double norm2 = std::fabs(c) + std::fabs(d);

    /*
     * test must be done relative to the magnitude of the two
     * row (or column) vectors!
     */
    return (std::fabs(det) <= test_threshold * norm1 * norm2);
}

const double double_inf = HUGE_VAL;
const double test_threshold = 1e-6;
