#include "pop_range.hpp"

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

using pop_range::views::pop;

// ---------------------------------------------------------------------
// Instrumented type to prove *how many* copies/moves actually happen.
// ---------------------------------------------------------------------
struct probe {
    int value = 0;
    static inline int copies = 0;
    static inline int moves = 0;
    static void reset() { copies = 0; moves = 0; }

    probe() = default;
    probe(int v) : value(v) {}
    probe(const probe& o) : value(o.value) { ++copies; }
    probe(probe&& o) noexcept : value(o.value) { ++moves; }
    probe& operator=(const probe& o) { value = o.value; ++copies; return *this; }
    probe& operator=(probe&& o) noexcept { value = o.value; ++moves; return *this; }
};

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";
    }

    // ---------------- feedback point 1: no unnecessary copies ----------------
    {
        probe::reset();
        std::stack<probe> s;
        for (int i = 0; i < 5; ++i) s.push(probe{i});
        probe::reset(); // ignore the copies/moves from pushing above

        int total = 0;
        for (auto v : pop(s)) total += v.value; // `auto v` -> move-constructed
        assert(total == 0 + 1 + 2 + 3 + 4);
        std::cout << "stack<probe> drain: copies=" << probe::copies
                   << " moves=" << probe::moves << " (expected copies=0, moves=5)\n";
        assert(probe::copies == 0);
        assert(probe::moves == 5);
    }
    {
        // priority_queue::top() is const-qualified (it must protect the heap
        // invariant), so std::move() on it can only select probe's *copy*
        // constructor -- there is no way around one copy per element here,
        // but it's still not a *second*, redundant copy on top of that.
        probe::reset();
        std::priority_queue<probe, std::vector<probe>, decltype([](const probe& a, const probe& b){ return a.value < b.value; })> pq;
        for (int i = 0; i < 4; ++i) pq.push(probe{i});
        probe::reset();

        int total = 0;
        for (auto v : pop(pq)) total += v.value;
        assert(total == 0 + 1 + 2 + 3);
        std::cout << "priority_queue<probe> drain: copies=" << probe::copies
                   << " moves=" << probe::moves
                   << " (expected copies=4 from top() being const; moves come from\n"
                   << "  priority_queue::pop()'s own internal heap reordering, not from pop_range)\n";
        assert(probe::copies == 4); // exactly one copy per element -- what pop_range controls
    }

    // ---------------- feedback point 2: plain containers (vector/deque/list) ----------------
    {
        std::vector<int> v{1, 2, 3, 4, 5};
        std::cout << "vector (back()/pop_back(), LIFO order): ";
        for (int x : pop(v)) std::cout << x << ' ';
        std::cout << '\n';
        assert(v.empty());
    }
    {
        std::deque<int> d{1, 2, 3, 4, 5};
        std::cout << "deque (front()/pop_front(), FIFO order): ";
        for (int x : pop(d)) std::cout << x << ' ';
        std::cout << '\n';
        assert(d.empty());
    }
    {
        std::list<int> l{1, 2, 3, 4, 5};
        std::cout << "list (front()/pop_front(), FIFO order): ";
        for (int x : pop(l)) std::cout << x << ' ';
        std::cout << '\n';
        assert(l.empty());
    }

    // ---------------- feedback point 3: sized_range ----------------
    {
        std::stack<int> s;
        for (int i = 1; i <= 4; ++i) s.push(i);
        auto view = pop(s);
        assert(view.size() == 4);
        auto it = view.begin();
        ++it; ++it; // pop two
        assert(view.size() == 2);
        std::cout << "sized_range: size() tracks remaining count correctly (4 -> 2)\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>>>);
    static_assert(std::ranges::sized_range<pop_range::pop_view<std::stack<int>>>);
    static_assert(std::ranges::sized_range<pop_range::pop_view<std::vector<int>>>);
    std::cout << "all static_asserts on iterator/range concepts passed\n";

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