#include "../test.h"
#include "rxcpp/operators/rx-sequence_equal.hpp"

SCENARIO("sequence_equal - source never emits", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1)
        });

        auto ys = sc.make_hot_observable({
            on.next(150, 1),
            on.next(200, 2),
            on.next(300, 3),
            on.next(400, 4),
            on.next(500, 5),
            on.completed(600)
        });

        WHEN("two observables are checked for equality"){

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

            THEN("the output is empty"){
                auto required = std::vector<rxsc::test::messages<bool>::recorded_type>();
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

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

SCENARIO("sequence_equal - other source never emits", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(310, 3),
            on.next(410, 4),
            on.next(510, 5),
            on.completed(610)
        });

        auto ys = sc.make_hot_observable({
            on.next(150, 1)
        });

        WHEN("two observables are checked for equality"){

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

            THEN("the output is empty"){
                auto required = std::vector<rxsc::test::messages<bool>::recorded_type>();
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

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

SCENARIO("sequence_equal - both sources never emit any items", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1)
        });

        auto ys = sc.make_hot_observable({
            on.next(150, 0),
        });

        WHEN("two observables are checked for equality"){

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

            THEN("the output is empty"){
                auto required = std::vector<rxsc::test::messages<bool>::recorded_type>();
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

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

SCENARIO("sequence_equal - both sources emit the same sequence of items", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<bool> o_on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(310, 3),
            on.next(410, 4),
            on.next(510, 5),
            on.completed(610)
        });

        auto ys = sc.make_hot_observable({
            on.next(150, 1),
            on.next(220, 2),
            on.next(330, 3),
            on.next(440, 4),
            on.next(550, 5),
            on.completed(600)
        });

        WHEN("two observables are checked for equality"){

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

            THEN("the output contains true"){
                auto required = rxu::to_vector({
                    o_on.next(610, true),
                    o_on.completed(610)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

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

SCENARIO("sequence_equal - first source emits less items than the second one", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<bool> o_on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(310, 3),
            on.next(410, 4),
            on.completed(610)
        });

        auto ys = sc.make_hot_observable({
            on.next(150, 1),
            on.next(220, 2),
            on.next(330, 3),
            on.next(440, 4),
            on.next(550, 5),
            on.completed(600)
        });

        WHEN("two observables are checked for equality"){

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

            THEN("the output contains false"){
                auto required = rxu::to_vector({
                    o_on.next(610, false),
                    o_on.completed(610)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

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

SCENARIO("sequence_equal - second source emits less items than the first one", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<bool> o_on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(310, 3),
            on.next(410, 4),
            on.next(510, 5),
            on.completed(610)
        });

        auto ys = sc.make_hot_observable({
            on.next(150, 1),
            on.next(220, 2),
            on.next(330, 3),
            on.next(440, 4),
            on.completed(600)
        });

        WHEN("two observables are checked for equality"){

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

            THEN("the output contains false"){
                auto required = rxu::to_vector({
                    o_on.next(610, false),
                    o_on.completed(610)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

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

SCENARIO("sequence_equal - sources emit different sequence of items", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<bool> o_on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(310, 9), //
            on.next(410, 4),
            on.next(510, 5),
            on.completed(610)
        });

        auto ys = sc.make_hot_observable({
            on.next(150, 1),
            on.next(220, 2),
            on.next(330, 3),
            on.next(440, 4),
            on.next(550, 5),
            on.completed(600)
        });

        WHEN("two observables are checked for equality"){

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

            THEN("the output contains false"){
                auto required = rxu::to_vector({
                    o_on.next(330, false),
                    o_on.completed(330)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

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

SCENARIO("sequence_equal - sources emit items in a different order", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<bool> o_on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(310, 3),
            on.next(410, 4),
            on.next(510, 5),
            on.completed(610)
        });

        auto ys = sc.make_hot_observable({
            on.next(150, 1),
            on.next(220, 2),
            on.next(330, 4),
            on.next(440, 3),
            on.next(550, 5),
            on.completed(600)
        });

        WHEN("two observables are checked for equality"){

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

            THEN("the output contains false"){
                auto required = rxu::to_vector({
                    o_on.next(330, false),
                    o_on.completed(330)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

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

SCENARIO("sequence_equal - source observable is empty", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<bool> o_on;

        auto xs = sc.make_hot_observable({
            on.completed(250)
        });

        auto ys = sc.make_hot_observable({
            on.next(150, 1),
            on.next(220, 2),
            on.next(330, 3),
            on.completed(600)
        });

        WHEN("two observables are checked for equality"){

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

            THEN("the output contains false"){
                auto required = rxu::to_vector({
                    o_on.next(330, false),
                    o_on.completed(330)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

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

SCENARIO("sequence_equal - other observable is empty", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<bool> o_on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(310, 3),
            on.completed(400)
        });

        auto ys = sc.make_hot_observable({
            on.completed(250)
        });

        WHEN("two observables are checked for equality"){

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

            THEN("the output contains false"){
                auto required = rxu::to_vector({
                    o_on.next(310, false),
                    o_on.completed(310)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

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

SCENARIO("sequence_equal - both observables are empty", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<bool> o_on;

        auto xs = sc.make_hot_observable({
            on.completed(400)
        });

        auto ys = sc.make_hot_observable({
            on.completed(250)
        });

        WHEN("two observables are checked for equality"){

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

            THEN("the output contains false"){
                auto required = rxu::to_vector({
                    o_on.next(400, true),
                    o_on.completed(400)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

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

SCENARIO("sequence_equal - source observable emits an error", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<bool> o_on;

        std::runtime_error ex("sequence_equal error");

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.error(250, ex)
        });

        auto ys = sc.make_hot_observable({
            on.next(150, 1),
            on.next(220, 2),
            on.next(550, 5),
            on.completed(600)
        });

        WHEN("two observables are checked for equality"){

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

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

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

SCENARIO("sequence_equal - other observable emits an error", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<bool> o_on;

        std::runtime_error ex("sequence_equal error");

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(310, 3),
            on.completed(400)
        });

        auto ys = sc.make_hot_observable({
            on.next(150, 1),
            on.error(250, ex)
        });

        WHEN("two observables are checked for equality"){

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

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

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

SCENARIO("sequence_equal - both observables emit errors", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<bool> o_on;

        std::runtime_error ex("sequence_equal error1");

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.error(250, ex)
        });

        auto ys = sc.make_hot_observable({
            on.error(300, ex)
        });

        WHEN("two observables are checked for equality"){

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

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

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

SCENARIO("sequence_equal - both sources emit the same sequence of items, custom comparing function", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<bool> o_on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(310, 3),
            on.next(410, 4),
            on.next(510, 5),
            on.completed(610)
        });

        auto ys = sc.make_hot_observable({
            on.next(150, 1),
            on.next(220, 2),
            on.next(330, 3),
            on.next(440, 4),
            on.next(550, 5),
            on.completed(600)
        });

        WHEN("two observables are checked for equality"){

            auto res = w.start(
                [xs, ys]() {
                    return xs
                            .sequence_equal(ys, [](int x, int y) { return x == y; })
                            .as_dynamic(); // forget type to workaround lambda deduction bug on msvc 2013
                }
            );

            THEN("the output contains true"){
                auto required = rxu::to_vector({
                    o_on.next(610, true),
                    o_on.completed(610)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

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

SCENARIO("sequence_equal - both sources emit the same sequence of items, custom coordinator", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<bool> o_on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(310, 3),
            on.next(410, 4),
            on.next(510, 5),
            on.completed(610)
        });

        auto ys = sc.make_hot_observable({
            on.next(150, 1),
            on.next(220, 2),
            on.next(330, 3),
            on.next(440, 4),
            on.next(550, 5),
            on.completed(600)
        });

        WHEN("two observables are checked for equality"){

            auto res = w.start(
                [xs, ys]() {
                    return xs
                            .sequence_equal(ys, rxcpp::identity_one_worker(rxsc::make_current_thread()))
                            .as_dynamic(); // forget type to workaround lambda deduction bug on msvc 2013
                }
            );

            THEN("the output contains true"){
                auto required = rxu::to_vector({
                    o_on.next(610, true),
                    o_on.completed(610)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

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

SCENARIO("sequence_equal - both sources emit the same sequence of items, custom comparing function and coordinator", "[sequence_equal][operators]"){
    GIVEN("two sources"){
        auto sc = rxsc::make_test();
        auto w = sc.create_worker();
        const rxsc::test::messages<int> on;
        const rxsc::test::messages<bool> o_on;

        auto xs = sc.make_hot_observable({
            on.next(150, 1),
            on.next(210, 2),
            on.next(310, 3),
            on.next(410, 4),
            on.next(510, 5),
            on.completed(610)
        });

        auto ys = sc.make_hot_observable({
            on.next(150, 1),
            on.next(220, 2),
            on.next(330, 3),
            on.next(440, 4),
            on.next(550, 5),
            on.completed(600)
        });

        WHEN("two observables are checked for equality"){

            auto res = w.start(
                [xs, ys]() {
                    return xs
                            .sequence_equal(ys, [](int x, int y) { return x == y; }, rxcpp::identity_one_worker(rxsc::make_current_thread()))
                            .as_dynamic(); // forget type to workaround lambda deduction bug on msvc 2013
                }
            );

            THEN("the output contains true"){
                auto required = rxu::to_vector({
                    o_on.next(610, true),
                    o_on.completed(610)
                });
                auto actual = res.get_observer().messages();
                REQUIRE(required == actual);
            }

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