#ifndef DYNAMICLABELING_H
#define DYNAMICLABELING_H

#include <memory>
#include <cassert>
#include "staticlabeling.h"
#include "instanceobserver.h"
#include "statistics.h"
#include "drawoptions.h"

class ProblemInstance;
class DrawingArea;
class StaticLabeler;

constexpr qreal NO_TRIMMING = std::numeric_limits<qreal>::infinity();

class DynamicLabeling : public InstanceObserver
{
    void replaceStaticLabeler(std::unique_ptr<StaticLabeler>&& newLabeler);
    void doInvalidate();
    void instanceDurationChanged();
    void instanceHadRouteAdded();
    void instanceAirplaneScheduleChanged();
    void instanceLabelModelChanged();
    void instanceWasReplaced(ProblemInstance &newInstance);
    void instanceIsBeingDestroyed();
protected:
    explicit DynamicLabeling(ProblemInstance const& p, qreal timestep = 5, qreal trimming = NO_TRIMMING);
public:
    virtual ~DynamicLabeling();

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

    ProblemInstance const& problemInstance() const { assert(_problemInstance); return *_problemInstance; }
    qreal duration() const;

    qreal timestep() const { return _timestep; }
    void timestep(qreal t) { _timestep = sanitizeTimestep(t); doInvalidate(); }

    qreal trimming() const { return _trimming; }
    void trimming(qreal t) { _trimming = sanitizeTrimming(t); doInvalidate(); }

    StaticLabeler const& staticLabeler() const { assert(_staticLabeler); return *_staticLabeler; }
    StaticLabeler& staticLabeler() { assert(_staticLabeler); return *_staticLabeler; }

    template <typename Labeler, typename... Args>
    void newStaticLabeler(Args&&... args) // create, and take ownership of, a new StaticLabeler subtype instance
    {
        std::unique_ptr<StaticLabeler> newLabeler( new Labeler(std::forward<Args>(args)...) );
        replaceStaticLabeler(std::move(newLabeler));
    }

    Statistics const& statistics() const { return _statistics; }
    Statistics& statistics() { return _statistics; }

    void drawAtTime(DrawingArea* drawing, qreal time, DrawOptions const& options);

    StaticLabeling labelingAtTime(qreal time);

    bool updateStatistics(qreal maxRuntime = -1);

protected:
    virtual StaticLabeling computeLabelingAtTime(qreal time) = 0;

    virtual void invalidate() = 0; // start over from scratch when anything substantial changes
                                   // (e.g. when routes are added, the label model is changed, ...)
    virtual void updateDuration() = 0; // extend or contract the labeling when the total
                                       // duration of the problem instance changes

    StaticLabeling staticLabelingAtTime(qreal time) const;

private:
    static qreal sanitizeTimestep(qreal t) { return t > 0.0 ? t : 5.0; }
    static qreal sanitizeTrimming(qreal t) { return t < 0.0 ? NO_TRIMMING : t; }

    void trimLabelCandidates(StaticLabeling& labeling) const;

    Statistics _statistics;
    ProblemInstance const* _problemInstance;
    std::unique_ptr<StaticLabeler> _staticLabeler;
    qreal _timestep, _trimming;
};

class AlwaysRelabelInterpolator : public DynamicLabeling // TODO: find better class name
{
public:
    explicit AlwaysRelabelInterpolator(ProblemInstance const& p);

protected:
    void invalidate();
    void updateDuration();
    StaticLabeling computeLabelingAtTime(qreal time);
};

#include "slowestinterpolation.h"

#endif // DYNAMICLABELING_H
