#ifndef GEOMUTILS_H
#define GEOMUTILS_H

#include <boost/range/algorithm/min_element.hpp>
#include <boost/range/algorithm/max_element.hpp>
#include <QPointF>

bool isNullVector(QPointF const& v);

QPointF roundedPoint(QPointF const& p);

QPointF perpendicular(QPointF const& v);

qreal dot(QPointF const& v1, QPointF const& v2);

qreal vectorLength(QPointF const& v);
qreal vectorLengthSquared(QPointF const& v);

qreal distance(QPointF const& p1, QPointF const& p2);
qreal distanceSquared(QPointF const& p1, QPointF const& p2);

bool isInfrontOf(QPointF const& p, QPointF const& pos, QPointF const& dir);

qreal closestPointOnLineParameter(QPointF const& point1, QPointF const& direction1, QPointF const& point2);
qreal closestPointOnSegmentParameter(QPointF const& point1, QPointF const& direction1, QPointF const& point2);
QPointF closestPointOnSegment(QPointF const& point1, QPointF const& direction1, QPointF const& point2);
qreal segmentToPointDistanceSquared(QPointF const& point1, QPointF const& direction1, QPointF const& point2);
qreal segmentToPointDistance(QPointF const& point1, QPointF const& direction1, QPointF const& point2);
qreal lineIntersectionParameter(QPointF const& point1, QPointF const& direction1, QPointF const& point2, QPointF const& direction2);

qreal projectOntoVerticalLine(qreal lineX, QPointF const& p, QPointF const& dir);

void turnPoint90CCW(QPointF& p);
void turnPoint90CW(QPointF& p);
void mirrorPointH(QPointF& p);
void mirrorPointV(QPointF& p);

// moving on the path A->B->C, do we turn left at B, turn right at B, or keep going straight?
bool isLeftTurn(QPointF const& A, QPointF const& B, QPointF const& C);
bool isRightTurn(QPointF const& A, QPointF const& B, QPointF const& C);
bool isNoTurn(QPointF const& A, QPointF const& B, QPointF const& C);

qreal turnedness(QPointF const& A, QPointF const& B, QPointF const& C);
bool isLeftTurn(qreal turnedness);
bool isRightTurn(qreal turnedness);
bool isNoTurn(qreal turnedness);

qreal smallestLabelMovement(qreal label1, qreal label2, qreal labelPerimeter);

qreal xDistance(qreal Ax, qreal Bx);
qreal xDistance(qreal Ax, QPointF const& B);
qreal xDistance(QPointF const& A, qreal Bx);
qreal xDistance(QPointF const& A, QPointF const& B);

// NOTE: all of the functions below return the first hit (in order along the path) if there are multiple.
namespace detail {
template <typename UnimodalPath>
inline QPointF firstTangency(
        UnimodalPath&& path, QPointF const& direction,
        bool (*isLeftTurn)(QPointF const&,QPointF const&,QPointF const&))
{
    assert(!path.empty());
    auto i1 = path.begin(), i2 = std::next(i1), iend = path.end();
    for (; i2 != iend; ++i1, ++i2) {
        QPointF const& B = *i1;
        QPointF const& C = *i2;
        QPointF A = B - direction;
        if (!isLeftTurn(A, B, C)) { return B; }
    }
    return *i1;
}
} // namespace detail

template <typename RightTurningPath>
QPointF firstTangencyToRightTurningPath(RightTurningPath&& path, QPointF const& direction)
{
    return detail::firstTangency(std::forward<RightTurningPath>(path), direction, isLeftTurn);
}
template <typename LeftTurningPath>
QPointF firstTangencyToLeftTurningPath(LeftTurningPath&& path, QPointF const& direction)
{
    return detail::firstTangency(std::forward<LeftTurningPath>(path), direction, isRightTurn);
}

// TODO: these two functions are called with unimodal chains in some places.  There, ternary search could be used.
template <typename Path>
QPointF bottomPointAlong(Path&& path)
{
    assert(!path.empty());
    return *boost::max_element(std::forward<Path>(path),
                               [](QPointF const& p1, QPointF const& p2) { return p1.y() < p2.y(); });
}
template <typename Path>
QPointF topPointAlong(Path&& path)
{
    assert(!path.empty());
    return *boost::min_element(std::forward<Path>(path),
                               [](QPointF const& p1, QPointF const& p2) { return p1.y() < p2.y(); });
}

#endif // GEOMUTILS_H
