#ifndef LABELMODEL_H
#define LABELMODEL_H

#include "labeltrajectory.h"
#include "labelcandidates.h"
#include "perimeterrange.h"
#include "airplane.h"
#include <memory>
#include <limits>
#include <QString>

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

enum WhichSlider
{
    LeftSlider, BottomSlider, RightSlider, TopSlider // NOTE: counter-clockwise order
};

class LabelModel
{
protected:
    LabelModel();
public:
    virtual ~LabelModel();

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

    static std::unique_ptr<LabelModel> fromName(QString const& name);
    static std::unique_ptr<LabelModel> defaultModel();
    virtual QString name() const = 0;

    void setLabelSize(int width, int height);
    int labelWidth() const  { return _labelWidth; }
    int labelHeight() const { return _labelHeight; }
    int labelHalfPerimeter() const { return _labelWidth + _labelHeight; }
    int labelPerimeter() const { return 2*_labelWidth + 2*_labelHeight; }
    int labelArea() const { return _labelWidth * _labelHeight; }

    bool labelsAlmostIntersect(QPointF const& center1, QPointF const& center2, qreal epsilon = 0.1) const;
    bool labelsIntersect(QPointF const& center1, QPointF const& center2) const
    { return labelsAlmostIntersect(center1, center2, 0.0); }
    bool labelsDefinitelyIntersect(QPointF const& center1, QPointF const& center2, qreal epsilon = 0.1) const
    { return labelsAlmostIntersect(center1, center2, -epsilon); }

    // Return a label trajectory for `airplane` from `label1` at `time1` to
    // `label2` at `time2` that minimizes both the average and the maximum
    // label speed relative to `airplane`.
    virtual LabelTrajectory slowestLabelTrajectory(
        Airplane const& airplane, qreal time1, qreal label1, qreal time2, qreal label2) const;

    // Return the allowed label candidates for a moving point at a certain time.
    LabelCandidates labelCandidates(Airplane const& airplane, qreal time) const
    { return labelCandidates(airplane.positionAtTime(time), airplane.directionAtTime(time)); }
    virtual LabelCandidates labelCandidates(QPointF const& pointPosition, QPointF const& pointDirection) const = 0;
    LabelCandidates labelCandidates(QPointF pointPosition, PerimeterRange const& range) const;

    // Return which part of a 4-slider model is covered by the above label candidates.
    PerimeterRange perimeterRange(Airplane const& airplane, qreal time) const
    { return perimeterRange(airplane.positionAtTime(time), airplane.directionAtTime(time)); }
    virtual PerimeterRange perimeterRange(QPointF const& pointPosition, QPointF const& pointDirection) const = 0;

    // The same but for a single label (candidate).
    qreal labelToPerimeterValue(QPointF const& point, QPointF const& labelCenter) const
    { return labelToPerimeterValue(labelCenter - point); }
    qreal labelToPerimeterValue(QPointF const& relativeLabelCenter) const;

    // The reverse: get a label position from a value along the perimeter of a 4slider.
    QPointF relativeLabelPosition(qreal perimeterValue) const;
    QPointF labelPosition(QPointF const& pointPosition, qreal perimeterValue) const
    { return pointPosition + relativeLabelPosition(perimeterValue); }

protected:
    // Where exactly on the four slider is the given label?
    struct SliderLocation {
        LabelCandidateSegment* slider;
        int sliderIndex;
        PerimeterRange sliderRange;
        qreal distanceFromStart;
        qreal distanceToEnd;
    };
    SliderLocation locateOnSliders(LabelCandidateSegment* sliders[], qreal perimeterValue) const;

private:
    int _labelWidth, _labelHeight;
};

class OneSliderModel : public LabelModel
{
public:
    explicit OneSliderModel(WhichSlider slider)
        : _slider(slider)
    {}
    QString name() const;

    LabelCandidates labelCandidates(QPointF const& pointPosition, QPointF const& pointDirection) const;
    PerimeterRange perimeterRange(QPointF const& pointPosition, QPointF const& pointDirection) const;

private:
    WhichSlider _slider;
};

class FourSliderModel : public LabelModel
{
public:
    QString name() const { return "FourSlider"; }
protected:
    LabelCandidates labelCandidates(QPointF const& pointPosition, QPointF const& pointDirection) const;
    PerimeterRange perimeterRange(QPointF const& pointPosition, QPointF const& pointDirection) const;
};

class BehindSliderModel : public LabelModel
{
public:
    QString name() const { return "BehindSlider"; }
    LabelTrajectory slowestLabelTrajectory(
        Airplane const& airplane, qreal time1, qreal label1, qreal time2, qreal label2) const;
protected:
    LabelCandidates labelCandidates(QPointF const& pointPosition, QPointF const& pointDirection) const;
    PerimeterRange perimeterRange(QPointF const& pointPosition, QPointF const& pointDirection) const;
};

#endif // LABELMODEL_H
