#include "statistics.h"
#include "staticlabeling.h"
#include "labelmodel.h"
#include "graphingarea.h"
#include "geomutils.h"
#include "assertions.h"
#include <QTextStream>

namespace {
constexpr qreal INVALID_POSITION = std::numeric_limits<qreal>::infinity();
} // unnamed namespace

Statistics::Statistics(qreal duration)
    : _graphOutput(nullptr), _firstUnrecorded(0), _graphWhat(FreeCount)
{
    reset(duration);
}

void Statistics::writeAsCSV(QIODevice& out) const
{
    assert(firstUnrecordedTime() < 0);
    normalizeLabelMovements();

    QTextStream stream(&out);
    for (auto&& entry : _summaries) {
        stream << entry.free << ',';
    }
    stream << '\n';
    for (auto&& entry : _summaries) {
        stream << entry.total << ',';
    }
    stream << '\n';
    for (auto&& entry : _summaries) {
        stream << entry.freeArea << ',';
    }
    stream << '\n';
    for (auto&& entry : _summaries) {
        stream << entry.summedArea << ',';
    }
    stream << '\n';

    for (auto&& m : _labelMovements) {
        stream << m.firstTick << ',';
        for (auto&& pos : m.positions) {
            stream << pos << ',';
        }
        stream << '\n';
    }
}

void Statistics::attachGraphOutput(GraphingArea& ga)
{
    if (_graphOutput != &ga) {
        _graphOutput = &ga;
        redraw();
    }
}

void Statistics::graphType(SummaryStatistic s)
{
    if (_graphWhat != s) {
        _graphWhat = s;
        redraw();
    }
}

void Statistics::detachGraphOutput()
{
    _graphOutput = nullptr;
}

void Statistics::reset(qreal duration)
{
    std::size_t size = 1+tickBeforeTime(duration);
    _summaries.assign(size, LabelingSummary());
    _labelMovements.clear();
    _firstUnrecorded = 0;
    if (_graphOutput) {
        _graphOutput->clear();
    }
}

void Statistics::updateDuration(qreal duration)
{
    std::size_t newSize = 1+tickBeforeTime(duration);
    if (_summaries.size() != newSize) {
        _summaries.resize(newSize, LabelingSummary());
        redraw();
    }
}

Statistics::LabelMovement& Statistics::labelMovementFor(LabeledPoint const& p)
{
    std::size_t index = p.airplane().id();
    if (_labelMovements.size() <= index) {
        _labelMovements.resize(index+1);
    }
    LabelMovement& result = _labelMovements[index];
    if (result.firstTick == INVALID_TICK) {
        result.firstTick = tickAfterTime( p.airplane().timeOfBirth() );
    }
    return result;
}

void Statistics::recordLabeling(StaticLabeling const& labeling)
{
    _labelPerimeter = labeling.labelModel().labelPerimeter();

    std::size_t tick = tickBeforeTime(labeling.time());
    recordLabelingSummaries(tick, labeling);
    recordLabelMovements(tick, labeling);

    if (_graphOutput) {
        if (tick > 0) { drawBar(tick-1); }
        drawBar(tick);
    }
}

void Statistics::recordLabelingSummaries(std::size_t tick, StaticLabeling const& labeling)
{
    assert(tick < _summaries.size());
    _summaries[tick].free  = labeling.fullyFreeLabels();
    _summaries[tick].total = labeling.points();
    _summaries[tick].freeArea  = labeling.freeLabelArea();
    _summaries[tick].summedArea = labeling.summedLabelArea();
}

void Statistics::recordLabelMovements(std::size_t tick, StaticLabeling const& labeling)
{
    for (auto&& p : labeling.labeledPoints()) {
        LabelMovement& m = labelMovementFor(p);
        assert(tick >= m.firstTick);
        std::size_t tickIndex = tick - m.firstTick;
        if (m.positions.size() <= tickIndex) {
            m.positions.resize(tickIndex+1, INVALID_POSITION);
        }

        qreal currentOld = m.positions[tickIndex];
        qreal current = p.labelPerimeterValue();
        m.positions[tickIndex] = current;

        assert(current != INVALID_POSITION);
        if (currentOld == INVALID_POSITION) {
            qreal previous = tickIndex > 0 ? m.positions[tickIndex-1] : INVALID_POSITION;
            if (previous != INVALID_POSITION) {
                recordSpeed(tick - 1, std::abs(smallestLabelMovement(previous, current, _labelPerimeter)));
            }
            qreal next = tickIndex < m.positions.size()-1 ? m.positions[tickIndex+1] : INVALID_POSITION;
            if (next != INVALID_POSITION) {
                recordSpeed(tick, std::abs(smallestLabelMovement(current, next, _labelPerimeter)));
            }
        }
    }
}

void Statistics::recordSpeed(std::size_t tick, qreal speed)
{
    assert(tick < _summaries.size());
    LabelingSummary& entry = _summaries[tick];
    entry.minMovement = std::min(entry.minMovement, speed);
    entry.maxMovement = std::max(entry.maxMovement, speed);
    entry.totalMovement += speed;
    entry.numMovements += 1;
}

void Statistics::normalizeLabelMovements() const
{
    for (auto&& m : _labelMovements) {
        assert(!m.positions.empty());
        qreal last = m.positions.front();
        for (qreal& pos : m.positions) {
            assert(pos != INVALID_POSITION);
            pos = last + smallestLabelMovement(last, pos, _labelPerimeter);
            last = pos;
        }
    }
}

int Statistics::freeAtTime(qreal time) const
{
    std::size_t i = tickBeforeTime(time);
    return i <= _summaries.size() ? _summaries[i].free : 0;
}

int Statistics::totalAtTime(qreal time) const
{
    std::size_t i = tickBeforeTime(time);
    return i <= _summaries.size() ? _summaries[i].total : 0;
}

qreal Statistics::firstUnrecordedTime() const
{
    std::size_t i;
    for (i = _firstUnrecorded; i < _summaries.size(); ++i) {
        if (_summaries[i].total < 0)
            break;
    }
    _firstUnrecorded = i;
    return i >= _summaries.size() ? -1 : ticksToSeconds(i);
}

void Statistics::drawBar(std::size_t tick) const
{
    LabelingSummary const& entry = _summaries[tick];
    switch (_graphWhat) {
    case FreeCount:
        _graphOutput->drawFreeAndTotalBar(entry.free, entry.total, tick, numTicks());
        break;
    case FreeArea:
        _graphOutput->drawFreeAndTotalBar(entry.freeArea, entry.summedArea, tick, numTicks());
        break;
    case LabelSpeeds:
        _graphOutput->drawSpeedBar(entry.minimumSpeed(), entry.averageSpeed(), entry.maximumSpeed(),
                                   50.0, tick, numTicks()); // TODO: don't hardcode! :S
        break;
    default:
        assert(false);
    }
}

void Statistics::redraw() const
{
    if (!_graphOutput) return;
    _graphOutput->clear();
    for (std::size_t tick = 0; tick < numTicks(); ++tick) {
        drawBar(tick);
    }
}
