// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "config.h"
#include "platform/scheduler/Scheduler.h"

#include "platform/TestingPlatformSupport.h"
#include "platform/TraceLocation.h"
#include "public/platform/Platform.h"
#include "public/platform/WebThread.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <string>
#include <vector>

using blink::Scheduler;
using namespace std;

namespace {

class TestMainThread : public blink::WebThread {
public:
    // blink::WebThread implementation.
    virtual void postTask(Task* task) OVERRIDE
    {
        m_pendingTasks.append(adoptPtr(task));
    }

    virtual void postDelayedTask(Task* task, long long delayMs) OVERRIDE
    {
        ASSERT_NOT_REACHED();
    }

    virtual bool isCurrentThread() const OVERRIDE
    {
        return true;
    }

    virtual void enterRunLoop() OVERRIDE
    {
        ASSERT_NOT_REACHED();
    }

    virtual void exitRunLoop() OVERRIDE
    {
        ASSERT_NOT_REACHED();
    }

    void runPendingTasks()
    {
        while (!m_pendingTasks.isEmpty())
            m_pendingTasks.takeFirst()->run();
    }

private:
    WTF::Deque<OwnPtr<Task> > m_pendingTasks;
};

class SchedulerTestingPlatformSupport : blink::TestingPlatformSupport {
public:
    SchedulerTestingPlatformSupport()
        : TestingPlatformSupport(TestingPlatformSupport::Config())
        , m_sharedTimerFunction(nullptr)
        , m_sharedTimerRunning(false)
        , m_sharedTimerFireInterval(0)
    {
    }

    // blink::Platform implementation.
    virtual blink::WebThread* currentThread() OVERRIDE
    {
        return &m_mainThread;
    }

    virtual void setSharedTimerFiredFunction(SharedTimerFunction timerFunction) OVERRIDE
    {
        m_sharedTimerFunction = timerFunction;
    }

    virtual void setSharedTimerFireInterval(double)
    {
        m_sharedTimerFireInterval = 0;
        m_sharedTimerRunning = true;
    }

    virtual void stopSharedTimer()
    {
        m_sharedTimerRunning = false;
    }

    void runPendingTasks()
    {
        m_mainThread.runPendingTasks();
    }

    bool sharedTimerRunning() const
    {
        return m_sharedTimerRunning;
    }

    double sharedTimerFireInterval() const
    {
        return m_sharedTimerFireInterval;
    }

    void triggerSharedTimer()
    {
        m_sharedTimerFunction();
    }

private:
    TestMainThread m_mainThread;
    SharedTimerFunction m_sharedTimerFunction;
    bool m_sharedTimerRunning;
    double m_sharedTimerFireInterval;
};

class SchedulerTest : public testing::Test {
public:
    SchedulerTest()
        : m_reentrantCount(0)
        , m_maxRecursion(4)
    {
        Scheduler::initializeOnMainThread();
        m_scheduler = Scheduler::shared();
    }

    ~SchedulerTest()
    {
        Scheduler::shutdown();
    }

    void runPendingTasks()
    {
        m_platformSupport.runPendingTasks();
    }

    void appendToVector(string value)
    {
        m_order.push_back(value);
    }

    void appendToVectorReentrantTask()
    {
        m_reentrantOrder.push_back(m_reentrantCount++);

        if (m_reentrantCount > m_maxRecursion)
            return;
        Scheduler::shared()->postTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVectorReentrantTask, this));
    }

    void appendToVectorReentrantInputTask()
    {
        m_reentrantOrder.push_back(m_reentrantCount++);

        if (m_reentrantCount > m_maxRecursion)
            return;
        m_scheduler->postInputTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVectorReentrantInputTask, this));
    }

    void appendToVectorReentrantCompositorTask()
    {
        m_reentrantOrder.push_back(m_reentrantCount++);

        if (m_reentrantCount > m_maxRecursion)
            return;
        m_scheduler->postCompositorTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVectorReentrantCompositorTask, this));
    }

protected:
    SchedulerTestingPlatformSupport m_platformSupport;
    Scheduler* m_scheduler;
    std::vector<string> m_order;
    std::vector<int> m_reentrantOrder;
    int m_reentrantCount;
    int m_maxRecursion;
};

void orderedTestTask(int value, int* result)
{
    *result = (*result << 4) | value;
}

void unorderedTestTask(int value, int* result)
{
    *result += value;
}

void idleTestTask(int value, int* result, double allottedTime)
{
    *result += value;
}

TEST_F(SchedulerTest, TestPostTask)
{
    int result = 0;
    m_scheduler->postTask(FROM_HERE, WTF::bind(&orderedTestTask, 1, &result));
    m_scheduler->postTask(FROM_HERE, WTF::bind(&orderedTestTask, 2, &result));
    m_scheduler->postTask(FROM_HERE, WTF::bind(&orderedTestTask, 3, &result));
    m_scheduler->postTask(FROM_HERE, WTF::bind(&orderedTestTask, 4, &result));
    runPendingTasks();
    EXPECT_EQ(0x1234, result);
}

TEST_F(SchedulerTest, TestPostMixedTaskTypes)
{
    int result = 0;
    m_scheduler->postTask(FROM_HERE, WTF::bind(&unorderedTestTask, 1, &result));
    m_scheduler->postInputTask(FROM_HERE, WTF::bind(&unorderedTestTask, 2, &result));
    m_scheduler->postCompositorTask(FROM_HERE, WTF::bind(&unorderedTestTask, 4, &result));
    m_scheduler->postTask(FROM_HERE, WTF::bind(&unorderedTestTask, 8, &result));
    runPendingTasks();
    EXPECT_EQ(15, result);
}

int s_sharedTimerTickCount;
void sharedTimerFunction()
{
    s_sharedTimerTickCount++;
}

TEST_F(SchedulerTest, TestSharedTimer)
{
    s_sharedTimerTickCount = 0;
    m_scheduler->setSharedTimerFiredFunction(&sharedTimerFunction);
    EXPECT_FALSE(m_platformSupport.sharedTimerRunning());
    m_scheduler->setSharedTimerFireInterval(0);
    EXPECT_TRUE(m_platformSupport.sharedTimerRunning());

    m_platformSupport.triggerSharedTimer();
    EXPECT_EQ(1, s_sharedTimerTickCount);

    m_scheduler->stopSharedTimer();
    EXPECT_FALSE(m_platformSupport.sharedTimerRunning());

    m_scheduler->setSharedTimerFiredFunction(nullptr);
    EXPECT_FALSE(m_platformSupport.sharedTimerRunning());
}

TEST_F(SchedulerTest, TestIdleTask)
{
    // TODO: Check task allottedTime when implemented in the scheduler.
    int result = 0;
    m_scheduler->postIdleTask(FROM_HERE, WTF::bind<double>(&idleTestTask, 1, &result));
    m_scheduler->postIdleTask(FROM_HERE, WTF::bind<double>(&idleTestTask, 1, &result));
    m_scheduler->postIdleTask(FROM_HERE, WTF::bind<double>(&idleTestTask, 1, &result));
    m_scheduler->postIdleTask(FROM_HERE, WTF::bind<double>(&idleTestTask, 1, &result));
    runPendingTasks();
    EXPECT_EQ(4, result);
}

TEST_F(SchedulerTest, TestTaskPrioritization)
{
    m_scheduler->postTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVector, this, string("L1")));
    m_scheduler->postTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVector, this, string("L2")));
    m_scheduler->postInputTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVector, this, string("I1")));
    m_scheduler->postInputTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVector, this, string("I2")));
    m_scheduler->postCompositorTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVector, this, string("C1")));
    m_scheduler->postCompositorTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVector, this, string("C2")));

    runPendingTasks();
    EXPECT_THAT(m_order, testing::ElementsAre(
        string("I1"), string("I2"), string("C1"), string("C2"), string("L1"), string("L2")));
}

TEST_F(SchedulerTest, TestRentrantTask)
{
    m_scheduler->postTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVectorReentrantTask, this));
    runPendingTasks();

    EXPECT_THAT(m_reentrantOrder, testing::ElementsAre(0, 1, 2, 3, 4));
}


TEST_F(SchedulerTest, TestRentrantInputTaskDuringShutdown)
{
    m_scheduler->postInputTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVectorReentrantInputTask, this));
    Scheduler::shutdown();

    EXPECT_THAT(m_reentrantOrder, testing::ElementsAre(0, 1, 2, 3, 4));
}

TEST_F(SchedulerTest, TestRentrantCompositorTaskDuringShutdown)
{
    m_scheduler->postCompositorTask(FROM_HERE, WTF::bind(&SchedulerTest::appendToVectorReentrantCompositorTask, this));
    Scheduler::shutdown();

    EXPECT_THAT(m_reentrantOrder, testing::ElementsAre(0, 1, 2, 3, 4));
}

} // namespace
