#include "pop_range.hpp"

#include <algorithm>
#include <cassert>
#include <iostream>
#include <numeric>
#include <queue>
#include <ranges>
#include <stack>
#include <vector>

using pop_range::views::pop;

template <typename Adaptor>
void print_by_reference(const char* label, Adaptor& adaptor) {
    std::cout << label << " (by reference, ranged-for): ";
    for (auto v : pop(adaptor)) std::cout << v << ' ';
    std::cout << '\n';
    std::cout << label << " empty after draining? " << std::boolalpha << adaptor.empty() << '\n';
}

int main() {
    // ---------------- std::stack : LIFO ----------------
    {
        std::stack<int> s;
        for (int i = 1; i <= 5; ++i) s.push(i);
        print_by_reference("stack", s);
    }

    // ---------------- std::queue : FIFO ----------------
    {
        std::queue<int> q;
        for (int i = 1; i <= 5; ++i) q.push(i);
        print_by_reference("queue", q);
    }

    // ---------------- std::priority_queue : max-heap ----------------
    {
        std::priority_queue<int> pq;
        for (int v : {3, 1, 4, 1, 5, 9, 2, 6}) pq.push(v);
        print_by_reference("priority_queue", pq);
    }

    // ---------------- pipe syntax ----------------
    {
        std::queue<std::string> q;
        q.push("alpha"); q.push("beta"); q.push("gamma");
        std::cout << "pipe syntax: ";
        for (auto v : q | pop) std::cout << v << ' ';
        std::cout << '\n';
    }

    // ---------------- composes with standard range adaptors ----------------
    {
        std::priority_queue<int> pq;
        for (int v : {10, 40, 20, 50, 30}) pq.push(v);

        // Take just the two largest without ever popping the rest.
        auto top2 = pop(pq) | std::views::take(2);
        std::vector<int> collected;
        for (int v : top2) collected.push_back(v);

        assert((collected == std::vector<int>{50, 40}));
        std::cout << "top2 via views::take: " << collected[0] << ' ' << collected[1] << '\n';
        std::cout << "priority_queue size after partial drain: " << pq.size() << " (expected 3)\n";
        assert(pq.size() == 3);
    }

    // ---------------- ownership: pop() on an rvalue (owns the container) ----------------
    {
        auto make_stack = [] {
            std::stack<int> s;
            s.push(100); s.push(200); s.push(300);
            return s;
        };
        int sum = 0;
        for (int v : pop(make_stack())) sum += v;
        assert(sum == 600);
        std::cout << "owned-rvalue sum: " << sum << " (expected 600)\n";
    }

    // ---------------- works with std::ranges algorithms directly ----------------
    {
        std::queue<int> q;
        for (int i = 1; i <= 4; ++i) q.push(i);
        auto view = pop(q);
        int total = std::ranges::fold_left(view, 0, std::plus<>{});
        assert(total == 10);
        std::cout << "accumulate over queue view: " << total << " (expected 10)\n";
    }

    // ---------------- empty adaptor produces an empty range ----------------
    {
        std::stack<int> s;
        auto view = pop(s);
        assert(view.begin() == view.end());
        std::cout << "empty stack -> begin() == end(): true\n";
    }

    // ---------------- concept sanity checks ----------------
    static_assert(std::input_iterator<pop_range::pop_iterator<std::stack<int>>>);
    static_assert(std::ranges::input_range<pop_range::pop_view<std::stack<int>>>);
    static_assert(std::ranges::view<pop_range::pop_view<std::stack<int>>>);
    static_assert(!std::ranges::forward_range<pop_range::pop_view<std::stack<int>>>);
    std::cout << "all static_asserts on iterator/range concepts passed\n";

    std::cout << "ALL TESTS PASSED\n";
    return 0;
}
