#include "probleminstance.h"
#include "instanceobserver.h"
#include "labelmodel.h"
#include "staticlabeler.h"
#include "dynamiclabeling.h"
#include "drawingarea.h"
#include "constants.h"
#include <random>
#include <QFile>
#include <QtEndian>

ProblemInstance::ProblemInstance(QString filePath)
    : _labelFont("Arial", 12), _labelModel(nullptr), _duration(60.0),
      _ipeData(*this, filePath)
{
}

ProblemInstance::~ProblemInstance()
{
    for (auto observer : _observers) {
        observer->instanceIsBeingDestroyed();
    }
}

void ProblemInstance::registerObserver(InstanceObserver& observer) const
{
    _observers.emplace(&observer);
}

void ProblemInstance::unregisterObserver(InstanceObserver& observer) const
{
    _observers.erase(&observer);
}

void ProblemInstance::notifyObserversOfDurationChange() const
{
    for (auto observer : _observers) {
        observer->instanceDurationChanged();
    }
}
void ProblemInstance::notifyObserversOfAddedRoute() const
{
    for (auto observer : _observers) {
        observer->instanceHadRouteAdded();
    }
}
void ProblemInstance::notifyObserversOfAirplaneScheduleChange() const
{
    for (auto observer : _observers) {
        observer->instanceAirplaneScheduleChanged();
    }
}
void ProblemInstance::notifyObserversOfLabelModelChange() const
{
    for (auto observer : _observers) {
        observer->instanceLabelModelChanged();
    }
}
void ProblemInstance::notifyObserversOfReplacement(ProblemInstance& newInstance) const
{
    for (auto observer : _observers) {
        observer->instanceWasReplaced(newInstance);
    }
}

ProblemInstance::route_range ProblemInstance::routes()
{
    return {_routes.begin(), _routes.end()};
}

ProblemInstance::route_const_range ProblemInstance::routes() const
{
    return {_routes.cbegin(), _routes.cend()};
}

void ProblemInstance::rerandomizeAirplaneSpawns()
{
    for (auto&& route : _routes) {
        route->rerollSeed();
    }
}

AirplaneRoute& ProblemInstance::newRoute()
{
    _routes.emplace_back( new AirplaneRoute(*this, _airplanePool) );
    return *_routes.back();
}

AirplaneRoute& ProblemInstance::addRoute(VertexId start, VertexId end, PredecessorMap& predecessors)
{
    AirplaneRoute& route = newRoute();
    for (VertexId v = end; v != start; v = predecessors[v]) {
        route.appendVertex( _routeNetwork.vertexPosition(v) );
        assert(v != predecessors[v]);
    }
    route.appendVertex( _routeNetwork.vertexPosition(start) );
    route.reverse();

    notifyObserversOfAddedRoute();
    return route;
}

void ProblemInstance::labelFont(QFont labelFont)
{
    _labelFont = labelFont;
    recomputeLabelSize();
}

void ProblemInstance::recomputeLabelSize()
{
    assert(_labelModel);

    QFontMetrics metrics(_labelFont);
    int maxw = 0, maxh = 0;
    for (AirplaneRoute const& route : routes()) {
        QSize size = metrics.size(0, route.biggestLabelText());
        int w = size.width() + size.width() % 2;
        int h = size.height() + size.height() % 2;
        if (w > maxw) maxw = w;
        if (h > maxh) maxh = h;
    }
    _labelModel->setLabelSize(maxw, maxh);
    notifyObserversOfLabelModelChange();
}

void ProblemInstance::setLabelModel(std::unique_ptr<LabelModel>&& labelModel)
{
    int w = 0; int h = 0;
    if (_labelModel) {
        w = _labelModel->labelWidth();
        h = _labelModel->labelHeight();
    }
    _labelModel = std::move(labelModel);
    assert(_labelModel);

    if (w > 0 && h > 0) {
        _labelModel->setLabelSize(w, h);
        notifyObserversOfLabelModelChange();
    } else {
        recomputeLabelSize();
        // NOTE: recomputeLabelSize also calls notifyObserversOfLabelModelChange, don't need to do it (again) here
    }
}

void ProblemInstance::draw(DrawingArea* drawing, DrawOptions const& options) const {
    _ipeData.draw(drawing);
    _routeNetwork.draw(drawing, options);
    if (options[DrawRoutes]) {
        for (auto&& route : _routes) {
            route->draw(drawing);
        }
    }
}

void ProblemInstance::addRandomRoutes(int amount)
{
    std::mt19937 rng(1337); // fixed seed so results are reproducible
    std::uniform_real_distribution<qreal> uniform(0, 800);
    QPointF p1, p2;
    std::vector<VertexId> vertices({VertexId(), VertexId()});
    for (int i = 0; i < amount; ++i) {
        p1.setX(uniform(rng)); p1.setY(uniform(rng));
        p2.setX(uniform(rng)); p2.setY(uniform(rng));
        vertices[0] = _routeNetwork.addVertex(p1);
        vertices[1] = _routeNetwork.addVertex(p2);
        _routeNetwork.addEdge(vertices[0], vertices[1]);
        AirplaneRoute& route = addRoute(vertices);
        route.usesPoissonProcess(false);
        route.averageInterarrivalTime(100);
    }
}

