#include "dynamiclabeling.h"
#include "probleminstance.h"
#include "drawingarea.h"
#include "labelmodel.h"
#include "staticlabeler.h"
#include "labeledpoint.h"
#include "slowestinterpolation.h"
#include "portals.h"
#include <chrono>

DynamicLabeling::DynamicLabeling(ProblemInstance const& p, qreal timestep, qreal trimming)
    : _statistics(p.duration()), _problemInstance(&p),
      _timestep(sanitizeTimestep(timestep)), _trimming(sanitizeTrimming(trimming))
{
    _problemInstance->registerObserver(*this);
}

DynamicLabeling::~DynamicLabeling()
{  
    if (_problemInstance) {
        _problemInstance->unregisterObserver(*this);
    }
    _problemInstance = nullptr;
}

qreal DynamicLabeling::duration() const
{
    return _problemInstance ? problemInstance().duration() : 0;
}

void DynamicLabeling::replaceStaticLabeler(std::unique_ptr<StaticLabeler>&& newLabeler)
{
    _staticLabeler = std::move(newLabeler);
    doInvalidate();
}

void DynamicLabeling::doInvalidate()
{
    _statistics.reset(duration());
    invalidate();
}

void DynamicLabeling::instanceDurationChanged()
{
    _statistics.updateDuration(duration());
    updateDuration();
}
void DynamicLabeling::instanceHadRouteAdded()
{
    doInvalidate();
}
void DynamicLabeling::instanceAirplaneScheduleChanged()
{
    doInvalidate();
}
void DynamicLabeling::instanceLabelModelChanged()
{
    doInvalidate();
}
void DynamicLabeling::instanceWasReplaced(ProblemInstance& newInstance)
{
    assert(_problemInstance != &newInstance);
    _problemInstance->unregisterObserver(*this);
    newInstance.registerObserver(*this);
    _problemInstance = &newInstance;

    doInvalidate();
}
void DynamicLabeling::instanceIsBeingDestroyed()
{
    _problemInstance = nullptr;
    doInvalidate();
}



namespace {

void drawSliders(DrawingArea* drawing, LabelCandidates const& candidates)
{
    LabelCandidateSegment const* segments[4] = {
        &candidates.leftmostCandidates(), &candidates.rightmostCandidates(),
        &candidates.topmostCandidates(),  &candidates.bottommostCandidates() };
    for (auto&& segment : segments) {
        if (!segment->isEmpty() && !segment->isDegenerate()) {
            drawing->drawLabelCandidateSegment(segment->leftmostPoint(),
                                               segment->rightmostPoint());
        }
    }
}

PerimeterRange combineTrimmings(PerimeterRange const& range1, PerimeterRange range2,
                                LabelModel const& labelModel)
{
    alignPerimeterRanges(range1, range2, labelModel.labelPerimeter());

    qreal endpoint1 = std::max(range1.first, range2.first);
    qreal endpoint2 = std::min(range1.second, range2.second);
    if (endpoint1 > endpoint2) { std::swap(endpoint1, endpoint2); }

    return PerimeterRange(endpoint1, endpoint2);
}

} // unnamed namespace

void DynamicLabeling::drawAtTime(DrawingArea* drawing, qreal time, DrawOptions const& options)
{
    if (!_staticLabeler) return;

    auto&& labeling = labelingAtTime(time);
    for (auto&& point : labeling.labeledPoints()) {
        if (options[DrawLabelModel]) {
            drawSliders(drawing, point.labelCandidates());
        }
        if (options[DrawLabelBBox]) {
            qreal freeness;
            if (options[ColorLabelsByFreeArea]) {
                freeness = point.labelFreeness();
            } else {
                freeness = point.isLabelFullyFree() ? 1.0 : 0.0;
            }
            drawing->drawLabelBBox( point.labelCenter(), freeness );
        }
        if (options[DrawLabelText]) {
            drawing->drawLabelText( point.labelCenter(), point.airplane().labelText() );
        }
        if (options[DrawLabelCenter]) {
            drawing->drawLabelCenter( point.labelCenter() );
        }
        if (options[DrawMovingPoints]) {
            drawing->drawPoint( point.position(), point.isLabelFullyFree() );
        }
    }
}

StaticLabeling DynamicLabeling::labelingAtTime(qreal time)
{
    StaticLabeling labeling = computeLabelingAtTime(time);
    _statistics.recordLabeling(labeling);
    return std::move(labeling);
}

bool DynamicLabeling::updateStatistics(qreal maxRuntime)
{
    qreal runtime;
    bool someWorkWasDone = false;

    typedef std::chrono::high_resolution_clock clock_type;
    clock_type::time_point start = clock_type::now();
    do {
        if (!_problemInstance || !_staticLabeler)
            break;

        qreal time = _statistics.firstUnrecordedTime();
        if (time < 0)
            break;
        _statistics.recordLabeling( computeLabelingAtTime(time) );
        someWorkWasDone = true;

        clock_type::time_point stop = clock_type::now();
        runtime = 1e-9*std::chrono::duration_cast<std::chrono::nanoseconds>(stop - start).count();
    } while (runtime < maxRuntime || maxRuntime < 0);

    return someWorkWasDone;
}

StaticLabeling DynamicLabeling::staticLabelingAtTime(qreal time) const
{
    assert(_staticLabeler);
    StaticLabeling result(_problemInstance->airplanesAtTime(time),
                          _problemInstance->labelModel(), time);
    if (trimming() != NO_TRIMMING) {
        trimLabelCandidates(result);
    }
    _staticLabeler->computeLabeling(result);
    return std::move(result);
}

void DynamicLabeling::trimLabelCandidates(StaticLabeling& labeling) const
{
    LabelModel const& labelModel = _problemInstance->labelModel();
    qreal t = labeling.time();

    for (LabeledPoint& p : labeling.labeledPoints()) {
        Portals before(p.airplane(), _problemInstance->labelModel(), t - _timestep, t);
        Portals after (p.airplane(), _problemInstance->labelModel(), t, t + _timestep);
        PerimeterRange trimmedBefore = before.trimmedRange2(trimming());
        PerimeterRange trimmedAfter  = after.trimmedRange1(trimming());
        PerimeterRange trimmed = combineTrimmings(trimmedBefore, trimmedAfter, labelModel);
        p.setLabelCandidates(trimmed, labelModel);
    }
}

AlwaysRelabelInterpolator::AlwaysRelabelInterpolator(ProblemInstance const& p)
    : DynamicLabeling(p)
{
}

void AlwaysRelabelInterpolator::invalidate()
{
}

void AlwaysRelabelInterpolator::updateDuration()
{
}

StaticLabeling AlwaysRelabelInterpolator::computeLabelingAtTime(qreal time)
{
    return staticLabelingAtTime(time);
}
