//
// Copyright (c) 2014 The ANGLE Project 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 "gmock/gmock.h"
#include "gtest/gtest.h"
#include "libANGLE/Fence.h"
#include "libANGLE/renderer/FenceNVImpl.h"
#include "libANGLE/renderer/FenceSyncImpl.h"

using ::testing::_;
using ::testing::Return;
using ::testing::SetArgumentPointee;

namespace {

//
// FenceNV tests
//

class MockFenceNVImpl : public rx::FenceNVImpl
{
  public:
    virtual ~MockFenceNVImpl() { destroy(); }

    MOCK_METHOD0(set, gl::Error());
    MOCK_METHOD2(test, gl::Error(bool, GLboolean *));
    MOCK_METHOD1(finishFence, gl::Error(GLboolean *));

    MOCK_METHOD0(destroy, void());
};

class FenceNVTest : public testing::Test
{
  protected:
    virtual void SetUp()
    {
        mImpl = new MockFenceNVImpl;
        EXPECT_CALL(*mImpl, destroy());
        mFence = new gl::FenceNV(mImpl);
    }

    virtual void TearDown()
    {
        delete mFence;
    }

    MockFenceNVImpl *mImpl;
    gl::FenceNV* mFence;
};

TEST_F(FenceNVTest, DestructionDeletesImpl)
{
    MockFenceNVImpl* impl = new MockFenceNVImpl;
    EXPECT_CALL(*impl, destroy()).Times(1).RetiresOnSaturation();

    gl::FenceNV* fence = new gl::FenceNV(impl);
    delete fence;

    // Only needed because the mock is leaked if bugs are present,
    // which logs an error, but does not cause the test to fail.
    // Ordinarily mocks are verified when destroyed.
    testing::Mock::VerifyAndClear(impl);
}

TEST_F(FenceNVTest, SetAndTestBehavior)
{
    EXPECT_CALL(*mImpl, set())
        .WillOnce(Return(gl::Error(GL_NO_ERROR)))
        .RetiresOnSaturation();
    EXPECT_EQ(GL_FALSE, mFence->isFence());
    mFence->setFence(GL_ALL_COMPLETED_NV);
    EXPECT_EQ(GL_TRUE, mFence->isFence());
    // Fake the behavior of testing the fence before and after it's passed.
    EXPECT_CALL(*mImpl, test(_, _))
        .WillOnce(DoAll(SetArgumentPointee<1>(GL_FALSE),
                        Return(gl::Error(GL_NO_ERROR))))
        .WillOnce(DoAll(SetArgumentPointee<1>(GL_TRUE),
                        Return(gl::Error(GL_NO_ERROR))))
        .RetiresOnSaturation();
    GLboolean out;
    mFence->testFence(&out);
    EXPECT_EQ(GL_FALSE, out);
    mFence->testFence(&out);
    EXPECT_EQ(GL_TRUE, out);
}

//
// FenceSync tests
//

class MockFenceSyncImpl : public rx::FenceSyncImpl
{
  public:
    virtual ~MockFenceSyncImpl() { destroy(); }

    MOCK_METHOD0(set, gl::Error());
    MOCK_METHOD3(clientWait, gl::Error(GLbitfield, GLuint64, GLenum *));
    MOCK_METHOD2(serverWait, gl::Error(GLbitfield, GLuint64));
    MOCK_METHOD1(getStatus, gl::Error(GLint *));

    MOCK_METHOD0(destroy, void());
};

class FenceSyncTest : public testing::Test
{
  protected:
    virtual void SetUp()
    {
        mImpl = new MockFenceSyncImpl;
        EXPECT_CALL(*mImpl, destroy());
        mFence = new gl::FenceSync(mImpl, 1);
        mFence->addRef();
    }

    virtual void TearDown()
    {
        mFence->release();
    }

    MockFenceSyncImpl *mImpl;
    gl::FenceSync* mFence;
};

TEST_F(FenceSyncTest, DestructionDeletesImpl)
{
    MockFenceSyncImpl* impl = new MockFenceSyncImpl;
    EXPECT_CALL(*impl, destroy()).Times(1).RetiresOnSaturation();

    gl::FenceSync* fence = new gl::FenceSync(impl, 1);
    fence->addRef();
    fence->release();

    // Only needed because the mock is leaked if bugs are present,
    // which logs an error, but does not cause the test to fail.
    // Ordinarily mocks are verified when destroyed.
    testing::Mock::VerifyAndClear(impl);
}

TEST_F(FenceSyncTest, SetAndGetStatusBehavior)
{
    EXPECT_CALL(*mImpl, set())
        .WillOnce(Return(gl::Error(GL_NO_ERROR)))
        .RetiresOnSaturation();
    mFence->set(GL_SYNC_GPU_COMMANDS_COMPLETE);
    EXPECT_EQ(GL_SYNC_GPU_COMMANDS_COMPLETE, mFence->getCondition());
    // Fake the behavior of testing the fence before and after it's passed.
    EXPECT_CALL(*mImpl, getStatus(_))
        .WillOnce(DoAll(SetArgumentPointee<0>(GL_UNSIGNALED),
                        Return(gl::Error(GL_NO_ERROR))))
        .WillOnce(DoAll(SetArgumentPointee<0>(GL_SIGNALED),
                        Return(gl::Error(GL_NO_ERROR))))
        .RetiresOnSaturation();
    GLint out;
    mFence->getStatus(&out);
    EXPECT_EQ(GL_UNSIGNALED, out);
    mFence->getStatus(&out);
    EXPECT_EQ(GL_SIGNALED, out);
}

} // namespace
