#include "tap.hpp"
#include "alloc/stack.hpp"


///////////////////////////////////////////////////////////////////////////////
void
n_allocations (cruft::alloc::stack &store,
               unsigned count,
               size_t bytes,
               size_t alignment = alignof (std::max_align_t))
{
    for (unsigned i = 0; i < count; ++i) {
        auto ptr = store.allocate<uint8_t> (bytes, alignment);
        store.deallocate (ptr);
    }
}


///////////////////////////////////////////////////////////////////////////////
int
main (void)
{
    cruft::TAP::logger tap;

    constexpr size_t BUFFER_AVAILABLE = 32;
    constexpr size_t BUFFER_REQUEST   = BUFFER_AVAILABLE - alignof (std::max_align_t);
    constexpr size_t BUFFER_PAD  = 32;
    constexpr size_t BUFFER_SIZE = BUFFER_AVAILABLE + BUFFER_PAD;

    // alignment is kinda important, so make it a little easier and ensure
    // something suitable right off the bat.
    alignas (std::max_align_t) std::byte memory[BUFFER_SIZE];
    std::fill (std::begin (memory), std::end (memory), std::byte{0});

    cruft::alloc::stack store (cruft::make_view(memory, memory + BUFFER_AVAILABLE));

    tap.expect_eq (store.begin (), std::begin (memory), "base pointers match");
    tap.expect_eq (store.offset (std::begin (memory)), 0u, "base offset is 0");
    tap.expect_eq (store.capacity (), BUFFER_AVAILABLE, "bytes capacity matches");

    // larger than total allocations should throw
    tap.expect_throw<std::bad_alloc> (
        [&store] (void) { store.allocate<uint8_t> (BUFFER_AVAILABLE + 1, 1); },
        "excessive allocation throws bad_alloc"
    );

    // try a large number of allocations so we exercise the frame handling and
    // alignment routines.
    tap.expect_nothrow (
        [&store] (void) { n_allocations (store, BUFFER_AVAILABLE, BUFFER_REQUEST); },
        "repeated allocation/deallocation"
    );

    // perform two near maximum allocations and check for exhaustion through
    // bad_alloc
    auto ptr = store.allocate<uint8_t> (BUFFER_REQUEST);
    (void)ptr;

    tap.expect_throw<std::bad_alloc> (
        [&store] (void) { store.allocate<uint8_t> (BUFFER_REQUEST); },
        "bad_alloc thrown on exhaustion"
    );

    // check byte counts are plausible. stacks use some extra memory for book
    // keeping, so we need to use relational comparison rather than equality.
    tap.expect_ge (store.used (), BUFFER_REQUEST, "bytes used matches");
    tap.expect_le (store.remain (), BUFFER_AVAILABLE - BUFFER_REQUEST, "bytes remain matches");
    
    // try many allocations again after resetting the allocator to zero usage
    store.reset ();
    tap.expect_nothrow (
        [&store] (void) { n_allocations (store, BUFFER_AVAILABLE, BUFFER_REQUEST); },
        "no bad_alloc after reset"
    );

    return tap.status ();
}