// Copyright 2016 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 "gpu/command_buffer/common/capabilities.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/graphics/gpu/DrawingBuffer.h"
#include "platform/graphics/gpu/Extensions3DUtil.h"
#include "public/platform/WebGraphicsContext3DProvider.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace blink {

enum {
  InitialWidth = 100,
  InitialHeight = 100,
  AlternateHeight = 50,
};

class DrawingBufferForTests : public DrawingBuffer {
 public:
  static PassRefPtr<DrawingBufferForTests> create(
      std::unique_ptr<WebGraphicsContext3DProvider> contextProvider,
      DrawingBuffer::Client* client,
      const IntSize& size,
      PreserveDrawingBuffer preserve) {
    std::unique_ptr<Extensions3DUtil> extensionsUtil =
        Extensions3DUtil::create(contextProvider->contextGL());
    RefPtr<DrawingBufferForTests> drawingBuffer = adoptRef(
        new DrawingBufferForTests(std::move(contextProvider),
                                  std::move(extensionsUtil), client, preserve));
    bool multisampleExtensionSupported = false;
    if (!drawingBuffer->initialize(size, multisampleExtensionSupported)) {
      drawingBuffer->beginDestruction();
      return nullptr;
    }
    return drawingBuffer.release();
  }

  DrawingBufferForTests(
      std::unique_ptr<WebGraphicsContext3DProvider> contextProvider,
      std::unique_ptr<Extensions3DUtil> extensionsUtil,
      DrawingBuffer::Client* client,
      PreserveDrawingBuffer preserve)
      : DrawingBuffer(
            std::move(contextProvider),
            std::move(extensionsUtil),
            client,
            false /* discardFramebufferSupported */,
            true /* wantAlphaChannel */,
            false /* premultipliedAlpha */,
            preserve,
            WebGL1,
            false /* wantDepth */,
            false /* wantStencil */,
            DrawingBuffer::AllowChromiumImage /* ChromiumImageUsage */),
        m_live(0) {}

  ~DrawingBufferForTests() override {
    if (m_live)
      *m_live = false;
  }

  bool* m_live;

  int recycledBitmapCount() { return m_recycledBitmaps.size(); }
};

class WebGraphicsContext3DProviderForTests
    : public WebGraphicsContext3DProvider {
 public:
  WebGraphicsContext3DProviderForTests(
      std::unique_ptr<gpu::gles2::GLES2Interface> gl)
      : m_gl(std::move(gl)) {}

  gpu::gles2::GLES2Interface* contextGL() override { return m_gl.get(); }
  bool isSoftwareRendering() const override { return false; }

  // Not used by WebGL code.
  GrContext* grContext() override { return nullptr; }
  bool bindToCurrentThread() override { return false; }
  gpu::Capabilities getCapabilities() override { return gpu::Capabilities(); }
  void setLostContextCallback(const base::Closure&) {}
  void setErrorMessageCallback(
      const base::Callback<void(const char*, int32_t id)>&) {}
  void signalQuery(uint32_t, const base::Closure&) override {}

 private:
  std::unique_ptr<gpu::gles2::GLES2Interface> m_gl;
};

// The target to use when binding a texture to a Chromium image.
GLenum imageCHROMIUMTextureTarget() {
#if OS(MACOSX)
  return GC3D_TEXTURE_RECTANGLE_ARB;
#else
  return GL_TEXTURE_2D;
#endif
}

// The target to use when preparing a mailbox texture.
GLenum drawingBufferTextureTarget() {
  if (RuntimeEnabledFeatures::webGLImageChromiumEnabled())
    return imageCHROMIUMTextureTarget();
  return GL_TEXTURE_2D;
}

class GLES2InterfaceForTests : public gpu::gles2::GLES2InterfaceStub,
                               public DrawingBuffer::Client {
 public:
  // GLES2InterfaceStub implementation:
  void BindTexture(GLenum target, GLuint texture) override {
    if (target == GL_TEXTURE_2D)
      m_state.activeTexture2DBinding = texture;
    m_boundTextures[target] = texture;
  }

  void BindFramebuffer(GLenum target, GLuint framebuffer) override {
    switch (target) {
      case GL_FRAMEBUFFER:
        m_state.drawFramebufferBinding = framebuffer;
        m_state.readFramebufferBinding = framebuffer;
        break;
      case GL_DRAW_FRAMEBUFFER:
        m_state.drawFramebufferBinding = framebuffer;
        break;
      case GL_READ_FRAMEBUFFER:
        m_state.readFramebufferBinding = framebuffer;
        break;
      default:
        break;
    }
  }

  void BindRenderbuffer(GLenum target, GLuint renderbuffer) override {
    m_state.renderbufferBinding = renderbuffer;
  }

  void Enable(GLenum cap) {
    if (cap == GL_SCISSOR_TEST)
      m_state.scissorEnabled = true;
  }

  void Disable(GLenum cap) {
    if (cap == GL_SCISSOR_TEST)
      m_state.scissorEnabled = false;
  }

  void ClearColor(GLfloat red,
                  GLfloat green,
                  GLfloat blue,
                  GLfloat alpha) override {
    m_state.clearColor[0] = red;
    m_state.clearColor[1] = green;
    m_state.clearColor[2] = blue;
    m_state.clearColor[3] = alpha;
  }

  void ClearDepthf(GLfloat depth) override { m_state.clearDepth = depth; }

  void ClearStencil(GLint s) override { m_state.clearStencil = s; }

  void ColorMask(GLboolean red,
                 GLboolean green,
                 GLboolean blue,
                 GLboolean alpha) override {
    m_state.colorMask[0] = red;
    m_state.colorMask[1] = green;
    m_state.colorMask[2] = blue;
    m_state.colorMask[3] = alpha;
  }

  void DepthMask(GLboolean flag) override { m_state.depthMask = flag; }

  void StencilMask(GLuint mask) override { m_state.stencilMask = mask; }

  void StencilMaskSeparate(GLenum face, GLuint mask) override {
    if (face == GL_FRONT)
      m_state.stencilMask = mask;
  }

  void PixelStorei(GLenum pname, GLint param) override {
    if (pname == GL_PACK_ALIGNMENT)
      m_state.packAlignment = param;
  }

  void BindBuffer(GLenum target, GLuint buffer) override {
    switch (target) {
      case GL_PIXEL_UNPACK_BUFFER:
        m_state.pixelUnpackBufferBinding = buffer;
        break;
      case GL_PIXEL_PACK_BUFFER:
        m_state.pixelPackBufferBinding = buffer;
        break;
      default:
        break;
    }
  }

  GLuint64 InsertFenceSyncCHROMIUM() override {
    static GLuint64 syncPointGenerator = 0;
    return ++syncPointGenerator;
  }

  void WaitSyncTokenCHROMIUM(const GLbyte* syncToken) override {
    memcpy(&m_mostRecentlyWaitedSyncToken, syncToken,
           sizeof(m_mostRecentlyWaitedSyncToken));
  }

  GLenum CheckFramebufferStatus(GLenum target) override {
    return GL_FRAMEBUFFER_COMPLETE;
  }

  void GetIntegerv(GLenum pname, GLint* value) override {
    if (pname == GL_MAX_TEXTURE_SIZE)
      *value = 1024;
  }

  void GenMailboxCHROMIUM(GLbyte* mailbox) override {
    ++m_currentMailboxByte;
    memset(mailbox, m_currentMailboxByte, GL_MAILBOX_SIZE_CHROMIUM);
  }

  void ProduceTextureDirectCHROMIUM(GLuint texture,
                                    GLenum target,
                                    const GLbyte* mailbox) override {
    ASSERT_EQ(target, drawingBufferTextureTarget());

    if (!m_createImageChromiumFail) {
      ASSERT_TRUE(m_textureSizes.contains(texture));
      m_mostRecentlyProducedSize = m_textureSizes.get(texture);
    }
  }

  void TexImage2D(GLenum target,
                  GLint level,
                  GLint internalformat,
                  GLsizei width,
                  GLsizei height,
                  GLint border,
                  GLenum format,
                  GLenum type,
                  const void* pixels) override {
    if (target == GL_TEXTURE_2D && !level) {
      m_textureSizes.set(m_boundTextures[target], IntSize(width, height));
    }
  }

  GLuint CreateGpuMemoryBufferImageCHROMIUM(GLsizei width,
                                            GLsizei height,
                                            GLenum internalformat,
                                            GLenum usage) override {
    if (m_createImageChromiumFail)
      return 0;
    m_imageSizes.set(m_currentImageId, IntSize(width, height));
    return m_currentImageId++;
  }

  MOCK_METHOD1(DestroyImageMock, void(GLuint imageId));
  void DestroyImageCHROMIUM(GLuint imageId) {
    m_imageSizes.remove(imageId);
    // No textures should be bound to this.
    CHECK(m_imageToTextureMap.find(imageId) == m_imageToTextureMap.end());
    m_imageSizes.remove(imageId);
    DestroyImageMock(imageId);
  }

  MOCK_METHOD1(BindTexImage2DMock, void(GLint imageId));
  void BindTexImage2DCHROMIUM(GLenum target, GLint imageId) {
    if (target == imageCHROMIUMTextureTarget()) {
      m_textureSizes.set(m_boundTextures[target],
                         m_imageSizes.find(imageId)->value);
      m_imageToTextureMap.set(imageId, m_boundTextures[target]);
      BindTexImage2DMock(imageId);
    }
  }

  MOCK_METHOD1(ReleaseTexImage2DMock, void(GLint imageId));
  void ReleaseTexImage2DCHROMIUM(GLenum target, GLint imageId) {
    if (target == imageCHROMIUMTextureTarget()) {
      m_imageSizes.set(m_currentImageId, IntSize());
      m_imageToTextureMap.remove(imageId);
      ReleaseTexImage2DMock(imageId);
    }
  }

  void GenSyncTokenCHROMIUM(GLuint64 fenceSync, GLbyte* syncToken) override {
    static uint64_t uniqueId = 1;
    gpu::SyncToken source(gpu::GPU_IO, 1,
                          gpu::CommandBufferId::FromUnsafeValue(uniqueId++), 2);
    memcpy(syncToken, &source, sizeof(source));
  }

  void GenTextures(GLsizei n, GLuint* textures) override {
    static GLuint id = 1;
    for (GLsizei i = 0; i < n; ++i)
      textures[i] = id++;
  }
  void DrawingBufferClientRestorePixelPackBufferBinding() override {
    state_.pixel_pack_buffer_binding = saved_state_.pixel_pack_buffer_binding;
  }

  // DrawingBuffer::Client implementation.
  bool DrawingBufferClientIsBoundForDraw() override {
    return !m_state.drawFramebufferBinding;
  }
  void DrawingBufferClientRestoreScissorTest() override {
    m_state.scissorEnabled = m_savedState.scissorEnabled;
  }
  void DrawingBufferClientRestoreMaskAndClearValues() override {
    memcpy(m_state.colorMask, m_savedState.colorMask,
           sizeof(m_state.colorMask));
    m_state.clearDepth = m_savedState.clearDepth;
    m_state.clearStencil = m_savedState.clearStencil;

    memcpy(m_state.clearColor, m_savedState.clearColor,
           sizeof(m_state.clearColor));
    m_state.depthMask = m_savedState.depthMask;
    m_state.stencilMask = m_savedState.stencilMask;
  }
  void DrawingBufferClientRestorePixelPackParameters() override {
    // TODO(zmo): restore ES3 pack parameters?
    m_state.packAlignment = m_savedState.packAlignment;
  }
  void DrawingBufferClientRestoreTexture2DBinding() override {
    m_state.activeTexture2DBinding = m_savedState.activeTexture2DBinding;
  }
  void DrawingBufferClientRestoreRenderbufferBinding() override {
    m_state.renderbufferBinding = m_savedState.renderbufferBinding;
  }
  void DrawingBufferClientRestoreFramebufferBinding() override {
    m_state.drawFramebufferBinding = m_savedState.drawFramebufferBinding;
    m_state.readFramebufferBinding = m_savedState.readFramebufferBinding;
  }
  void DrawingBufferClientRestorePixelUnpackBufferBinding() override {
    m_state.pixelUnpackBufferBinding = m_savedState.pixelUnpackBufferBinding;
  }

  // Testing methods.
  gpu::SyncToken mostRecentlyWaitedSyncToken() const {
    return m_mostRecentlyWaitedSyncToken;
  }
  GLuint nextImageIdToBeCreated() const { return m_currentImageId; }
  IntSize mostRecentlyProducedSize() const {
    return m_mostRecentlyProducedSize;
  }

  void setCreateImageChromiumFail(bool fail) {
    m_createImageChromiumFail = fail;
  }

  // Saves current GL state for later verification.
  void SaveState() { m_savedState = m_state; }
  void VerifyStateHasNotChangedSinceSave() const {
    for (size_t i = 0; i < 4; ++i) {
      EXPECT_EQ(m_state.clearColor[0], m_savedState.clearColor[0]);
      EXPECT_EQ(m_state.colorMask[0], m_savedState.colorMask[0]);
    }
    EXPECT_EQ(m_state.clearDepth, m_savedState.clearDepth);
    EXPECT_EQ(m_state.clearStencil, m_savedState.clearStencil);
    EXPECT_EQ(m_state.depthMask, m_savedState.depthMask);
    EXPECT_EQ(m_state.stencilMask, m_savedState.stencilMask);
    EXPECT_EQ(m_state.packAlignment, m_savedState.packAlignment);
    EXPECT_EQ(m_state.activeTexture2DBinding,
              m_savedState.activeTexture2DBinding);
    EXPECT_EQ(m_state.renderbufferBinding, m_savedState.renderbufferBinding);
    EXPECT_EQ(m_state.drawFramebufferBinding,
              m_savedState.drawFramebufferBinding);
    EXPECT_EQ(m_state.readFramebufferBinding,
              m_savedState.readFramebufferBinding);
    EXPECT_EQ(m_state.pixelUnpackBufferBinding,
              m_savedState.pixelUnpackBufferBinding);
    EXPECT_EQ(m_state.pixelPackBufferBinding,
              m_savedState.pixelPackBufferBinding);
  }

 private:
  std::map<GLenum, GLuint> m_boundTextures;

  // State tracked to verify that it is restored correctly.
  struct State {
    bool scissorEnabled = false;

    GLfloat clearColor[4] = {0, 0, 0, 0};
    GLfloat clearDepth = 0;
    GLint clearStencil = 0;

    GLboolean colorMask[4] = {0, 0, 0, 0};
    GLboolean depthMask = 0;
    GLuint stencilMask = 0;

    GLint packAlignment = 4;

    // The bound 2D texture for the active texture unit.
    GLuint activeTexture2DBinding = 0;
    GLuint renderbufferBinding = 0;
    GLuint drawFramebufferBinding = 0;
    GLuint readFramebufferBinding = 0;
    GLuint pixelUnpackBufferBinding = 0;
    GLuint pixelPackBufferBinding = 0;
  };
  State m_state;
  State m_savedState;

  gpu::SyncToken m_mostRecentlyWaitedSyncToken;
  GLbyte m_currentMailboxByte = 0;
  IntSize m_mostRecentlyProducedSize;
  bool m_createImageChromiumFail = false;
  GLuint m_currentImageId = 1;
  HashMap<GLuint, IntSize> m_textureSizes;
  HashMap<GLuint, IntSize> m_imageSizes;
  HashMap<GLuint, GLuint> m_imageToTextureMap;
};

}  // blink
