#ifndef AIRPLANEPOOL_H
#define AIRPLANEPOOL_H

#include <memory> // std::unique_ptr
#include <vector>
#include <boost/iterator/indirect_iterator.hpp>
#include <boost/iterator/filter_iterator.hpp>
#include <boost/range/iterator_range.hpp>
#include "airplaneroute.h"

class Airplane;

class AirplanePool
{
    typedef std::unique_ptr<Airplane> AirplanePtr;
    typedef std::vector<AirplanePtr> Container;

    struct BetweenTimes
    {
        BetweenTimes(qreal time1, qreal time2) : _time1(time1), _time2(time2) {}
        bool operator()(Airplane const& airplane) {
            return airplane.isAliveBetween(_time1, _time2);
        }
    private:
        qreal _time1, _time2;
    };

    typedef Container::iterator raw_iterator;
    typedef Container::const_iterator raw_const_iterator;

    typedef boost::indirect_iterator<raw_iterator, Airplane> iterator;
public:
    typedef boost::indirect_iterator<raw_const_iterator, Airplane const> const_iterator;
    typedef boost::filter_iterator<BetweenTimes, const_iterator> filtered_const_iterator;
    typedef boost::iterator_range<const_iterator> const_range;
    typedef boost::iterator_range<filtered_const_iterator> filtered_const_range;

    AirplanePool();

    AirplanePool(AirplanePool const&) = delete;
    AirplanePool& operator=(AirplanePool const&) = delete;
    Airplane& spawnAirplane(AirplaneRoute const& route, qreal timeOfBirth, AirplaneID relativeID);
    void recycleAirplane(Airplane& airplane);

    const_range airplanes() const;
    filtered_const_range airplanesAtTime(qreal time) const { return airplanesBetweenTimes(time, time); }
    filtered_const_range airplanesBetweenTimes(qreal time1, qreal time2) const;

    template <typename RouteRange>
    void spawnAirplanesUpto(qreal time, RouteRange&& routes) {
        for (auto&& route : routes) {
            route->spawnAirplanesUpto(time);
        }
    }
    void ensureProperAirplaneIDs() const {
        if (_numOrdered < _numUsed) {
            auto self = const_cast<AirplanePool*>(this); // TODO: this is ugly
            self->fixAirplaneOrderAndIDs();
        }
    }

private:
    void spawnAirplanesUpto(qreal time, AirplaneRoute& route);
    void fixAirplaneOrderAndIDs();

    iterator begin() { return _airplanes.begin(); }
    iterator endOrdered() { return _airplanes.begin() + _numOrdered; }
    iterator end() { return _airplanes.begin() + _numUsed; }
    const_iterator begin() const { return _airplanes.begin(); }
    const_iterator endOrdered() const { return _airplanes.begin() + _numOrdered; }
    const_iterator end() const { return _airplanes.begin() + _numUsed; }

private:
    Container _airplanes;    // _airplanes[0.._numOrdered) is ordered by (timeOfBirth(), route().id(), relativeId())
    std::size_t _numOrdered; // _airplanes[_numOrdered.._numUsed) is in an arbitrary order
    std::size_t _numUsed;    // _airplanes[_numUsed.._airplanes.size()) contains unused/recycled airplanes
};

#endif // AIRPLANEPOOL_H
