#include "slowestinterpolation.h"
#include "labelmodel.h"
#include "probleminstance.h"
#include "staticlabeling.h"
#include "assertions.h"
#ifdef PRODUCE_TIMINGS
#  include <chrono>
#  include <iostream>
#endif

SlowestInterpolation::SlowestInterpolation(ProblemInstance const& problemInstance, qreal timestep, qreal trimming)
    : DynamicLabeling(problemInstance, timestep, trimming), _labelTrajectories(numberOfChunks())
{
}

int SlowestInterpolation::numberOfChunks() const
{
    return chunkForTime(problemInstance().duration()) + 1;
}

void SlowestInterpolation::invalidate()
{
    for (auto& chunk : _labelTrajectories) {
        chunk.clear();
    }
    _labelTrajectories.resize(numberOfChunks());
}

void SlowestInterpolation::updateDuration()
{
    _labelTrajectories.resize(numberOfChunks());
}

StaticLabeling SlowestInterpolation::computeLabelingAtTime(qreal time)
{
    // TODO: make the LabelCandidates in each LabeledPoint correspond properly to the trimming?
    // The following would at least do this for moments that are a multiple of the timestep.
    /*
    if (timestep() * std::floor(time / timestep()) == time) {
        return staticLabelingAtTime(time);
    }
    */

    auto airplanes = problemInstance().airplanesAtTime(time);
    auto& labelModel = problemInstance().labelModel();
    StaticLabeling result(airplanes, labelModel, time);

    auto& chunk = _labelTrajectories[chunkForTime(time)];
    if (chunk.empty()) {
        computeChunkTrajectories(time);
    }

    for (auto& point : result.labeledPoints()) {
        auto i = chunk.find(point.airplane().id());
        assert(i != chunk.end());
        auto& trajectory = i->second;
        point.setLabel(trajectory.parameterValueAtTime(time), labelModel);
    }
    result.recomputeLabelFreenesses();

    return std::move(result);
}

void SlowestInterpolation::computeChunkTrajectories(qreal time)
{
    qreal time1 = timestep() * std::floor(time / timestep());
    qreal time2 = time1 + timestep();
    auto airplanes = problemInstance().airplanesBetweenTimes(time1, time2);
    if (airplanes.empty())
        return;

    auto labeling1 = staticLabelingAtTime(time1);
    auto labeling2 = staticLabelingAtTime(time2);
    auto& chunk = _labelTrajectories[chunkForTime(time)];

#ifdef PRODUCE_TIMINGS
    typedef std::chrono::high_resolution_clock clock_type;
    clock_type::time_point start = clock_type::now();
#endif
    for (auto&& airplane : airplanes) {
        LabelTrajectory trajectory = computeTrajectoryInChunk(airplane, labeling1, labeling2);
        if (airplane.id() == 0) { // 5
            //trajectory.printForIpe();
        }
        chunk.emplace(airplane.id(), std::move(trajectory));
    }
#ifdef PRODUCE_TIMINGS
    clock_type::time_point stop = clock_type::now();
    qreal runtime = 1e-6*std::chrono::duration_cast<std::chrono::nanoseconds>(stop - start).count();
    std::cout << "SlowestInterpolation on " << airplanes.size() << " points: " << runtime << " ms" << std::endl;
#endif
}

namespace {
qreal clampToLifetime(qreal time, Airplane const& airplane)
{
    return std::max(airplane.timeOfBirth(), std::min(time, airplane.timeOfDeath()));
}
} // unnamed namespace

LabelTrajectory SlowestInterpolation::computeTrajectoryInChunk(
        Airplane const& airplane, StaticLabeling const& labeling1, StaticLabeling const& labeling2)
{
    qreal time1  = clampToLifetime(labeling1.time(), airplane);
    qreal label1 = labeling1.isDefinedFor(airplane) ? labeling1[airplane].labelPerimeterValue() : ANY_LABEL;
    qreal time2  = clampToLifetime(labeling2.time(), airplane);
    qreal label2 = labeling2.isDefinedFor(airplane) ? labeling2[airplane].labelPerimeterValue() : ANY_LABEL;
    assert(time1 <= time2);
    LabelModel const& labelModel = problemInstance().labelModel();
    return labelModel.slowestLabelTrajectory(airplane, time1,label1, time2,label2);
}
