#ifndef PROBLEMINSTANCE_H
#define PROBLEMINSTANCE_H

#include "routenetwork.h"
#include "airplaneroute.h"
#include "airplanepool.h"
#include "ipefiledata.h"
#include "drawoptions.h"
#include <unordered_set>
#include <vector>
#include <cassert>
#include <memory>
#include <boost/iterator/indirect_iterator.hpp>
#include <QFile>
#include <QByteArray>
#include <QRectF>
#include <QFont>

class DrawingArea;
class LabelModel;



class InstanceObserver;

class ProblemInstance
{
public:
    explicit ProblemInstance(QString filePath = QString());
    ~ProblemInstance();

    ProblemInstance(ProblemInstance const&) = delete;
    ProblemInstance& operator=(ProblemInstance const&) = delete;
    ProblemInstance(ProblemInstance&&) = delete;
    ProblemInstance& operator=(ProblemInstance&&) = delete;


public:
    void registerObserver(InstanceObserver& observer) const;
    void unregisterObserver(InstanceObserver& observer) const;
    void notifyObserversOfDurationChange() const;
    void notifyObserversOfAddedRoute() const;
    void notifyObserversOfAirplaneScheduleChange() const;
    void notifyObserversOfLabelModelChange() const;
    void notifyObserversOfReplacement(ProblemInstance &newInstance) const;



public:
    void draw(DrawingArea* drawing, DrawOptions const& options) const;



public:
    qreal duration() const { return _duration; }
    void duration(qreal newDuration) { _duration = newDuration; notifyObserversOfDurationChange(); }



public:
    RouteNetwork&       routeNetwork()       { return _routeNetwork; }
    RouteNetwork const& routeNetwork() const { return _routeNetwork; }


private:
    typedef std::vector< std::unique_ptr<AirplaneRoute> > RouteContainer;
public:
    typedef boost::indirect_iterator<RouteContainer::iterator, AirplaneRoute> route_iterator;
    typedef boost::indirect_iterator<RouteContainer::const_iterator, AirplaneRoute const> route_const_iterator;
    typedef boost::iterator_range<route_iterator> route_range;
    typedef boost::iterator_range<route_const_iterator> route_const_range;

    route_range routes();
    route_const_range routes() const;

    void rerandomizeAirplaneSpawns();

    template <typename VertexIdRange>
    AirplaneRoute& addRoute(VertexIdRange&& vertices)
    {
        AirplaneRoute& route = newRoute();
        for (VertexId v : vertices) {
            route.appendVertex(_routeNetwork.vertexPosition(v));
        }
        notifyObserversOfAddedRoute();
        return route;
    }
    // NOTE: this should really be "PredecessorMap const&" but then operator[] cannot be used.
    AirplaneRoute& addRoute(VertexId start, VertexId end, PredecessorMap& predecessors);
private:
    AirplaneRoute& newRoute();


public: // for timing purposes only
    void addRandomRoutes(int amount);


public:
    typedef AirplanePool::const_iterator airplane_const_iterator;
    typedef AirplanePool::filtered_const_iterator filtered_airplane_const_iterator;
    typedef AirplanePool::const_range airplane_const_range;
    typedef AirplanePool::filtered_const_range filtered_airplane_const_range;

    airplane_const_range airplanes() const {
        _airplanePool.spawnAirplanesUpto(duration(), _routes);
        return _airplanePool.airplanes();
    }
    filtered_airplane_const_range airplanesAtTime(qreal time) const {
        _airplanePool.spawnAirplanesUpto(clampToDuration(time), _routes);
        if (time > _duration) {
            return noAirplanes();
        } else {
            return _airplanePool.airplanesAtTime(time);
        }
    }
    filtered_airplane_const_range airplanesBetweenTimes(qreal time1, qreal time2) const {
        _airplanePool.spawnAirplanesUpto(clampToDuration(time2), _routes);
        if (time1 > _duration) {
            return noAirplanes();
        } else {
            return _airplanePool.airplanesBetweenTimes(time1, clampToDuration(time2));
        }
    }
private:
    qreal clampToDuration(qreal time) const { return std::max(0.0, std::min(time, duration())); }
    filtered_airplane_const_range noAirplanes() const {
        return _airplanePool.airplanesBetweenTimes(-1, -1);
    }


public:
    QFont labelFont() const { return _labelFont; }
    void labelFont(QFont labelFont);

    LabelModel const& labelModel() const { assert(_labelModel); return *_labelModel; }
    template <typename Model, typename... Args>
    void newLabelModel(Args&&... args) // create, and take ownership of, a new LabelModel subtype instance
    {
        std::unique_ptr<LabelModel> p( new Model(std::forward<Args>(args)...) );
        setLabelModel(std::move(p));
    }
    void setLabelModel(std::unique_ptr<LabelModel>&& labelModel);
    void recomputeLabelSize();

public:
    QString filePath() const { return _ipeData.filePath(); }
    void saveToFile(QString filePath) { _ipeData.saveToFile(filePath); }



private:
    mutable std::unordered_set<InstanceObserver*> _observers;

    RouteNetwork _routeNetwork;
    RouteContainer _routes;
    mutable AirplanePool _airplanePool;

    QFont _labelFont;
    std::unique_ptr<LabelModel> _labelModel;

    qreal _duration;

    IpeFileData _ipeData;
};

#endif // PROBLEMINSTANCE_H
