#include "../test.h"
#include <rxcpp/operators/rx-concat.hpp>
#include <rxcpp/operators/rx-buffer_count.hpp>
#include <rxcpp/operators/rx-buffer_time.hpp>
#include <rxcpp/operators/rx-buffer_time_count.hpp>
#include <rxcpp/operators/rx-take.hpp>

SCENARIO("buffer count partial window", "[buffer][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(220, 3),
            on.next(230, 4),
            on.next(240, 5),
            on.completed(250)
        });

        WHEN("group each int with the next 4 ints"){

            auto res = w.start(
                [&]() {
                    return xs
                        | rxo::buffer(5)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        | rxo::as_dynamic();
                }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(250, rxu::to_vector({ 2, 3, 4, 5 })),
                    v_on.completed(250)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 250)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer count full windows", "[buffer][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(220, 3),
            on.next(230, 4),
            on.next(240, 5),
            on.completed(250)
        });

        WHEN("group each int with the next int"){

            auto res = w.start(
                [&]() {
                return xs
                    .buffer(2)
                    // forget type to workaround lambda deduction bug on msvc 2013
                    .as_dynamic();
            }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(220, rxu::to_vector({ 2, 3 })),
                    v_on.next(240, rxu::to_vector({ 4, 5 })),
                    v_on.completed(250)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 250)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer count full and partial windows", "[buffer][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(220, 3),
            on.next(230, 4),
            on.next(240, 5),
            on.completed(250)
        });

        WHEN("group each int with the next 2 ints"){

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer(3)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(230, rxu::to_vector({ 2, 3, 4 })),
                    v_on.next(250, rxu::to_vector({ 5 })),
                    v_on.completed(250)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 250)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer count error", "[buffer][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        std::runtime_error ex("buffer on_error from source");

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(220, 3),
            on.next(230, 4),
            on.next(240, 5),
            on.error(250, ex)
        });

        WHEN("group each int with the next 4 ints"){

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer(5)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.error(250, ex)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 250)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer count skip less", "[buffer][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(220, 3),
            on.next(230, 4),
            on.next(240, 5),
            on.completed(250)
        });

        WHEN("group each int with the next 2 ints"){

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer(3, 1)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(230, rxu::to_vector({ 2, 3, 4 })),
                    v_on.next(240, rxu::to_vector({ 3, 4, 5 })),
                    v_on.next(250, rxu::to_vector({ 4, 5 })),
                    v_on.next(250, rxu::to_vector({ 5 })),
                    v_on.completed(250)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 250)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer count skip more", "[buffer][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(220, 3),
            on.next(230, 4),
            on.next(240, 5),
            on.completed(250)
        });

        WHEN("group each int with the next int skipping the third one"){

            auto res = w.start(
                [&]() {
                return xs
                    .buffer(2, 3)
                    // forget type to workaround lambda deduction bug on msvc 2013
                    .as_dynamic();
            }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(220, rxu::to_vector({ 2, 3 })),
                    v_on.next(250, rxu::to_vector({ 5 })),
                    v_on.completed(250)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 250)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer count basic", "[buffer][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(100, 1),
            on.next(210, 2),
            on.next(240, 3),
            on.next(280, 4),
            on.next(320, 5),
            on.next(350, 6),
            on.next(380, 7),
            on.next(420, 8),
            on.next(470, 9),
            on.completed(600)
        });

        WHEN("group each int with the next 2 ints"){

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer(3, 2)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(280, rxu::to_vector({ 2, 3, 4 })),
                    v_on.next(350, rxu::to_vector({ 4, 5, 6 })),
                    v_on.next(420, rxu::to_vector({ 6, 7, 8 })),
                    v_on.next(600, rxu::to_vector({ 8, 9 })),
                    v_on.completed(600)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 600)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer count disposed", "[buffer][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(100, 1),
            on.next(210, 2),
            on.next(240, 3),
            on.next(280, 4),
            on.next(320, 5),
            on.next(350, 6),
            on.next(380, 7),
            on.next(420, 8),
            on.next(470, 9),
            on.completed(600)
        });

        WHEN("group each int with the next 2 ints"){

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer(3, 2)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                },
                370
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(280, rxu::to_vector({ 2, 3, 4 })),
                    v_on.next(350, rxu::to_vector({ 4, 5, 6 })),
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 370)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer count error 2", "[buffer][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        std::runtime_error ex("buffer on_error from source");

        auto xs = sc.make_hot_observable({
            on.next(100, 1),
            on.next(210, 2),
            on.next(240, 3),
            on.next(280, 4),
            on.next(320, 5),
            on.next(350, 6),
            on.next(380, 7),
            on.next(420, 8),
            on.next(470, 9),
            on.error(600, ex)
        });

        WHEN("group each int with the next 2 ints"){

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer(3, 2)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(280, rxu::to_vector({ 2, 3, 4 })),
                    v_on.next(350, rxu::to_vector({ 4, 5, 6 })),
                    v_on.next(420, rxu::to_vector({ 6, 7, 8 })),
                    v_on.error(600, ex)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 600)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer with time on intervals", "[buffer_with_time][operators][long][!hide]"){
    GIVEN("7 intervals of 2 seconds"){
        WHEN("the period is 2sec and the initial is 5sec"){
            // time:   |-----------------|
            // events:      1 2 3 4 5 6 7
            // buffers: ---
            //             -1-
            //                2-3
            //                   -4-
            //                      5-6
            //                         -7
            using namespace std::chrono;

            #define TIME milliseconds
            #define UNIT *15

            auto sc = rxsc::make_current_thread();
            auto so = rx::synchronize_in_one_worker(sc);
            auto start = sc.now() + TIME(5 UNIT);
            auto period = TIME(2 UNIT);

            auto bufSource = rxs::interval(start, period, so)
                | rxo::take(7)
                | rxo::buffer_with_time(TIME(3 UNIT), so);

            bufSource
                .subscribe(
                    [](std::vector<long> counter){
                        printf("on_next: ");
                        std::for_each(counter.begin(), counter.end(), [](long c){
                            printf("%ld ", c);
                        });
                        printf("\n");
                    },
                    [](rxu::error_ptr){
                        printf("on_error\n");
                    },
                    [](){
                        printf("on_completed\n");
                    }
                );
        }
    }
}

SCENARIO("buffer with time on intervals, implicit coordination", "[buffer_with_time][operators][long][!hide]"){
    GIVEN("7 intervals of 2 seconds"){
        WHEN("the period is 2sec and the initial is 5sec"){
            // time:   |-----------------|
            // events:      1 2 3 4 5 6 7
            // buffers: ---
            //             -1-
            //                2-3
            //                   -4-
            //                      5-6
            //                         -7
            using namespace std::chrono;

            #define TIME milliseconds
            #define UNIT *15

            auto sc = rxsc::make_current_thread();
            auto so = rx::synchronize_in_one_worker(sc);
            auto start = sc.now() + TIME(5 UNIT);
            auto period = TIME(2 UNIT);

            rx::observable<>::interval(start, period, so)
                .take(7)
                .buffer_with_time(TIME(3 UNIT))
                .subscribe(
                    [](std::vector<long> counter){
                        printf("on_next: ");
                        std::for_each(counter.begin(), counter.end(), [](long c){
                            printf("%ld ", c);
                        });
                        printf("\n");
                    },
                    [](rxu::error_ptr){
                        printf("on_error\n");
                    },
                    [](){
                        printf("on_completed\n");
                    }
                );
        }
    }
}

SCENARIO("buffer with time on overlapping intervals", "[buffer_with_time][operators][long][!hide]"){
    GIVEN("5 intervals of 2 seconds"){
        WHEN("the period is 2sec and the initial is 5sec"){
            // time:   |-------------|
            // events:      1 2 3 4 5
            // buffers: ----
            //            --1-
            //              1-2-
            //                2-3-
            //                  3-4-
            //                    4-5
            //                      5
            using namespace std::chrono;

            #define TIME milliseconds
            #define UNIT *15

            auto sc = rxsc::make_current_thread();
            auto so = rx::synchronize_in_one_worker(sc);
            auto start = sc.now() + TIME(5 UNIT);
            auto period = TIME(2 UNIT);

            rx::observable<>::interval(start, period, so)
                .take(5)
                .buffer_with_time(TIME(4 UNIT), TIME(2 UNIT), so)
                .subscribe(
                    [](std::vector<long> counter){
                        printf("on_next: ");
                        std::for_each(counter.begin(), counter.end(), [](long c){
                            printf("%ld ", c);
                        });
                        printf("\n");
                    },
                    [](rxu::error_ptr){
                        printf("on_error\n");
                    },
                    [](){
                        printf("on_completed\n");
                    }
                );
        }
    }
}

SCENARIO("buffer with time on overlapping intervals, implicit coordination", "[buffer_with_time][operators][long][!hide]"){
    GIVEN("5 intervals of 2 seconds"){
        WHEN("the period is 2sec and the initial is 5sec"){
            // time:   |-------------|
            // events:      1 2 3 4 5
            // buffers: ----
            //            --1-
            //              1-2-
            //                2-3-
            //                  3-4-
            //                    4-5
            //                      5
            using namespace std::chrono;

            #define TIME milliseconds
            #define UNIT *15

            auto sc = rxsc::make_current_thread();
            auto so = rx::synchronize_in_one_worker(sc);
            auto start = sc.now() + TIME(5 UNIT);
            auto period = TIME(2 UNIT);

            rx::observable<>::interval(start, period, so)
                .take(5)
                .buffer_with_time(TIME(4 UNIT), TIME(2 UNIT))
                .subscribe(
                    [](std::vector<long> counter){
                        printf("on_next: ");
                        std::for_each(counter.begin(), counter.end(), [](long c){
                            printf("%ld ", c);
                        });
                        printf("\n");
                    },
                    [](rxu::error_ptr){
                        printf("on_error\n");
                    },
                    [](){
                        printf("on_completed\n");
                    }
                );
        }
    }
}

SCENARIO("buffer with time on intervals, error", "[buffer_with_time][operators][long][!hide]"){
    GIVEN("5 intervals of 2 seconds"){
        WHEN("the period is 2sec and the initial is 5sec"){
            // time:   |-------------|
            // events:      1 2 3 4 5
            // buffers: ----
            //            --1-
            //              1-2-
            //                2-3-
            //                  3-4-
            //                    4-5
            //                      5
            using namespace std::chrono;

            #define TIME milliseconds
            #define UNIT *15

            auto sc = rxsc::make_current_thread();
            auto so = rx::synchronize_in_one_worker(sc);
            auto start = sc.now() + TIME(5 UNIT);
            auto period = TIME(2 UNIT);

            std::runtime_error ex("buffer_with_time on_error from source");

            auto ys1 = rx::observable<>::interval(start, period, so).take(5);
            auto ys2 = rx::observable<>::error<long, std::runtime_error>(std::runtime_error("buffer_with_time on_error from source"), so);
            ys1.concat(so, ys2)
                .buffer_with_time(TIME(4 UNIT), TIME(2 UNIT), so)
                .subscribe(
                    [](std::vector<long> counter){
                        printf("on_next: ");
                        std::for_each(counter.begin(), counter.end(), [](long c){
                            printf("%ld ", c);
                        });
                        printf("\n");
                    },
                    [](rxu::error_ptr){
                        printf("on_error\n");
                    },
                    [](){
                        printf("on_completed\n");
                    }
                );
        }
    }
}

SCENARIO("buffer with time, overlapping intervals", "[buffer_with_time][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto so = rx::synchronize_in_one_worker(sc);
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(100, 1),
            on.next(210, 2),
            on.next(240, 3),
            on.next(280, 4),
            on.next(320, 5),
            on.next(350, 6),
            on.next(380, 7),
            on.next(420, 8),
            on.next(470, 9),
            on.completed(600)
        });
        WHEN("group ints on intersecting intervals"){
            using namespace std::chrono;

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer_with_time(milliseconds(100), milliseconds(70), so)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(301, rxu::to_vector({ 2, 3, 4 })),
                    v_on.next(371, rxu::to_vector({ 4, 5, 6 })),
                    v_on.next(441, rxu::to_vector({ 6, 7, 8 })),
                    v_on.next(511, rxu::to_vector({ 8, 9 })),
                    v_on.next(581, std::vector<int>()),
                    v_on.next(601, std::vector<int>()),
                    v_on.completed(601)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 600)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer with time, intervals with skips", "[buffer_with_time][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto so = rx::synchronize_in_one_worker(sc);
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(100, 1),
            on.next(210, 2),
            on.next(240, 3),
            on.next(280, 4),
            on.next(320, 5),
            on.next(350, 6),
            on.next(380, 7),
            on.next(420, 8),
            on.next(470, 9),
            on.completed(600)
        });
        WHEN("group ints on intervals with skips"){
            using namespace std::chrono;

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer_with_time(milliseconds(70), milliseconds(100), so)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(271, rxu::to_vector({ 2, 3 })),
                    v_on.next(371, rxu::to_vector({ 5, 6 })),
                    v_on.next(471, rxu::to_vector({ 8, 9 })),
                    v_on.next(571, std::vector<int>()),
                    v_on.completed(601)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 600)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer with time, error", "[buffer_with_time][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto so = rx::synchronize_in_one_worker(sc);
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        std::runtime_error ex("buffer_with_time on_error from source");

        auto xs = sc.make_hot_observable({
            on.next(100, 1),
            on.next(210, 2),
            on.next(240, 3),
            on.next(280, 4),
            on.next(320, 5),
            on.next(350, 6),
            on.next(380, 7),
            on.next(420, 8),
            on.next(470, 9),
            on.error(600, ex)
        });
        WHEN("group ints on intersecting intervals"){
            using namespace std::chrono;

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer_with_time(milliseconds(100), milliseconds(70), so)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(301, rxu::to_vector({ 2, 3, 4 })),
                    v_on.next(371, rxu::to_vector({ 4, 5, 6 })),
                    v_on.next(441, rxu::to_vector({ 6, 7, 8 })),
                    v_on.next(511, rxu::to_vector({ 8, 9 })),
                    v_on.next(581, std::vector<int>()),
                    v_on.error(601, ex)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 600)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer with time, disposed", "[buffer_with_time][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto so = rx::synchronize_in_one_worker(sc);
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(100, 1),
            on.next(210, 2),
            on.next(240, 3),
            on.next(280, 4),
            on.next(320, 5),
            on.next(350, 6),
            on.next(380, 7),
            on.next(420, 8),
            on.next(470, 9),
            on.completed(600)
        });
        WHEN("group ints on intersecting intervals"){
            using namespace std::chrono;

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer_with_time(milliseconds(100), milliseconds(70), so)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                },
                370
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(301, rxu::to_vector({ 2, 3, 4 })),
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 371)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer with time, same", "[buffer_with_time][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto so = rx::synchronize_in_one_worker(sc);
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(100, 1),
            on.next(210, 2),
            on.next(240, 3),
            on.next(280, 4),
            on.next(320, 5),
            on.next(350, 6),
            on.next(380, 7),
            on.next(420, 8),
            on.next(470, 9),
            on.completed(600)
        });
        WHEN("group ints on intervals"){
            using namespace std::chrono;

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer_with_time(milliseconds(100), so)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(301, rxu::to_vector({ 2, 3, 4 })),
                    v_on.next(401, rxu::to_vector({ 5, 6, 7 })),
                    v_on.next(501, rxu::to_vector({ 8, 9 })),
                    v_on.next(601, std::vector<int>()),
                    v_on.completed(601)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 600)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer with time or count, basic", "[buffer_with_time_or_count][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto so = rx::synchronize_in_one_worker(sc);
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(205, 1),
            on.next(210, 2),
            on.next(240, 3),
            on.next(280, 4),
            on.next(320, 5),
            on.next(350, 6),
            on.next(370, 7),
            on.next(420, 8),
            on.next(470, 9),
            on.completed(600)
        });
        WHEN("group ints on intervals"){
            using namespace std::chrono;

            auto res = w.start(
                [&]() {
                    return xs
                        | rxo::buffer_with_time_or_count(milliseconds(70), 3, so)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        | rxo::as_dynamic();
                }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(241, rxu::to_vector({ 1, 2, 3 })),
                    v_on.next(312, rxu::to_vector({ 4 })),
                    v_on.next(371, rxu::to_vector({ 5, 6, 7 })),
                    v_on.next(442, rxu::to_vector({ 8 })),
                    v_on.next(512, rxu::to_vector({ 9 })),
                    v_on.next(582, std::vector<int>()),
                    v_on.next(601, std::vector<int>()),
                    v_on.completed(601)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 600)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer with time or count, error", "[buffer_with_time_or_count][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto so = rx::synchronize_in_one_worker(sc);
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        std::runtime_error ex("buffer_with_time on_error from source");

        auto xs = sc.make_hot_observable({
            on.next(205, 1),
            on.next(210, 2),
            on.next(240, 3),
            on.next(280, 4),
            on.next(320, 5),
            on.next(350, 6),
            on.next(370, 7),
            on.next(420, 8),
            on.next(470, 9),
            on.error(600, ex)
        });
        WHEN("group ints on intervals"){
            using namespace std::chrono;

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer_with_time_or_count(milliseconds(70), 3, so)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(241, rxu::to_vector({ 1, 2, 3 })),
                    v_on.next(312, rxu::to_vector({ 4 })),
                    v_on.next(371, rxu::to_vector({ 5, 6, 7 })),
                    v_on.next(442, rxu::to_vector({ 8 })),
                    v_on.next(512, rxu::to_vector({ 9 })),
                    v_on.next(582, std::vector<int>()),
                    v_on.error(601, ex)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 600)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer with time or count, dispose", "[buffer_with_time_or_count][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto so = rx::synchronize_in_one_worker(sc);
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(205, 1),
            on.next(210, 2),
            on.next(240, 3),
            on.next(280, 4),
            on.next(320, 5),
            on.next(350, 6),
            on.next(370, 7),
            on.next(420, 8),
            on.next(470, 9),
            on.completed(600)
        });
        WHEN("group ints on intervals"){
            using namespace std::chrono;

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer_with_time_or_count(milliseconds(70), 3, so)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                },
                372
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(241, rxu::to_vector({ 1, 2, 3 })),
                    v_on.next(312, rxu::to_vector({ 4 })),
                    v_on.next(371, rxu::to_vector({ 5, 6, 7 })),
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 373)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer with time or count, only time triggered", "[buffer_with_time_or_count][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto so = rx::synchronize_in_one_worker(sc);
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(205, 1),
            on.next(305, 2),
            on.next(505, 3),
            on.next(605, 4),
            on.next(610, 5),
            on.completed(850)
        });
        WHEN("group ints on intervals"){
            using namespace std::chrono;

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer_with_time_or_count(milliseconds(100), 3, so)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(301, rxu::to_vector({ 1 })),
                    v_on.next(401, rxu::to_vector({ 2 })),
                    v_on.next(501, std::vector<int>()),
                    v_on.next(601, rxu::to_vector({ 3 })),
                    v_on.next(701, rxu::to_vector({ 4, 5 })),
                    v_on.next(801, std::vector<int>()),
                    v_on.next(851, std::vector<int>()),
                    v_on.completed(851)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 850)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}

SCENARIO("buffer with time or count, only count triggered", "[buffer_with_time_or_count][operators]"){
    GIVEN("1 hot observable of ints."){
        auto sc = rxsc::make_test();
        auto so = rx::synchronize_in_one_worker(sc);
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<std::vector<int>> v_on;

        auto xs = sc.make_hot_observable({
            on.next(205, 1),
            on.next(305, 2),
            on.next(505, 3),
            on.next(605, 4),
            on.next(610, 5),
            on.completed(850)
        });
        WHEN("group ints on intervals"){
            using namespace std::chrono;

            auto res = w.start(
                [&]() {
                    return xs
                        .buffer_with_time_or_count(milliseconds(370), 2, so)
                        // forget type to workaround lambda deduction bug on msvc 2013
                        .as_dynamic();
                }
            );

            THEN("the output contains groups of ints"){
                auto required = rxu::to_vector({
                    v_on.next(306, rxu::to_vector({ 1, 2 })),
                    v_on.next(606, rxu::to_vector({ 3, 4 })),
                    v_on.next(851, rxu::to_vector({ 5 })),
                    v_on.completed(851)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

            THEN("there was one subscription and one unsubscription to the xs"){
                auto required = rxu::to_vector({
                    on.subscribe(200, 850)
                });
                auto actual = xs.subscriptions();
                REQUIRE(required == actual);
            }
        }
    }
}