// Copyright 2015 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 "content/common/gpu/child_window_surface_win.h"

#include "base/compiler_specific.h"
#include "base/win/scoped_hdc.h"
#include "base/win/wrapped_window_proc.h"
#include "content/common/gpu/gpu_channel_manager.h"
#include "content/common/gpu/gpu_messages.h"
#include "ui/base/win/hidden_window.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/win/hwnd_util.h"
#include "ui/gl/egl_util.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_surface_egl.h"
#include "ui/gl/scoped_make_current.h"

namespace content {

namespace {

ATOM g_window_class;

LRESULT CALLBACK IntermediateWindowProc(HWND window,
                                        UINT message,
                                        WPARAM w_param,
                                        LPARAM l_param) {
  switch (message) {
    case WM_ERASEBKGND:
      // Prevent windows from erasing the background.
      return 1;
    case WM_PAINT:
      PAINTSTRUCT paint;
      if (BeginPaint(window, &paint)) {
        ChildWindowSurfaceWin* window_surface =
            reinterpret_cast<ChildWindowSurfaceWin*>(
                gfx::GetWindowUserData(window));
        DCHECK(window_surface);

        // Wait to clear the contents until a GL draw occurs, as otherwise an
        // unsightly black flash may happen if the GL contents are still
        // transparent.
        window_surface->InvalidateWindowRect(gfx::Rect(paint.rcPaint));
        EndPaint(window, &paint);
      }
      return 0;
    default:
      return DefWindowProc(window, message, w_param, l_param);
  }
}

void InitializeWindowClass() {
  if (g_window_class)
    return;

  WNDCLASSEX intermediate_class;
  base::win::InitializeWindowClass(
      L"Intermediate D3D Window",
      &base::win::WrappedWindowProc<IntermediateWindowProc>, CS_OWNDC, 0, 0,
      nullptr, reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)), nullptr,
      nullptr, nullptr, &intermediate_class);
  g_window_class = RegisterClassEx(&intermediate_class);
  if (!g_window_class) {
    LOG(ERROR) << "RegisterClass failed.";
    return;
  }
}
}

ChildWindowSurfaceWin::ChildWindowSurfaceWin(GpuChannelManager* manager,
                                             HWND parent_window)
    : gfx::NativeViewGLSurfaceEGL(0),
      parent_window_(parent_window),
      manager_(manager) {
  // Don't use EGL_ANGLE_window_fixed_size so that we can avoid recreating the
  // window surface, which can cause flicker on DirectComposition.
  enable_fixed_size_angle_ = false;
}

EGLConfig ChildWindowSurfaceWin::GetConfig() {
  if (!config_) {
    int alpha_size = alpha_ ? 8 : EGL_DONT_CARE;

    EGLint config_attribs[] = {EGL_ALPHA_SIZE,
                               alpha_size,
                               EGL_BLUE_SIZE,
                               8,
                               EGL_GREEN_SIZE,
                               8,
                               EGL_RED_SIZE,
                               8,
                               EGL_RENDERABLE_TYPE,
                               EGL_OPENGL_ES2_BIT,
                               EGL_SURFACE_TYPE,
                               EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
                               EGL_NONE};

    EGLDisplay display = GetHardwareDisplay();
    EGLint num_configs;
    if (!eglChooseConfig(display, config_attribs, &config_, 1, &num_configs)) {
      LOG(ERROR) << "eglChooseConfig failed with error "
                 << ui::GetLastEGLErrorString();
      return NULL;
    }
  }

  return config_;
}

bool ChildWindowSurfaceWin::InitializeNativeWindow() {
  if (window_)
    return true;
  InitializeWindowClass();
  DCHECK(g_window_class);

  RECT windowRect;
  GetClientRect(parent_window_, &windowRect);

  window_ = CreateWindowEx(
      WS_EX_NOPARENTNOTIFY, reinterpret_cast<wchar_t*>(g_window_class), L"",
      WS_CHILDWINDOW | WS_DISABLED | WS_VISIBLE, 0, 0,
      windowRect.right - windowRect.left, windowRect.bottom - windowRect.top,
      ui::GetHiddenWindow(), NULL, NULL, NULL);
  gfx::SetWindowUserData(window_, this);
  manager_->Send(new GpuHostMsg_AcceleratedSurfaceCreatedChildWindow(
      parent_window_, window_));
  return true;
}

bool ChildWindowSurfaceWin::Resize(const gfx::Size& size,
                                   float scale_factor,
                                   bool has_alpha) {
  if (!SupportsPostSubBuffer()) {
    if (!MoveWindow(window_, 0, 0, size.width(), size.height(), FALSE)) {
      return false;
    }
    return gfx::NativeViewGLSurfaceEGL::Resize(size, scale_factor, has_alpha);
  } else {
    if (size == GetSize() && has_alpha == alpha_)
      return true;

    if (!MoveWindow(window_, 0, 0, size.width(), size.height(), FALSE)) {
      return false;
    }
    size_ = size;
    if (has_alpha == alpha_) {
      // A 0-size PostSubBuffer doesn't swap but forces the swap chain to resize
      // to match the window.
      PostSubBuffer(0, 0, 0, 0);
    } else {
      alpha_ = has_alpha;
      config_ = nullptr;

      scoped_ptr<ui::ScopedMakeCurrent> scoped_make_current;
      gfx::GLContext* current_context = gfx::GLContext::GetCurrent();
      bool was_current = current_context && current_context->IsCurrent(this);
      if (was_current) {
        scoped_make_current.reset(
            new ui::ScopedMakeCurrent(current_context, this));
        current_context->ReleaseCurrent(this);
      }

      Destroy();

      if (!Initialize()) {
        LOG(ERROR) << "Failed to resize window.";
        return false;
      }
    }
    return true;
  }
}

gfx::SwapResult ChildWindowSurfaceWin::SwapBuffers() {
  gfx::SwapResult result = NativeViewGLSurfaceEGL::SwapBuffers();
  ClearInvalidContents();
  return result;
}

gfx::SwapResult ChildWindowSurfaceWin::PostSubBuffer(int x,
                                                     int y,
                                                     int width,
                                                     int height) {
  gfx::SwapResult result =
      NativeViewGLSurfaceEGL::PostSubBuffer(x, y, width, height);
  ClearInvalidContents();
  return result;
}

void ChildWindowSurfaceWin::InvalidateWindowRect(const gfx::Rect& rect) {
  rect_to_clear_.Union(rect);
}

void ChildWindowSurfaceWin::ClearInvalidContents() {
  if (!rect_to_clear_.IsEmpty()) {
    base::win::ScopedGetDC dc(window_);

    RECT rect = rect_to_clear_.ToRECT();

    // DirectComposition composites with the contents under the SwapChain,
    // so ensure that's cleared. GDI treats black as transparent.
    FillRect(dc, &rect, reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)));
    rect_to_clear_ = gfx::Rect();
  }
}

ChildWindowSurfaceWin::~ChildWindowSurfaceWin() {
  gfx::SetWindowUserData(window_, nullptr);
  DestroyWindow(window_);
}

}  // namespace content
