/*
 * Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
 * that can be found in the LICENSE file.
 */

#include "gmock/gmock.h"
#include "gtest/gtest.h"

#include <list>
#include <vector>

#include "IntrusiveList.hpp"

#include "concurrent/ParallelProcessor.hpp"

#include "SingleThreadExecutor.hpp"
#include "TestSupport.hpp"

using ::testing::_;
using namespace kotlin;

namespace {

class Element {
public:
    Element() : Element(0) {}
    explicit Element(int value) : a(value), b(value), c(value), d(value) {}

    bool isValid() const {
        return a == b && b == c && c == d;
    }

private:
    std::size_t a;
    std::size_t b;
    std::size_t c;
    std::size_t d;
};

} // namespace


TEST(BoundedQueueTest, ConcurrentEnqueue) {
    constexpr auto kThreadCount = 16;
    constexpr auto kElemsPerThread = 1024;
    BoundedQueue<Element, kThreadCount * kElemsPerThread> queue;

    std::atomic<bool> start = false;
    std::list<ScopedThread> threads;
    for (int t = 0; t < kThreadCount; ++t) {
        threads.emplace_back([&, t]() {
            while (!start) {
                std::this_thread::yield();
            }
            for (int e = 0; e < kElemsPerThread; ++e) {
                queue.enqueue(Element(t + e));
            }
        });
    }
    start = true;

    threads.clear();

    while (auto elem = queue.dequeue()) {
        EXPECT_TRUE(elem->isValid());
    }
}

TEST(BoundedQueueTest, ConcurrentDequeue) {
    constexpr auto kThreadCount = 16;
    constexpr auto kElemsPerThread = 1024;
    BoundedQueue<Element, kThreadCount * kElemsPerThread> queue;

    for (int i = 0; i < kThreadCount * kElemsPerThread; ++i) {
        queue.enqueue(Element(i));
    }

    std::atomic<bool> start = false;
    std::list<ScopedThread> threads;
    for (int t = 0; t < kThreadCount; ++t) {
        threads.emplace_back([&]() {
            while (!start) {
                std::this_thread::yield();
            }
            while (auto elem = queue.dequeue()) {
                EXPECT_TRUE(elem->isValid());
            }
        });
    }
    start = true;

    threads.clear();
}

TEST(BoundedQueueTest, PingPongWithOverflow) {
    constexpr auto kElemsPerThread = 1024;
    BoundedQueue<Element, kElemsPerThread / 2> queue;

    std::atomic<bool> start = false;
    std::list<ScopedThread> writers;
    for (std::size_t t = 0; t < kDefaultThreadCount; ++t) {
        writers.emplace_back([&]() {
            while (!start) {
                std::this_thread::yield();
            }
            for (int i = 0; i < kElemsPerThread; ++i) {
                while (!queue.enqueue(Element(i))) {
                    std::this_thread::yield();
                }
            }
        });
    }

    std::atomic<bool> allWritten = false;
    std::list<ScopedThread> readers;
    for (std::size_t t = 0; t < kDefaultThreadCount; ++t) {
        readers.emplace_back([&]() {
            while (!start) {
                std::this_thread::yield();
            }
            while (!allWritten) {
                while (auto elem = queue.dequeue()) {
                    EXPECT_TRUE(elem->isValid());
                }
            }
        });
    }

    start = true;
    writers.clear();
    allWritten = true;
}
