#include <sled/futures/when_all.h>
#include <sled/random.h>
#include <sled/system/thread_pool.h>
#include <sled/time_utils.h>

TEST_SUITE("futures when_all")
{
    TEST_CASE("single thread")
    {
        sled::Promise<int> p1;
        sled::Promise<int> p2;
        sled::Promise<int> p3;

        auto f1 = p1.GetFuture();
        auto f2 = p2.GetFuture();
        auto f3 = p3.GetFuture();

        SUBCASE("all success")
        {
            p1.Success(1);
            p2.Success(2);
            p3.Success(3);

            CHECK(WhenAll(f1, f2, f3));
            CHECK_EQ(f1.Result(), 1);
            CHECK_EQ(f2.Result(), 2);
            CHECK_EQ(f3.Result(), 3);
        }
        SUBCASE("all failed")
        {
            p1.Failure(sled::failure::FailureFromString<sled::Promise<int>::FailureType>("1"));
            p2.Failure(sled::failure::FailureFromString<sled::Promise<int>::FailureType>("2"));
            p3.Failure(sled::failure::FailureFromString<sled::Promise<int>::FailureType>("3"));

            CHECK_FALSE(WhenAll(f1, f2, f3));
            CHECK(f1.IsFailed());
            CHECK(f2.IsFailed());
            CHECK(f3.IsFailed());
        }
    }

    TEST_CASE("multi thread")
    {
        sled::ThreadPool pool(10);
        std::vector<sled::Future<int>> futures;

        SUBCASE("all success")
        {
            for (int i = 0; i < 1000; ++i) {
                sled::Promise<int> p;
                auto f = p.GetFuture();
                pool.PostTask([p, i] { p.Success(i); });
                futures.push_back(f);
            }
            CHECK(WhenAll(futures));
            for (int i = 0; i < 1000; ++i) { CHECK_EQ(futures[i].Result(), i); }
        }

        SUBCASE("all failed")
        {
            for (int i = 0; i < 1000; ++i) {
                sled::Promise<int> p;
                auto f = p.GetFuture();
                pool.PostTask([p, i] {
                    p.Failure(sled::failure::FailureFromString<sled::Promise<int>::FailureType>(std::to_string(i)));
                });
                futures.push_back(f);
            }
            CHECK_FALSE(WhenAll(futures));
            bool has_failed = false;
            for (int i = 0; i < 1000; ++i) {
                if (futures[i].IsFailed()) {
                    has_failed = true;
                    break;
                }
            }
            CHECK(has_failed);
        }

        SUBCASE("some failed")
        {
            for (int i = 0; i < 1000; ++i) {
                sled::Promise<int> p;
                auto f = p.GetFuture();
                pool.PostTask([p, i] {
                    sled::Random random(sled::TimeUTCNanos());
                    sled::Thread::SleepMs(random.Rand(1, 15));
                    if (i % 2 == 0) {
                        p.Success(i);
                    } else {
                        p.Failure(sled::failure::FailureFromString<sled::Promise<int>::FailureType>(std::to_string(i)));
                    }
                });
                futures.push_back(f);
            }
            CHECK_FALSE(WhenAll(futures));
            bool has_failed = false;
            for (int i = 0; i < 1000; ++i) {
                if (futures[i].IsFailed()) {
                    has_failed = true;
                    break;
                }
            }
            CHECK(has_failed);
        }
    }
}
