#include "labelcandidates.h"
#include "constants.h"
#include "assertions.h"
#include <algorithm>
#include <cmath>
#include <iostream>

namespace {
    bool isInsideExtendedLabel(QPointF const& point, QPointF const& labelCenter,
                               qreal labelWidth, qreal labelHeight)
    {
        bool xContained = std::abs(labelCenter.x() - point.x()) < labelWidth;
        bool yContained = std::abs(labelCenter.y() - point.y()) < labelHeight;
        return xContained && yContained;
    }
    bool isFullyContainedInExtendedLabel(LabelCandidateSegment const& segment, QPointF labelCenter,
                                         qreal labelWidth, qreal labelHeight)
    {
        return isInsideExtendedLabel(segment.topmostPoint(), labelCenter, labelWidth, labelHeight)
            && isInsideExtendedLabel(segment.bottommostPoint(), labelCenter, labelWidth, labelHeight);
    }
}

LabelCandidateSegment::LabelCandidateSegment()
    : _isDoubleton(false)
{
    makeEmpty();
}

LabelCandidateSegment::LabelCandidateSegment(QPointF const& endpoint1, QPointF const& endpoint2)
    : _isDoubleton(false)
{
    reset(endpoint1, endpoint2);
}

LabelCandidateSegment LabelCandidateSegment::empty()
{
    return LabelCandidateSegment();
}

LabelCandidateSegment LabelCandidateSegment::singleton(QPointF const& point)
{
    return LabelCandidateSegment(point, point);
}

LabelCandidateSegment LabelCandidateSegment::doubleton(QPointF const& point1, QPointF const& point2)
{
    LabelCandidateSegment result;
    result._isDoubleton = true;
    result.reset(point1, point2);
    return result;
}

LabelCandidateSegment LabelCandidateSegment::segment(QPointF const& endpoint1, QPointF const& endpoint2)
{
    return LabelCandidateSegment(endpoint1, endpoint2);
}

void LabelCandidateSegment::reset(QPointF const& endpoint1, QPointF const& endpoint2)
{
    if (endpoint2.x() < endpoint1.x() || (endpoint2.x() == endpoint1.x() && endpoint2.y() < endpoint2.y())) {
        _leftPoint = endpoint2;
        _rightPoint = endpoint1;
    } else {
        _leftPoint = endpoint1;
        _rightPoint = endpoint2;
    }
    assert(!isEmpty());
    assert( isHorizontal() || isVertical() || _isDoubleton );
}

bool LabelCandidateSegment::isEmpty() const
{
    return _leftPoint.x() > _rightPoint.x();
}

bool LabelCandidateSegment::isDegenerate() const
{
    return _isDoubleton;
}

bool LabelCandidateSegment::isHorizontal() const
{
    return !_isDoubleton && _leftPoint.y() == _rightPoint.y();
}

bool LabelCandidateSegment::isVertical() const
{
    return !_isDoubleton && _leftPoint.x() == _rightPoint.x();
}

void LabelCandidateSegment::turnClockwise()
{
    if (!isEmpty()) {
        reset( QPointF(-_leftPoint.y(),  _leftPoint.x()),
               QPointF(-_rightPoint.y(), _rightPoint.x()) );
    }
}

void LabelCandidateSegment::turnCounterClockwise()
{
    if (!isEmpty()) {
        reset( QPointF(_leftPoint.y(),  -_leftPoint.x()),
               QPointF(_rightPoint.y(), -_rightPoint.x()) );
    }
}

void LabelCandidateSegment::mirror()
{
    if (!isEmpty()) {
        reset( QPointF(-_leftPoint.x(),  _leftPoint.y()),
               QPointF(-_rightPoint.x(), _rightPoint.y()) );
    }
}

QPointF const& LabelCandidateSegment::leftmostPoint() const
{
    assert(!isEmpty());
    return _leftPoint;
}
QPointF& LabelCandidateSegment::leftmostPoint()
{
    assert(!isEmpty());
    return _leftPoint;
}

QPointF const& LabelCandidateSegment::rightmostPoint() const
{
    assert(!isEmpty());
    return _rightPoint;
}
QPointF& LabelCandidateSegment::rightmostPoint()
{
    assert(!isEmpty());
    return _rightPoint;
}

QPointF const& LabelCandidateSegment::topmostPoint() const
{
    assert(!isEmpty());
    if (_rightPoint.y() < _leftPoint.y())
        return _rightPoint;
    else
        return _leftPoint;
}
QPointF& LabelCandidateSegment::topmostPoint()
{
    assert(!isEmpty());
    if (_rightPoint.y() < _leftPoint.y())
        return _rightPoint;
    else
        return _leftPoint;
}

QPointF const& LabelCandidateSegment::bottommostPoint() const
{
    assert(!isEmpty());
    if (_rightPoint.y() >= _leftPoint.y())
        return _rightPoint;
    else
        return _leftPoint;
}
QPointF& LabelCandidateSegment::bottommostPoint()
{
    assert(!isEmpty());
    if (_rightPoint.y() >= _leftPoint.y())
        return _rightPoint;
    else
        return _leftPoint;
}

bool LabelCandidateSegment::removeIntersecting(QPointF const& labelCenter, qreal labelWidth, qreal labelHeight)
{
    if (isEmpty())
        return false;

    bool leftInside  = isInsideExtendedLabel(_leftPoint,  labelCenter, labelWidth, labelHeight);
    bool rightInside = isInsideExtendedLabel(_rightPoint, labelCenter, labelWidth, labelHeight);
    if (leftInside && rightInside) {
        makeEmpty();
        return true;
    } else if (!leftInside && !rightInside) {
        return false;
    } else if (_isDoubleton) {
        if (leftInside)
            _leftPoint = _rightPoint;
        else /* rightInside */
            _rightPoint = _leftPoint;
        _isDoubleton = false;
        return true;
    } else if (isHorizontal()) {
        if (leftInside)
            _leftPoint.rx() = labelCenter.x() + labelWidth;
        else /* rightInside */
            _rightPoint.rx() = labelCenter.x() - labelWidth;

        return true;
    } else /* isVertical() */ {
        bool topInside = leftInside;
        bool bottomInside = rightInside;
        if (_leftPoint.y() > _rightPoint.y())
            std::swap(topInside, bottomInside);

        if (topInside)
            topmostPoint().ry() = labelCenter.y() + labelHeight;
        else /* bottomInside */
            bottommostPoint().ry() = labelCenter.y() - labelHeight;

        return true;
    }
}

bool LabelCandidateSegment::removeBlockingWhenHorizontal(LabelCandidateSegment const& blocker, qreal labelWidth, qreal labelHeight)
{
    if (isFullyContainedInExtendedLabel(blocker, leftmostPoint(), labelWidth, labelHeight)) {
        makeEmpty();
        return true;
    } else {
        return false;
    }
}

bool LabelCandidateSegment::removeBlockingWhenVertical(LabelCandidateSegment const& blocker, qreal labelWidth, qreal labelHeight)
{
    bool topBlocking    = isFullyContainedInExtendedLabel(blocker, topmostPoint(), labelWidth, labelHeight);
    bool bottomBlocking = isFullyContainedInExtendedLabel(blocker, bottommostPoint(), labelWidth, labelHeight);
    if (topBlocking && bottomBlocking) {
        makeEmpty();
        return true;
    } else if (_isDoubleton) {
        if (topBlocking) {
            topmostPoint() = bottommostPoint();
            return true;
        } else if (bottomBlocking) {
            bottommostPoint() = topmostPoint();
            return true;
        } else {
            return false;
        }
    } else if (topBlocking) {
        qreal newTop = blocker.topmostPoint().y() + labelHeight;
        if (newTop > bottommostPoint().y())
            makeEmpty();
        else
            topmostPoint().ry() = newTop;
        return true;
    } else if (bottomBlocking) {
        qreal newBottom = blocker.bottommostPoint().y() - labelHeight;
        if (newBottom < topmostPoint().y())
            makeEmpty();
        else
            bottommostPoint().ry() = newBottom;
        return true;
    } else /* !topBlocking && !bottomBlocking */ {
        QPointF midpoint = 0.5*(topmostPoint() + bottommostPoint());
        bool midBlocking = isFullyContainedInExtendedLabel(blocker, midpoint, labelWidth, labelHeight);
        if (midBlocking) {
            _isDoubleton = true;
            return true; // all candidates except the topmost and bottommost are blocking
        } else {
            return false; // no candidates are blocking
        }
    }
}

bool LabelCandidateSegment::removeBlocking(LabelCandidates const& candidates, qreal labelWidth, qreal labelHeight)
{
    if (isEmpty())
        return false;

    LabelCandidateSegment blocker = candidates.rightmostCandidates();
    if (blocker.isEmpty()) {
        LabelCandidateSegment const& top = candidates.topmostCandidates();
        LabelCandidateSegment const& bottom = candidates.bottommostCandidates();
        if (!top.isEmpty() && !bottom.isEmpty()) {
            // Not technically a valid LabelCandidateSegment: the two points are
            // not necessarily on a shared vertical line.  Still, this will work.
            blocker = LabelCandidateSegment::doubleton(top.rightmostPoint(), bottom.rightmostPoint());
        } else if (top.isEmpty() && bottom.isEmpty()) {
            blocker = candidates.leftmostCandidates();
        } else if (top.isEmpty()) {
            blocker = LabelCandidateSegment::singleton(bottom.rightmostPoint());
        } else /* bottom.isEmpty() */ {
            blocker = LabelCandidateSegment::singleton(top.rightmostPoint());
        }
    }
    assert(!blocker.isEmpty());

    if (isHorizontal()) {
        return removeBlockingWhenHorizontal(blocker, labelWidth, labelHeight);
    } else /* isVertical() */ {
        return removeBlockingWhenVertical(blocker, labelWidth, labelHeight);
    }
}

void LabelCandidateSegment::printForIpe() const
{
    if (isEmpty()) { return; }
    std::cout << "<path stroke=\"black\">\n"
              << leftmostPoint().x() << " " << leftmostPoint().y() << " m\n"
              << rightmostPoint().x() << " " << rightmostPoint().y()
              << (isDegenerate() ? " m\n" : " l\n")
              << "</path>" << std::endl;
}

void LabelCandidateSegment::makeEmpty() {
    _leftPoint = QPointF(+1, 0);
    _rightPoint = QPointF(-1, 0);
}

namespace {
    LabelCandidateSegment const empty;
    QPointF NW(QPointF const& p, int w, int h) { return QPointF(p.x() - 0.5*w, p.y() - 0.5*h); }
    QPointF NE(QPointF const& p, int w, int h) { return QPointF(p.x() + 0.5*w, p.y() - 0.5*h); }
    QPointF SW(QPointF const& p, int w, int h) { return QPointF(p.x() - 0.5*w, p.y() + 0.5*h); }
    QPointF SE(QPointF const& p, int w, int h) { return QPointF(p.x() + 0.5*w, p.y() + 0.5*h); }
}

LabelCandidates LabelCandidates::leftSlider(QPointF const& p, int w, int h)
{
    return LabelCandidates( LabelCandidateSegment(NW(p,w,h), SW(p,w,h)), empty, empty, empty, w, h );
}

LabelCandidates LabelCandidates::rightSlider(QPointF const& p, int w, int h)
{
    return LabelCandidates( empty, LabelCandidateSegment(NE(p,w,h), SE(p,w,h)), empty, empty, w, h );
}

LabelCandidates LabelCandidates::topSlider(QPointF const& p, int w, int h)
{
    return LabelCandidates( empty, empty, LabelCandidateSegment(NW(p,w,h), NE(p,w,h)), empty, w, h );
}

LabelCandidates LabelCandidates::bottomSlider(QPointF const& p, int w, int h)
{
    return LabelCandidates( empty, empty, empty, LabelCandidateSegment(SW(p,w,h), SE(p,w,h)), w, h );
}

LabelCandidates LabelCandidates::fourSliders(QPointF const& p, int w, int h)
{
    return LabelCandidates( LabelCandidateSegment(NW(p,w,h), SW(p,w,h)),
                            LabelCandidateSegment(NE(p,w,h), SE(p,w,h)),
                            LabelCandidateSegment(NW(p,w,h), NE(p,w,h)),
                            LabelCandidateSegment(SW(p,w,h), SE(p,w,h)),
                            w, h );
}

bool LabelCandidates::isEmpty() const
{
    return _segments[L].isEmpty() && _segments[R].isEmpty() && _segments[T].isEmpty() && _segments[B].isEmpty();
}

void LabelCandidates::turnClockwise()
{
    LabelCandidateSegment tempL = _segments[L];
    _segments[L] = _segments[B];
    _segments[B] = _segments[R];
    _segments[R] = _segments[T];
    _segments[T] = tempL;
    for (int i = 0; i < 4; ++i)
        _segments[i].turnClockwise();
    std::swap(_labelWidth, _labelHeight);
}

void LabelCandidates::turnCounterClockwise()
{
    LabelCandidateSegment tempL = _segments[L];
    _segments[L] = _segments[T];
    _segments[T] = _segments[R];
    _segments[R] = _segments[B];
    _segments[B] = tempL;
    for (int i = 0; i < 4; ++i)
        _segments[i].turnCounterClockwise();
    std::swap(_labelWidth, _labelHeight);
}

void LabelCandidates::mirror()
{
    LabelCandidateSegment tempL = _segments[L];
    _segments[L] = _segments[R];
    _segments[R] = tempL;
    for (int i = 0; i < 4; ++i)
        _segments[i].mirror();
    return;
}

QPointF& LabelCandidates::leftmostPoint()
{
    LabelCandidates const* self = const_cast<LabelCandidates const*>(this);
    return const_cast<QPointF&>( self->leftmostPoint() );
}

QPointF const& LabelCandidates::leftmostPoint() const {
    if (!leftmostCandidates().isEmpty()) {
        return leftmostCandidates().topmostPoint();
    }

    if (!topmostCandidates().isEmpty() && !bottommostCandidates().isEmpty()) {
        QPointF const& p1 = topmostCandidates().leftmostPoint();
        QPointF const& p2 = bottommostCandidates().leftmostPoint();
        if (p2.x() < p1.x() || (p2.x() == p1.x() && p2.y() < p1.y()))
            return p2;
        else
            return p1;
    } else if (!topmostCandidates().isEmpty()) {
        return topmostCandidates().leftmostPoint();
    } else if (!bottommostCandidates().isEmpty()) {
        return bottommostCandidates().leftmostPoint();
    }

    assert( !rightmostCandidates().isEmpty() );
    return rightmostCandidates().topmostPoint();
}

bool LabelCandidates::removeAlmostIntersecting(QPointF const& labelCenter, qreal epsilon)
{
    bool removedSomething = false;
    for (int i = 0; i < 4; ++i)
        removedSomething |= _segments[i].removeAlmostIntersecting(labelCenter, _labelWidth, _labelHeight, epsilon);
    return removedSomething;
}

bool LabelCandidates::removeAlmostBlocking(LabelCandidates const& candidates, qreal epsilon)
{
    bool removedSomething = false;
    for (int i = 0; i < 4; ++i)
        removedSomething |= _segments[i].removeAlmostBlocking(candidates, _labelWidth, _labelHeight, epsilon);
    return removedSomething;
}

void LabelCandidates::printForIpe() const
{
    std::cout << "<group>\n";
    for (int i = 0; i < 4; ++i) {
        _segments[i].printForIpe();
    }
    std::cout << "</group>" << std::endl;
}
