#ifndef STATICLABELING_H
#define STATICLABELING_H

#include <tuple>
#include <unordered_map>
#include <boost/iterator/transform_iterator.hpp>
#include "airplane.h"
#include "labeledpoint.h"
#include <boost/range/iterator_range.hpp>

class LabelModel;

class StaticLabeling
{
    typedef std::unordered_map<AirplaneID, LabeledPoint> LabelMap;
    struct TakeValue {
        LabeledPoint& operator()(std::pair<AirplaneID const, LabeledPoint>& p) const { return p.second; }
        LabeledPoint const& operator()(std::pair<AirplaneID const, LabeledPoint> const& p) const { return p.second; }
        typedef LabeledPoint& result_type;
    };
public:
    typedef boost::transform_iterator<TakeValue, typename LabelMap::iterator, LabeledPoint&> iterator;
    typedef boost::transform_iterator<TakeValue, typename LabelMap::const_iterator, LabeledPoint const&> const_iterator;
    typedef boost::iterator_range<const_iterator> const_range;
    typedef boost::iterator_range<iterator> range;

public:
    template <typename Airplanes>
    StaticLabeling(Airplanes&& airplanes, LabelModel const& labelModel, qreal time)
        : _time(time), _totalFreeArea(0), _labelModel(&labelModel)
    {
        for (auto&& airplane : airplanes) {
            _labels.emplace(std::piecewise_construct,
                            std::forward_as_tuple(airplane.id()),
                            std::forward_as_tuple(airplane, labelModel, time));
        }
    }

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

    qreal time() const { return _time; }
    LabelModel const& labelModel() const { return *_labelModel; }

    StaticLabeling(StaticLabeling&& other);
    StaticLabeling& operator=(StaticLabeling&& other);

    bool isDefinedFor(AirplaneID airplane) const;
    template <typename A>
    bool isDefinedFor(A&& airplane) const
    { return isDefinedFor(airplaneID(std::forward<A>(airplane))); }

    LabeledPoint const& operator[](AirplaneID airplane) const;
    template <typename A>
    LabeledPoint const& operator[](A&& airplane) const
    { return (*this)[airplaneID(std::forward<A>(airplane))]; }

    bool empty() const { return _labels.empty(); }
    int points() const { return _labels.size(); }
    int fullyFreeLabels() const { return freeLabels(1.0); }
    int freeLabels(qreal freenessThreshold) const;

    void recomputeLabelFreenesses();

    qreal freeLabelArea() const { return _totalFreeArea; }
    qreal summedLabelArea() const;

    const_range labeledPoints() const;
    range labeledPoints();

private:
    AirplaneID airplaneID(Airplane const& airplane) const { return airplane.id(); }
    AirplaneID airplaneID(AirplaneID airplane) const { return airplane; }
    AirplaneID airplaneID(LabeledPoint const& point) const { return point.airplane().id(); }

private:
    LabelMap _labels;
    qreal _time, _totalFreeArea;
    LabelModel const* _labelModel;
};

#endif // STATICLABELING_H
