// Copyright 2012 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 "cc/texture_layer.h"

#include <string>

#include "base/callback.h"
#include "cc/layer_tree_host.h"
#include "cc/layer_tree_impl.h"
#include "cc/single_thread_proxy.h"
#include "cc/test/fake_impl_proxy.h"
#include "cc/test/fake_layer_tree_host_client.h"
#include "cc/test/fake_layer_tree_host_impl.h"
#include "cc/test/layer_tree_test_common.h"
#include "cc/texture_layer_impl.h"
#include "cc/thread.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::Mock;
using ::testing::_;
using ::testing::AtLeast;
using ::testing::AnyNumber;

namespace cc {
namespace {

class MockLayerImplTreeHost : public LayerTreeHost {
public:
    MockLayerImplTreeHost()
        : LayerTreeHost(&m_fakeClient, LayerTreeSettings())
    {
        initialize(scoped_ptr<Thread>(NULL));
    }

    MOCK_METHOD0(acquireLayerTextures, void());
    MOCK_METHOD0(setNeedsCommit, void());

private:
    FakeLayerImplTreeHostClient m_fakeClient;
};


class TextureLayerTest : public testing::Test {
public:
    TextureLayerTest()
        : m_hostImpl(&m_proxy)
    {
    }

protected:
    virtual void SetUp()
    {
        m_layerTreeHost.reset(new MockLayerImplTreeHost);
    }

    virtual void TearDown()
    {
        Mock::VerifyAndClearExpectations(m_layerTreeHost.get());
        EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(AnyNumber());
        EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AnyNumber());

        m_layerTreeHost->setRootLayer(0);
        m_layerTreeHost.reset();
    }

    scoped_ptr<MockLayerImplTreeHost> m_layerTreeHost;
    FakeImplProxy m_proxy;
    FakeLayerTreeHostImpl m_hostImpl;
};

TEST_F(TextureLayerTest, syncImplWhenChangingTextureId)
{
    scoped_refptr<TextureLayer> testLayer = TextureLayer::create(0);
    ASSERT_TRUE(testLayer);

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(AnyNumber());
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AnyNumber());
    m_layerTreeHost->setRootLayer(testLayer);
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());
    EXPECT_EQ(testLayer->layerTreeHost(), m_layerTreeHost.get());

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(0);
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AtLeast(1));
    testLayer->setTextureId(1);
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(AtLeast(1));
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AtLeast(1));
    testLayer->setTextureId(2);
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(AtLeast(1));
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AtLeast(1));
    testLayer->setTextureId(0);
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());
}

TEST_F(TextureLayerTest, syncImplWhenDrawing)
{
    gfx::RectF dirtyRect(0, 0, 1, 1);

    scoped_refptr<TextureLayer> testLayer = TextureLayer::create(0);
    ASSERT_TRUE(testLayer);
    scoped_ptr<TextureLayerImpl> implLayer;
    implLayer = TextureLayerImpl::create(m_hostImpl.activeTree(), 1, false);
    ASSERT_TRUE(implLayer);

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(AnyNumber());
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AnyNumber());
    m_layerTreeHost->setRootLayer(testLayer);
    testLayer->setTextureId(1);
    testLayer->setIsDrawable(true);
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());
    EXPECT_EQ(testLayer->layerTreeHost(), m_layerTreeHost.get());

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(1);
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(0);
    testLayer->willModifyTexture();
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(0);
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(1);
    testLayer->setNeedsDisplayRect(dirtyRect);
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(0);
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(1);
    testLayer->pushPropertiesTo(implLayer.get()); // fake commit
    testLayer->setIsDrawable(false);
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());

    // Verify that non-drawable layers don't signal the compositor,
    // except for the first draw after last commit, which must acquire
    // the texture.
    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(1);
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(0);
    testLayer->willModifyTexture();
    testLayer->setNeedsDisplayRect(dirtyRect);
    testLayer->pushPropertiesTo(implLayer.get()); // fake commit
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());

    // Second draw with layer in non-drawable state: no texture
    // acquisition.
    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(0);
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(0);
    testLayer->willModifyTexture();
    testLayer->setNeedsDisplayRect(dirtyRect);
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());
}

TEST_F(TextureLayerTest, syncImplWhenRemovingFromTree)
{
    scoped_refptr<Layer> rootLayer = Layer::create();
    ASSERT_TRUE(rootLayer);
    scoped_refptr<Layer> childLayer = Layer::create();
    ASSERT_TRUE(childLayer);
    rootLayer->addChild(childLayer);
    scoped_refptr<TextureLayer> testLayer = TextureLayer::create(0);
    ASSERT_TRUE(testLayer);
    testLayer->setTextureId(0);
    childLayer->addChild(testLayer);

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(AnyNumber());
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AnyNumber());
    m_layerTreeHost->setRootLayer(rootLayer);
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(0);
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AtLeast(1));
    testLayer->removeFromParent();
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(0);
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AtLeast(1));
    childLayer->addChild(testLayer);
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(0);
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AtLeast(1));
    testLayer->setTextureId(1);
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(AtLeast(1));
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AtLeast(1));
    testLayer->removeFromParent();
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());
}

class MockMailboxCallback {
public:
    MOCK_METHOD2(Release, void(const std::string& mailbox, unsigned syncPoint));
};

struct CommonMailboxObjects {
    CommonMailboxObjects()
        : m_mailboxName1(64, '1')
        , m_mailboxName2(64, '2')
        , m_syncPoint1(1)
        , m_syncPoint2(2)
    {
        m_releaseMailbox1 = base::Bind(&MockMailboxCallback::Release,
                                       base::Unretained(&m_mockCallback),
                                       m_mailboxName1);
        m_releaseMailbox2 = base::Bind(&MockMailboxCallback::Release,
                                       base::Unretained(&m_mockCallback),
                                       m_mailboxName2);
        Mailbox m1;
        m1.setName(reinterpret_cast<const int8*>(m_mailboxName1.data()));
        m_mailbox1 = TextureMailbox(m1, m_releaseMailbox1, m_syncPoint1);
        Mailbox m2;
        m2.setName(reinterpret_cast<const int8*>(m_mailboxName2.data()));
        m_mailbox2 = TextureMailbox(m2, m_releaseMailbox2, m_syncPoint2);
    }

    std::string m_mailboxName1;
    std::string m_mailboxName2;
    MockMailboxCallback m_mockCallback;
    TextureMailbox::ReleaseCallback m_releaseMailbox1;
    TextureMailbox::ReleaseCallback m_releaseMailbox2;
    TextureMailbox m_mailbox1;
    TextureMailbox m_mailbox2;
    unsigned m_syncPoint1;
    unsigned m_syncPoint2;
};

class TextureLayerWithMailboxTest : public TextureLayerTest {
protected:
    virtual void TearDown()
    {
        Mock::VerifyAndClearExpectations(&m_testData.m_mockCallback);
        EXPECT_CALL(m_testData.m_mockCallback,
                    Release(m_testData.m_mailboxName1,
                            m_testData.m_syncPoint1)).Times(1);
        TextureLayerTest::TearDown();
    }

    CommonMailboxObjects m_testData;
};

TEST_F(TextureLayerWithMailboxTest, replaceMailboxOnMainThreadBeforeCommit)
{
    scoped_refptr<TextureLayer> testLayer = TextureLayer::createForMailbox();
    ASSERT_TRUE(testLayer);

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(0);
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AnyNumber());
    m_layerTreeHost->setRootLayer(testLayer);
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(0);
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AtLeast(1));
    testLayer->setTextureMailbox(m_testData.m_mailbox1);
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(0);
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AtLeast(1));
    EXPECT_CALL(m_testData.m_mockCallback,
                Release(m_testData.m_mailboxName1,
                        m_testData.m_syncPoint1)).Times(1);
    testLayer->setTextureMailbox(m_testData.m_mailbox2);
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());
    Mock::VerifyAndClearExpectations(&m_testData.m_mockCallback);

    EXPECT_CALL(*m_layerTreeHost, acquireLayerTextures()).Times(0);
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AtLeast(1));
    EXPECT_CALL(m_testData.m_mockCallback,
                Release(m_testData.m_mailboxName2,
                        m_testData.m_syncPoint2)).Times(1);
    testLayer->setTextureMailbox(TextureMailbox());
    Mock::VerifyAndClearExpectations(m_layerTreeHost.get());
    Mock::VerifyAndClearExpectations(&m_testData.m_mockCallback);

    // Test destructor.
    EXPECT_CALL(*m_layerTreeHost, setNeedsCommit()).Times(AtLeast(1));
    testLayer->setTextureMailbox(m_testData.m_mailbox1);
}

class TextureLayerImplWithMailboxThreadedCallback : public ThreadedTest {
public:
    TextureLayerImplWithMailboxThreadedCallback()
        : m_resetMailbox(false)
    {
    }

    // Make sure callback is received on main and doesn't block the impl thread.
    void releaseCallback(unsigned syncPoint) {
        EXPECT_EQ(true, proxy()->isMainThread());
        endTest();
    }

    virtual void beginTest() OVERRIDE
    {
        m_layer = TextureLayer::createForMailbox();
        m_layer->setIsDrawable(true);
        m_layerTreeHost->setRootLayer(m_layer);
        TextureMailbox mailbox(
            std::string(64, '1'),
            base::Bind(
                &TextureLayerImplWithMailboxThreadedCallback::releaseCallback,
                base::Unretained(this)));
        m_layer->setTextureMailbox(mailbox);
        postSetNeedsCommitToMainThread();
    }

    virtual void didCommit() OVERRIDE
    {
        if (m_resetMailbox)
            return;

        m_layer->setTextureMailbox(TextureMailbox());
        m_resetMailbox = true;
    }

    virtual void afterTest() OVERRIDE
    {
    }

private:
    bool m_resetMailbox;
    scoped_refptr<TextureLayer> m_layer;
};

SINGLE_AND_MULTI_THREAD_TEST_F(TextureLayerImplWithMailboxThreadedCallback);

class TextureLayerImplWithMailboxTest : public TextureLayerTest {
protected:
    virtual void SetUp()
    {
        TextureLayerTest::SetUp();
        m_layerTreeHost.reset(new MockLayerImplTreeHost);
        EXPECT_TRUE(m_hostImpl.initializeRenderer(createFakeOutputSurface()));
    }

    CommonMailboxObjects m_testData;
};

TEST_F(TextureLayerImplWithMailboxTest, testImplLayerCallbacks)
{
    scoped_ptr<TextureLayerImpl> implLayer;
    implLayer = TextureLayerImpl::create(m_hostImpl.activeTree(), 1, true);
    ASSERT_TRUE(implLayer);

    // Test setting identical mailbox.
    EXPECT_CALL(m_testData.m_mockCallback, Release(_, _)).Times(0);
    implLayer->setTextureMailbox(m_testData.m_mailbox1);
    implLayer->setTextureMailbox(m_testData.m_mailbox1);
    Mock::VerifyAndClearExpectations(&m_testData.m_mockCallback);

    // Test multiple commits without a draw.
    EXPECT_CALL(m_testData.m_mockCallback,
                Release(m_testData.m_mailboxName1,
                        m_testData.m_syncPoint1)).Times(1);
    implLayer->setTextureMailbox(m_testData.m_mailbox2);
    Mock::VerifyAndClearExpectations(&m_testData.m_mockCallback);

    // Test resetting the mailbox.
    EXPECT_CALL(m_testData.m_mockCallback,
                Release(m_testData.m_mailboxName2,
                        m_testData.m_syncPoint2)).Times(1);
    implLayer->setTextureMailbox(TextureMailbox());
    Mock::VerifyAndClearExpectations(&m_testData.m_mockCallback);

    // Test destructor.
    EXPECT_CALL(m_testData.m_mockCallback,
                Release(m_testData.m_mailboxName1,
                        m_testData.m_syncPoint1)).Times(1);
    implLayer->setTextureMailbox(m_testData.m_mailbox1);
}

TEST_F(TextureLayerImplWithMailboxTest, testDestructorCallbackOnCreatedResource)
{
    scoped_ptr<TextureLayerImpl> implLayer;
    implLayer = TextureLayerImpl::create(m_hostImpl.activeTree(), 1, true);
    ASSERT_TRUE(implLayer);

    EXPECT_CALL(m_testData.m_mockCallback,
                Release(m_testData.m_mailboxName1, _)).Times(1);
    implLayer->setTextureMailbox(m_testData.m_mailbox1);
    implLayer->willDraw(m_hostImpl.activeTree()->resource_provider());
    implLayer->didDraw(m_hostImpl.activeTree()->resource_provider());
    implLayer->setTextureMailbox(TextureMailbox());
}

TEST_F(TextureLayerImplWithMailboxTest, testCallbackOnInUseResource)
{
    ResourceProvider *provider = m_hostImpl.activeTree()->resource_provider();
    ResourceProvider::ResourceId id =
        provider->createResourceFromTextureMailbox(m_testData.m_mailbox1);
    provider->allocateForTesting(id);

    // Transfer some resources to the parent.
    ResourceProvider::ResourceIdArray resourceIdsToTransfer;
    resourceIdsToTransfer.push_back(id);
    TransferableResourceList list;
    provider->prepareSendToParent(resourceIdsToTransfer, &list);
    EXPECT_TRUE(provider->inUseByConsumer(id));
    EXPECT_CALL(m_testData.m_mockCallback, Release(_, _)).Times(0);
    provider->deleteResource(id);
    Mock::VerifyAndClearExpectations(&m_testData.m_mockCallback);
    EXPECT_CALL(m_testData.m_mockCallback,
                Release(m_testData.m_mailboxName1, _)).Times(1);
    provider->receiveFromParent(list);
}

}  // namespace
}  // namespace cc