#include "airplanepool.h"
#include "airplane.h"
#include <algorithm>

namespace {

struct Earlier
{
    bool operator()(Airplane const& a1, Airplane const& a2)
    {
        if (a1.timeOfBirth() < a2.timeOfBirth())   return true;
        if (a2.timeOfBirth() < a1.timeOfBirth())   return false;

        if (a1.route().id() < a2.route().id())   return true;
        if (a2.route().id() < a1.route().id())   return false;

        return a1.relativeId() < a2.relativeId();
    }
    bool operator()(std::unique_ptr<Airplane> const& a1, std::unique_ptr<Airplane> const& a2)
    {
        return (*this)(*a1.get(), *a2.get());
    }
};

} // unnamed namespace

AirplanePool::AirplanePool()
    : _numOrdered(0), _numUsed(0)
{}

Airplane& AirplanePool::spawnAirplane(AirplaneRoute const& route, qreal timeOfBirth, AirplaneID relativeId)
{
    AirplaneID id = _numUsed;
    if (_numUsed == _airplanes.size()) {
        _airplanes.emplace_back( new Airplane(*this, route, timeOfBirth, id, relativeId) );
    } else {
        Airplane* airplane = _airplanes[_numUsed].get();
        airplane->~Airplane();
        new(airplane) Airplane(*this, route, timeOfBirth, id, relativeId);
    }
    ++_numUsed;
    return *_airplanes[id];
}

void AirplanePool::recycleAirplane(Airplane& airplane)
{
    auto i = std::lower_bound(begin(), end(), airplane, Earlier());
    _numOrdered = std::min<std::size_t>(_numOrdered, std::distance(begin(), i));

    auto last = end() - 1;
    std::iter_swap(i.base(), last.base());
    --_numUsed;
}

AirplanePool::const_range AirplanePool::airplanes() const
{
    ensureProperAirplaneIDs();
    return boost::make_iterator_range(begin(), end());
}

AirplanePool::filtered_const_range AirplanePool::airplanesBetweenTimes(qreal time1, qreal time2) const
{
    ensureProperAirplaneIDs();
    BetweenTimes predicate(time1, time2);
    filtered_const_iterator i(predicate, begin(), end());
    filtered_const_iterator j(predicate, end(), end());
    return boost::make_iterator_range(i, j);
}

void AirplanePool::fixAirplaneOrderAndIDs()
{
    // the use of .base() ensures that we're sorting the unique_ptrs, not the Airplanes
    // TODO: this is rather ugly
    std::sort(endOrdered().base(), end().base(), Earlier());
    std::inplace_merge(begin().base(), endOrdered().base(), end().base(), Earlier());

    AirplaneID i = 0;
    for (Airplane& airplane : boost::make_iterator_range(begin(), end())) {
        airplane.setId(i++);
    }
    _numOrdered = _numUsed;
}
