/*
 * Copyright (C) 2015 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#include "Test.h"
#include <wtf/Condition.h>
#include <wtf/Lock.h>
#include <wtf/Vector.h>
#include <wtf/WorkQueue.h>
#include <string>
#include <thread>

namespace TestWebKitAPI {

using namespace std::literals::chrono_literals;

static const char* simpleTestLabel = "simpleTest";
static const char* longTestLabel = "longTest";
static const char* thirdTestLabel = "thirdTest";
static const char* dispatchAfterLabel = "dispatchAfter";
    
TEST(WTF_WorkQueue, Simple)
{
    Lock m_lock;
    Condition m_testCompleted;
    Vector<std::string> m_functionCallOrder;

    bool calledSimpleTest = false;
    bool calledLongTest = false;
    bool calledThirdTest = false;

    static const char* simpleTestLabel = "simpleTest";
    static const char* longTestLabel = "longTest";
    static const char* thirdTestLabel = "thirdTest";

    auto queue = WorkQueue::create("com.apple.WebKit.Test.simple");
    int initialRefCount = queue->refCount();
    EXPECT_EQ(1, initialRefCount);

    LockHolder locker(m_lock);
    queue->dispatch([&](void) {
        m_functionCallOrder.append(simpleTestLabel);
        calledSimpleTest = true;
    });

    queue->dispatch([&](void) {
        m_functionCallOrder.append(longTestLabel);
        std::this_thread::sleep_for(100ns);
        calledLongTest = true;
    });

    queue->dispatch([&](void) {
        LockHolder locker(m_lock);
        m_functionCallOrder.append(thirdTestLabel);
        calledThirdTest = true;

        EXPECT_TRUE(calledSimpleTest);
        EXPECT_TRUE(calledLongTest);
        EXPECT_TRUE(calledThirdTest);
        
        m_testCompleted.notifyOne();
    });

    EXPECT_GT(queue->refCount(), 1);

    m_testCompleted.wait(m_lock);

    EXPECT_TRUE(calledSimpleTest);
    EXPECT_TRUE(calledLongTest);
    EXPECT_TRUE(calledThirdTest);

    EXPECT_EQ(static_cast<size_t>(3), m_functionCallOrder.size());
    EXPECT_STREQ(simpleTestLabel, m_functionCallOrder[0].c_str());
    EXPECT_STREQ(longTestLabel, m_functionCallOrder[1].c_str());
    EXPECT_STREQ(thirdTestLabel, m_functionCallOrder[2].c_str());
}

TEST(WTF_WorkQueue, TwoQueues)
{
    Lock m_lock;
    Condition m_testQueue1Completed, m_testQueue2Completed;
    Vector<std::string> m_functionCallOrder;

    bool calledSimpleTest = false;
    bool calledLongTest = false;
    bool calledThirdTest = false;
    
    auto queue1 = WorkQueue::create("com.apple.WebKit.Test.twoQueues1");
    auto queue2 = WorkQueue::create("com.apple.WebKit.Test.twoQueues2");

    EXPECT_EQ(1, queue1->refCount());
    EXPECT_EQ(1, queue2->refCount());

    LockHolder locker(m_lock);
    
    queue1->dispatch([&](void) {
        m_functionCallOrder.append(simpleTestLabel);
        calledSimpleTest = true;
    });

    queue2->dispatch([&](void) {
        std::this_thread::sleep_for(50ms);

        LockHolder locker(m_lock);

        // Will fail if queue2 took the mutex before queue1.
        EXPECT_TRUE(calledThirdTest);

        m_functionCallOrder.append(longTestLabel);
        calledLongTest = true;
        m_testQueue2Completed.notifyOne();
    });

    queue1->dispatch([&](void) {
        LockHolder locker(m_lock);
        m_functionCallOrder.append(thirdTestLabel);
        calledThirdTest = true;
        
        m_testQueue1Completed.notifyOne();
    });

    m_testQueue1Completed.wait(m_lock);

    EXPECT_TRUE(calledSimpleTest);
    EXPECT_FALSE(calledLongTest);
    EXPECT_TRUE(calledThirdTest);

    m_testQueue2Completed.wait(m_lock);

    EXPECT_TRUE(calledSimpleTest);
    EXPECT_TRUE(calledLongTest);
    EXPECT_TRUE(calledThirdTest);

    EXPECT_EQ(static_cast<size_t>(3), m_functionCallOrder.size());
    EXPECT_STREQ(simpleTestLabel, m_functionCallOrder[0].c_str());
    EXPECT_STREQ(thirdTestLabel, m_functionCallOrder[1].c_str());
    EXPECT_STREQ(longTestLabel, m_functionCallOrder[2].c_str());
}

TEST(WTF_WorkQueue, DispatchAfter)
{
    Lock m_lock;
    Condition m_testCompleted, m_dispatchAfterTestCompleted;
    Vector<std::string> m_functionCallOrder;

    bool calledSimpleTest = false;
    bool calledDispatchAfterTest = false;

    auto queue = WorkQueue::create("com.apple.WebKit.Test.dispatchAfter");

    LockHolder locker(m_lock);

    queue->dispatch([&](void) {
        LockHolder locker(m_lock);
        m_functionCallOrder.append(simpleTestLabel);
        calledSimpleTest = true;
        m_testCompleted.notifyOne();
    });

    queue->dispatchAfter(500_ms, [&](void) {
        LockHolder locker(m_lock);
        m_functionCallOrder.append(dispatchAfterLabel);
        calledDispatchAfterTest = true;
        m_dispatchAfterTestCompleted.notifyOne();
    });

    m_testCompleted.wait(m_lock);

    EXPECT_TRUE(calledSimpleTest);
    EXPECT_FALSE(calledDispatchAfterTest);
    
    m_dispatchAfterTestCompleted.wait(m_lock);

    EXPECT_TRUE(calledSimpleTest);
    EXPECT_TRUE(calledDispatchAfterTest);

    EXPECT_EQ(static_cast<size_t>(2), m_functionCallOrder.size());
    EXPECT_STREQ(simpleTestLabel, m_functionCallOrder[0].c_str());
    EXPECT_STREQ(dispatchAfterLabel, m_functionCallOrder[1].c_str());
}

TEST(WTF_WorkQueue, DestroyOnSelf)
{
    Lock lock;
    Condition dispatchAfterTestStarted;
    Condition dispatchAfterTestCompleted;
    bool started = false;
    bool completed = false;

    {
        LockHolder locker(lock);
        {
            auto queue = WorkQueue::create("com.apple.WebKit.Test.dispatchAfter");
            queue->dispatchAfter(500_ms, [&](void) {
                LockHolder locker(lock);
                dispatchAfterTestStarted.wait(lock, [&] {
                    return started;
                });
                completed = true;
                dispatchAfterTestCompleted.notifyOne();
            });
        }
        started = true;
        dispatchAfterTestStarted.notifyOne();
    }
    {
        LockHolder locker(lock);
        dispatchAfterTestCompleted.wait(lock, [&] {
            return completed;
        });
        WTF::sleep(0.1);
    }
}

} // namespace TestWebKitAPI
