#include "perimeterrange.h"
#include "assertions.h"
#include <cmath>
#include <algorithm>

namespace {

const qreal epsilon = 0.000001;

bool almostInPerimeterRange(qreal value, PerimeterRange const& range)
{
    return range.first-epsilon <= value && value <= range.second+epsilon;
}
// positive for disjoint ranges, negative for overlapping ranges
qreal signedDistanceBetweenRanges(PerimeterRange const& range1, PerimeterRange const& range2)
{
    return std::max(range1.first,  range2.first)
         - std::min(range1.second, range2.second);
}

} // unnamed namespace

bool inPerimeterRange(qreal value, PerimeterRange const& range)
{
    return range.first <= value && value <= range.second;
}

void shiftPerimeterRangeOntoValue(PerimeterRange& range, qreal value, qreal labelPerimeter)
{
    PerimeterRange newRange = range;
    if (value < range.first-epsilon) {
        qreal shift = labelPerimeter * std::ceil( ((range.first-epsilon) - value) / labelPerimeter );
        newRange.first  -= shift;
        newRange.second -= shift;
    } else if (value > range.second+epsilon) {
        qreal shift = labelPerimeter * std::ceil( (value - (range.second+epsilon)) / labelPerimeter );
        newRange.first  += shift;
        newRange.second += shift;
    }
    assert(almostInPerimeterRange(value, newRange));
    range = std::move(newRange);
}

void shiftValueOntoPerimeterRange(qreal& value, PerimeterRange const& range, qreal labelPerimeter)
{
    qreal newValue = value;
    if (newValue < range.first-epsilon) {
        qreal shift = labelPerimeter * std::ceil( ((range.first-epsilon) - newValue) / labelPerimeter );
        newValue += shift;
    } else if (newValue > range.second+epsilon) {
        qreal shift = labelPerimeter * std::ceil( (newValue - (range.second+epsilon)) / labelPerimeter );
        newValue -= shift;
    }
    assert(almostInPerimeterRange(newValue, range));
    newValue = std::max(range.first, std::min(newValue, range.second));
    assert(inPerimeterRange(newValue, range));
    value = newValue;
}

void alignPerimeterRanges(PerimeterRange const& range1, PerimeterRange& range2, qreal labelPerimeter)
{
    qreal length1 = range1.second - range1.first;
    qreal length2 = range2.second - range2.first;
    qreal maximumDistance = 0.5*(labelPerimeter - length1 - length2);
    qreal distance = signedDistanceBetweenRanges(range1, range2);
    if (distance <= maximumDistance) { return; }

    qreal numShifts = std::ceil(distance / labelPerimeter);
    qreal shiftSign = (range1.first < range2.first) ? -1.0 : +1.0;
    qreal totalShift = shiftSign * labelPerimeter * numShifts;
    range2.first += totalShift;
    range2.second += totalShift;

    assert(signedDistanceBetweenRanges(range1, range2) <= maximumDistance);
}

PerimeterRange intersectRanges(PerimeterRange const& range1, PerimeterRange const& range2)
{
    PerimeterRange result( std::max(range1.first,  range2.first),
                           std::min(range1.second, range2.second) );
    assert(result.first <= result.second);
    return std::move(result);
}

bool perimeterRangesOverlap(PerimeterRange const& range1, PerimeterRange const& range2)
{
    qreal gap = std::max(range1.first,  range2.first)
              - std::min(range1.second, range2.second);
    return gap <= 0;
}
