#include "geomutils.h"
#include "assertions.h"
#include <cmath>

bool isNullVector(QPointF const& v)
{
    return v.x() == 0 && v.y() == 0;
}

QPointF roundedPoint(QPointF const& p) {
    return QPointF(round(p.x()), round(p.y()));
}

QPointF perpendicular(QPointF const& v) {
    return QPointF(v.y(), -v.x());
}

qreal dot(QPointF const& v1, QPointF const& v2)
{
    return v1.x()*v2.x() + v1.y()*v2.y();
}

qreal vectorLength(QPointF const& v)
{
    return std::sqrt(vectorLengthSquared(v));
}

qreal vectorLengthSquared(QPointF const& v)
{
    return dot(v, v);
}

qreal distance(QPointF const& p1, QPointF const& p2)
{
    return vectorLength(p2 - p1);
}

qreal distanceSquared(QPointF const& p1, QPointF const& p2)
{
    return vectorLengthSquared(p2 - p1);
}

bool isInfrontOf(QPointF const& p, QPointF const& pos, QPointF const& dir)
{
    return dot(p-pos, dir) > 0;
}

qreal closestPointOnLineParameter(QPointF const& point1, QPointF const& direction1, QPointF const& point2)
{
    return dot(point2-point1, direction1)
         / dot(direction1, direction1);
}
qreal closestPointOnSegmentParameter(QPointF const& point1, QPointF const& direction1, QPointF const& point2)
{
    qreal alpha = closestPointOnLineParameter(point1, direction1, point2);
    if (alpha < 0) alpha = 0;
    else if (alpha > 1) alpha = 1;
    return alpha;
}

QPointF closestPointOnSegment(QPointF const& point1, QPointF const& direction1, QPointF const& point2)
{
    qreal alpha = closestPointOnSegmentParameter(point1, direction1, point2);
    return point1 + alpha*direction1;
}
qreal segmentToPointDistanceSquared(QPointF const& point1, QPointF const& direction1, QPointF const& point2)
{
    return distanceSquared(closestPointOnSegment(point1, direction1, point2), point2);
}
qreal segmentToPointDistance(QPointF const& point1, QPointF const& direction1, QPointF const& point2)
{
    return distance(closestPointOnSegment(point1, direction1, point2), point2);
}

qreal lineIntersectionParameter(QPointF const& point1, QPointF const& direction1, QPointF const& point2, QPointF const& direction2)
{
    return ( direction2.x()*(point1.y() - point2.y()) - direction2.y()*(point1.x() - point2.x()) )
         / ( direction2.y()*direction1.x() - direction1.y()*direction2.x() );
}

qreal projectOntoVerticalLine(qreal lineX, QPointF const& p, QPointF const& dir)
{
    if (p.x() == lineX) {
        return p.y();
    } else {
        assert(dir.x() != 0);
        QPointF projection = p + dir * ((lineX - p.x()) / dir.x());
        assert(projection.x() == lineX);
        return projection.y();
    }
}

void turnPoint90CCW(QPointF& p) {
    qreal x = p.x(); qreal y = p.y();
    p.setX(y); p.setY(-x);
}
void turnPoint90CW(QPointF& p) {
    qreal x = p.x(); qreal y = p.y();
    p.setX(-y); p.setY(x);
}
void mirrorPointH(QPointF& p) {
    p.setX(-p.x());
}
void mirrorPointV(QPointF& p) {
    p.setY(-p.y());
}

qreal turnedness(QPointF const& A, QPointF const& B, QPointF const& C) {
    // the "turnedness" at B of the path A->B->C is the determinant
    // |1 A.x A.y|     |B.x B.y|       |1 B.y|       |1 B.x|
    // |1 B.x B.y| = 1*|C.x C.y| - A.x*|1 C.y| + A.y*|1 C.x|
    // |1 C.x C.y|
    // which is <0 for a left turn at B and >0 for a right turn at B
    //
    // NOTE: Qt's coordinate system has the y-axis flipped with respect
    //   to mathematical convention.  In math, <0 is a right turn, and
    //   >0 a left turn.
    return (B.x()-A.x())*(C.y()-A.y()) - (B.y()-A.y())*(C.x()-A.x());
}
bool isLeftTurn(qreal turnedness) {
    return turnedness < 0;
}
bool isRightTurn(qreal turnedness) {
    return turnedness > 0;
}
bool isNoTurn(qreal turnedness) {
    return turnedness == 0;
}
bool isLeftTurn(QPointF const& A, QPointF const& B, QPointF const& C)  {
    return isLeftTurn( turnedness(A, B, C) );
}
bool isRightTurn(QPointF const& A, QPointF const& B, QPointF const& C) {
    return isRightTurn( turnedness(A, B, C) );
}
bool isNoTurn(QPointF const& A, QPointF const& B, QPointF const& C) {
    return isNoTurn( turnedness(A, B, C) );
}

qreal smallestLabelMovement(qreal label1, qreal label2, qreal labelPerimeter)
{
    qreal result = std::fmod(label2 - label1, labelPerimeter);
    if (result < -labelPerimeter/2) {
        return result + labelPerimeter;
    } else if (result > +labelPerimeter/2) {
        return result - labelPerimeter;
    } else {
        return result;
    }
}

qreal xDistance(qreal Ax, qreal Bx)
{
    return std::abs(Bx - Ax);
}
qreal xDistance(qreal Ax, QPointF const& B)
{
    return xDistance(Ax, B.x());
}
qreal xDistance(QPointF const& A, qreal Bx)
{
    return xDistance(A.x(), Bx);
}
qreal xDistance(QPointF const& A, QPointF const& B)
{
    return xDistance(A.x(), B.x());
}
