// SPDX-FileCopyrightText: 2002 Dominique Devriese <devriese@kde.org>

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

#include "line_imp.h"

#include "bogus_imp.h"
#include "point_imp.h"

#include "../kig/kig_view.h"
#include "../misc/common.h"
#include "../misc/equationstring.h"
#include "../misc/kigpainter.h"
#include "../misc/kigtransform.h"
#include "../misc/rect.h"

#include <KLazyLocalizedString>
#include <cmath>
using namespace std;

AbstractLineImp::AbstractLineImp(const Coordinate &a, const Coordinate &b)
    : mdata(a, b)
{
}

AbstractLineImp::~AbstractLineImp()
{
}

bool AbstractLineImp::inRect(const Rect &r, int width, const KigWidget &w) const
{
    return lineInRect(r, mdata.a, mdata.b, width, this, w);
}

int AbstractLineImp::numberOfProperties() const
{
    return Parent::numberOfProperties() + 2;
}

const ObjectImpType *AbstractLineImp::impRequirementForProperty(int which) const
{
    if (which < Parent::numberOfProperties())
        return Parent::impRequirementForProperty(which);
    else
        return AbstractLineImp::stype();
}

const char *AbstractLineImp::iconForProperty(int which) const
{
    if (which < Parent::numberOfProperties())
        return Parent::iconForProperty(which);
    if (which == Parent::numberOfProperties())
        return "slope"; // slope
    if (which == Parent::numberOfProperties() + 1)
        return "kig_text"; // equation
    else
        assert(false);
    return "";
}

ObjectImp *AbstractLineImp::property(int which, const KigDocument &w) const
{
    if (which < Parent::numberOfProperties())
        return Parent::property(which, w);
    if (which == Parent::numberOfProperties())
        return new DoubleImp(slope());
    if (which == Parent::numberOfProperties() + 1)
        return new StringImp(equationString());
    else
        assert(false);
    return new InvalidImp;
}

const QByteArrayList AbstractLineImp::propertiesInternalNames() const
{
    QByteArrayList l = Parent::propertiesInternalNames();
    l << "slope";
    l << "equation";
    assert(l.size() == AbstractLineImp::numberOfProperties());
    return l;
}

const QList<KLazyLocalizedString> AbstractLineImp::properties() const
{
    QList<KLazyLocalizedString> l = Parent::properties();
    l << kli18n("Slope");
    l << kli18n("Equation");
    assert(l.size() == AbstractLineImp::numberOfProperties());
    return l;
}

int SegmentImp::numberOfProperties() const
{
    return Parent::numberOfProperties() + 6;
}

const QByteArrayList SegmentImp::propertiesInternalNames() const
{
    QByteArrayList s = Parent::propertiesInternalNames();
    s << "length";
    s << "mid-point";
    s << "golden-point";
    s << "support";
    s << "end-point-A";
    s << "end-point-B";
    assert(s.size() == SegmentImp::numberOfProperties());
    return s;
}

const QList<KLazyLocalizedString> SegmentImp::properties() const
{
    QList<KLazyLocalizedString> s = Parent::properties();
    s << kli18n("Length");
    s << kli18n("Mid Point");
    s << kli18n("Golden Ratio Point");
    s << kli18n("Support Line");
    s << kli18n("First End Point");
    s << kli18n("Second End Point");
    assert(s.size() == SegmentImp::numberOfProperties());
    return s;
}

const ObjectImpType *SegmentImp::impRequirementForProperty(int which) const
{
    if (which < Parent::numberOfProperties())
        return Parent::impRequirementForProperty(which);
    else
        return SegmentImp::stype();
}

const char *SegmentImp::iconForProperty(int which) const
{
    int pnum = 0;
    if (which < Parent::numberOfProperties())
        return Parent::iconForProperty(which);
    else if (which == Parent::numberOfProperties() + pnum++)
        return "distance"; // length
    else if (which == Parent::numberOfProperties() + pnum++)
        return "segment_midpoint"; // mid point
    else if (which == Parent::numberOfProperties() + pnum++)
        return "segment_golden_point"; // golden ratio point
    else if (which == Parent::numberOfProperties() + pnum++)
        return ""; // support line
    else if (which == Parent::numberOfProperties() + pnum++)
        return "endpoint1"; // mid point
    else if (which == Parent::numberOfProperties() + pnum++)
        return "endpoint2"; // mid point
    else
        assert(false);
    return "";
}

ObjectImp *SegmentImp::property(int which, const KigDocument &w) const
{
    int pnum = 0;

    if (which < Parent::numberOfProperties())
        return Parent::property(which, w);
    else if (which == Parent::numberOfProperties() + pnum++)
        return new DoubleImp(mdata.dir().length());
    else if (which == Parent::numberOfProperties() + pnum++)
        return new PointImp((mdata.a + mdata.b) / 2);
    else if (which == Parent::numberOfProperties() + pnum++)
        return new PointImp(mdata.a + (sqrt(5) - 1) / 2 * (mdata.b - mdata.a));
    else if (which == Parent::numberOfProperties() + pnum++)
        return new LineImp(mdata.a, mdata.b);
    else if (which == Parent::numberOfProperties() + pnum++)
        return new PointImp(mdata.a);
    else if (which == Parent::numberOfProperties() + pnum++)
        return new PointImp(mdata.b);
    else
        assert(false);
    return new InvalidImp;
}

int RayImp::numberOfProperties() const
{
    return Parent::numberOfProperties() + 2;
}

const QByteArrayList RayImp::propertiesInternalNames() const
{
    QByteArrayList s = Parent::propertiesInternalNames();
    s << "support";
    s << "end-point-A";
    assert(s.size() == RayImp::numberOfProperties());
    return s;
}

const QList<KLazyLocalizedString> RayImp::properties() const
{
    QList<KLazyLocalizedString> s = Parent::properties();
    s << kli18n("Support Line");
    s << kli18n("End Point");
    assert(s.size() == RayImp::numberOfProperties());
    return s;
}

const ObjectImpType *RayImp::impRequirementForProperty(int which) const
{
    if (which < Parent::numberOfProperties())
        return Parent::impRequirementForProperty(which);
    else
        return RayImp::stype();
}

const char *RayImp::iconForProperty(int which) const
{
    int pnum = 0;
    if (which < Parent::numberOfProperties())
        return Parent::iconForProperty(which);
    else if (which == Parent::numberOfProperties() + pnum++)
        return ""; // support line
    else if (which == Parent::numberOfProperties() + pnum++)
        return "endpoint1"; // end point
    else
        assert(false);
    return "";
}

ObjectImp *RayImp::property(int which, const KigDocument &w) const
{
    int pnum = 0;

    if (which < Parent::numberOfProperties())
        return Parent::property(which, w);
    else if (which == Parent::numberOfProperties() + pnum++)
        return new LineImp(mdata.a, mdata.b);
    else if (which == Parent::numberOfProperties() + pnum++)
        return new PointImp(mdata.a);
    else
        assert(false);
    return new InvalidImp;
}

double AbstractLineImp::slope() const
{
    Coordinate diff = mdata.dir();
    return diff.y / diff.x;
}

const QString AbstractLineImp::equationString() const
{
    Coordinate p = mdata.a;
    Coordinate q = mdata.b;

    EquationString ret = EquationString(QLatin1String(""));

    double a = q.y - p.y;
    double b = p.x - q.x;
    double c = q.x * p.y - q.y * p.x;

    bool needsign = false;
    if (fabs(b) < 1e-6 * fabs(a)) {
        ret.addTerm(1.0, ret.x(), needsign);
        ret.addTerm(b / a, ret.y(), needsign);
        ret.addTerm(c / a, QLatin1String(""), needsign);
        ret.append(" = 0");
        return ret;
    }

    ret.append("y = ");
    ret.addTerm(-a / b, ret.x(), needsign);
    ret.addTerm(-c / b, QLatin1String(""), needsign);
    if (!needsign)
        ret.append("0");
    //  double m = ( q.y - p.y ) / ( q.x - p.x );
    //  double r = - ( q.y - p.y ) * p.x / ( q.x - p.x ) + p.y;
    //
    //  QString ret = QString::fromUtf8( "y = %1x " ) +
    //                QString::fromUtf8( r > 0 ? "+" : "-" ) +
    //                QString::fromUtf8( " %2" );
    //
    //  ret = ret.arg( m, 0, 'g', 3 );
    //  ret = ret.arg( abs( r ), 0, 'g', 3 );

    return ret;
}

void SegmentImp::draw(KigPainter &p) const
{
    p.drawSegment(mdata);
}

bool SegmentImp::contains(const Coordinate &p, int width, const KigWidget &w) const
{
    return internalContainsPoint(p, w.screenInfo().normalMiss(width));
}

void RayImp::draw(KigPainter &p) const
{
    p.drawRay(mdata);
}

bool RayImp::contains(const Coordinate &p, int width, const KigWidget &w) const
{
    return internalContainsPoint(p, w.screenInfo().normalMiss(width));
}

void LineImp::draw(KigPainter &p) const
{
    p.drawLine(mdata);
}

bool LineImp::contains(const Coordinate &p, int width, const KigWidget &w) const
{
    return internalContainsPoint(p, w.screenInfo().normalMiss(width));
}

SegmentImp::SegmentImp(const Coordinate &a, const Coordinate &b)
    : AbstractLineImp(a, b)
{
}

RayImp::RayImp(const Coordinate &a, const Coordinate &b)
    : AbstractLineImp(a, b)
{
}

LineImp::LineImp(const Coordinate &a, const Coordinate &b)
    : AbstractLineImp(a, b)
{
}

SegmentImp *SegmentImp::copy() const
{
    return new SegmentImp(mdata);
}

RayImp *RayImp::copy() const
{
    return new RayImp(mdata);
}

LineImp *LineImp::copy() const
{
    return new LineImp(mdata);
}

const Coordinate SegmentImp::getPoint(double param, const KigDocument &) const
{
    return mdata.a + mdata.dir() * param;
}

double SegmentImp::getParam(const Coordinate &p, const KigDocument &) const
{
    Coordinate pt = calcPointOnPerpend(data(), p);
    pt = calcIntersectionPoint(data(), LineData(p, pt));
    // if pt is over the end of the segment ( i.e. it's on the line
    // which the segment is a part of, but not of the segment itself.;
    // ) we set it to one of the end points of the segment...
    if ((pt - mdata.a).length() > mdata.dir().length())
        pt = mdata.b;
    else if ((pt - mdata.b).length() > mdata.dir().length())
        pt = mdata.a;
    if (mdata.b == mdata.a)
        return 0;
    return ((pt - mdata.a).length()) / (mdata.dir().length());
}

LineData AbstractLineImp::data() const
{
    return mdata;
}

const Coordinate RayImp::getPoint(double param, const KigDocument &) const
{
    param = 1.0 / param - 1.0;
    return mdata.a + mdata.dir() * param;
}

double RayImp::getParam(const Coordinate &p, const KigDocument &) const
{
    const LineData ld = data();
    Coordinate pt = calcPointOnPerpend(ld, p);
    pt = calcIntersectionPoint(ld, LineData(p, pt));
    // if pt is over the end of the ray ( i.e. it's on the line
    // which the ray is a part of, but not of the ray itself.;
    // ) we set it to the start point of the ray...
    Coordinate dir = ld.dir();
    pt -= ld.a;
    double param;
    if (dir.x != 0)
        param = pt.x / dir.x;
    else if (dir.y != 0)
        param = pt.y / dir.y;
    else
        param = 0.;
    if (param < 0.)
        param = 0.;

    // mp:  let's try with 1/(x+1),  this reverses the mapping, but
    // should allow to take advantage of the tightness of floating point
    // numbers near zero, in order to get more possible positions near
    // infinity

    param = 1. / (param + 1.);

    assert(param >= 0. && param <= 1.);
    return param;
}

const Coordinate LineImp::getPoint(double p, const KigDocument &) const
{
    // inspired upon KSeg

    // we need to spread the points over the line, it should also come near
    // the (infinite) end of the line, but most points should be near
    // the two points we contain...
    if (p <= 0.)
        p = 1e-6;
    if (p >= 1.)
        p = 1 - 1e-6;
    p = 2 * p - 1;
    if (p > 0)
        p = p / (1 - p);
    else
        p = p / (1 + p);
    //  p *= 1024;    // such multiplying factor could be useful in order to
    // have more points near infinity, at the expense of
    // points near ma and mb
    return mdata.a + p * mdata.dir();
}

double LineImp::getParam(const Coordinate &point, const KigDocument &) const
{
    // somewhat the reverse of getPoint, although it also supports
    // points not on the line...

    Coordinate pa = point - mdata.a;
    Coordinate ba = mdata.dir();
    double balsq = ba.x * ba.x + ba.y * ba.y;
    assert(balsq > 0);

    double p = (pa.x * ba.x + pa.y * ba.y) / balsq;
    //  p /= 1024;
    if (p > 0)
        p = p / (1 + p);
    else
        p = p / (1 - p);

    return 0.5 * (p + 1);
}

ObjectImp *SegmentImp::transform(const Transformation &t) const
{
    if (!t.isAffine()) /* we need to check the position of the two points */
    {
        if (t.getProjectiveIndicator(mdata.a) * t.getProjectiveIndicator(mdata.b) < 0)
            return new InvalidImp();
    }
    Coordinate na = t.apply(mdata.a);
    Coordinate nb = t.apply(mdata.b);
    if (na.valid() && nb.valid())
        return new SegmentImp(na, nb);
    else
        return new InvalidImp();
}

ObjectImp *LineImp::transform(const Transformation &t) const
{
    Coordinate na = t.apply(mdata.a);
    Coordinate nb = t.apply(mdata.b);
    if (na.valid() && nb.valid())
        return new LineImp(na, nb);
    else
        return new InvalidImp();
}

ObjectImp *RayImp::transform(const Transformation &t) const
{
    if (!t.isAffine()) /* we need to check the position of the two points */
    {
        double pa = t.getProjectiveIndicator(mdata.a);
        double pb = t.getProjectiveIndicator(mdata.b);
        if (pa < 0)
            pb = -pb;
        if (pb < fabs(pa))
            return new InvalidImp();
        Coordinate na = t.apply(mdata.a);
        Coordinate nb = t.apply0(mdata.b - mdata.a);
        if (na.valid() && nb.valid())
            return new SegmentImp(na, nb);
        else
            return new InvalidImp();
    }
    Coordinate na = t.apply(mdata.a);
    Coordinate nb = t.apply(mdata.b);
    if (na.valid() && nb.valid())
        return new RayImp(na, nb);
    else
        return new InvalidImp();
}

AbstractLineImp::AbstractLineImp(const LineData &d)
    : mdata(d)
{
}

SegmentImp::SegmentImp(const LineData &d)
    : AbstractLineImp(d)
{
}

RayImp::RayImp(const LineData &d)
    : AbstractLineImp(d)
{
}

LineImp::LineImp(const LineData &d)
    : AbstractLineImp(d)
{
}

double SegmentImp::length() const
{
    return mdata.length();
}

void SegmentImp::visit(ObjectImpVisitor *vtor) const
{
    vtor->visit(this);
}

void RayImp::visit(ObjectImpVisitor *vtor) const
{
    vtor->visit(this);
}

void LineImp::visit(ObjectImpVisitor *vtor) const
{
    vtor->visit(this);
}

bool AbstractLineImp::equals(const ObjectImp &rhs) const
{
    return rhs.type() == type() && static_cast<const AbstractLineImp &>(rhs).data() == data();
}

const ObjectImpType *AbstractLineImp::stype()
{
    static const ObjectImpType t(Parent::stype(),
                                 "line",
                                 kli18n("line"),
                                 I18N_NOOP("Select a Line"),
                                 nullptr,
                                 KLazyLocalizedString(),
                                 KLazyLocalizedString(),
                                 KLazyLocalizedString(),
                                 KLazyLocalizedString(),
                                 KLazyLocalizedString(),
                                 KLazyLocalizedString());
    return &t;
}

const ObjectImpType *LineImp::stype()
{
    static const ObjectImpType t(Parent::stype(),
                                 "line",
                                 kli18n("line"),
                                 I18N_NOOP("Select this line"),
                                 I18N_NOOP("Select line %1"),
                                 kli18n("Remove a Line"),
                                 kli18n("Add a Line"),
                                 kli18n("Move a Line"),
                                 kli18n("Attach to this line"),
                                 kli18n("Show a Line"),
                                 kli18n("Hide a Line"));
    return &t;
}

const ObjectImpType *SegmentImp::stype()
{
    static const ObjectImpType t(Parent::stype(),
                                 "segment",
                                 kli18n("segment"),
                                 I18N_NOOP("Select this segment"),
                                 I18N_NOOP("Select segment %1"),
                                 kli18n("Remove a Segment"),
                                 kli18n("Add a Segment"),
                                 kli18n("Move a Segment"),
                                 kli18n("Attach to this segment"),
                                 kli18n("Show a Segment"),
                                 kli18n("Hide a Segment"));
    return &t;
}

const ObjectImpType *RayImp::stype()
{
    static const ObjectImpType t(Parent::stype(),
                                 "ray",
                                 kli18n("half-line"),
                                 I18N_NOOP("Select this half-line"),
                                 I18N_NOOP("Select half-line %1"),
                                 kli18n("Remove a Half-Line"),
                                 kli18n("Add a Half-Line"),
                                 kli18n("Move a Half-Line"),
                                 kli18n("Attach to this half-line"),
                                 kli18n("Show a Half-Line"),
                                 kli18n("Hide a Half-Line"));
    return &t;
}

const ObjectImpType *SegmentImp::type() const
{
    return SegmentImp::stype();
}

const ObjectImpType *RayImp::type() const
{
    return RayImp::stype();
}

const ObjectImpType *LineImp::type() const
{
    return LineImp::stype();
}

bool SegmentImp::containsPoint(const Coordinate &p, const KigDocument &) const
{
    return internalContainsPoint(p, test_threshold);
}

bool SegmentImp::internalContainsPoint(const Coordinate &p, double threshold) const
{
    return isOnSegment(p, mdata.a, mdata.b, threshold);
}

bool RayImp::containsPoint(const Coordinate &p, const KigDocument &) const
{
    return internalContainsPoint(p, test_threshold);
}

bool RayImp::internalContainsPoint(const Coordinate &p, double threshold) const
{
    return isOnRay(p, mdata.a, mdata.b, threshold);
}

bool LineImp::containsPoint(const Coordinate &p, const KigDocument &) const
{
    return internalContainsPoint(p, test_threshold);
}

bool LineImp::internalContainsPoint(const Coordinate &p, double threshold) const
{
    return isOnLine(p, mdata.a, mdata.b, threshold);
}

bool AbstractLineImp::isPropertyDefinedOnOrThroughThisImp(int which) const
{
    int pnum = 0;

    if (which < Parent::numberOfProperties())
        return Parent::isPropertyDefinedOnOrThroughThisImp(which);
    else if (which == Parent::numberOfProperties() + pnum++)
        return false;
    else if (which == Parent::numberOfProperties() + pnum++)
        return false;
    else
        assert(false);
    return false;
}

bool SegmentImp::isPropertyDefinedOnOrThroughThisImp(int which) const
{
    int pnum = 0;

    if (which < Parent::numberOfProperties())
        return Parent::isPropertyDefinedOnOrThroughThisImp(which);
    else if (which == Parent::numberOfProperties() + pnum++)
        return false;
    else if (which == Parent::numberOfProperties() + pnum++)
        return true;
    else if (which == Parent::numberOfProperties() + pnum++)
        return true;
    else if (which == Parent::numberOfProperties() + pnum++)
        return true;
    else if (which == Parent::numberOfProperties() + pnum++)
        return true;
    else if (which == Parent::numberOfProperties() + pnum++)
        return true;
    else
        assert(false);
    return false;
}

bool RayImp::isPropertyDefinedOnOrThroughThisImp(int which) const
{
    int pnum = 0;

    if (which < Parent::numberOfProperties())
        return Parent::isPropertyDefinedOnOrThroughThisImp(which);
    else if (which == Parent::numberOfProperties() + pnum++)
        return true;
    else if (which == Parent::numberOfProperties() + pnum++)
        return true;
    else
        assert(false);
    return false;
}

Rect SegmentImp::surroundingRect() const
{
    return Rect(mdata.a, mdata.b);
}

Rect RayImp::surroundingRect() const
{
    return Rect::invalidRect();
}

Rect LineImp::surroundingRect() const
{
    return Rect::invalidRect();
}
