// Copyright (c) 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 "ui/gl/gl_surface_egl_x11.h"

#include "ui/events/platform/platform_event_source.h"
#include "ui/gl/egl_util.h"

extern "C" {
#include <X11/Xlib.h>
}

using ui::GetLastEGLErrorString;
using ui::PlatformEvent;
using ui::PlatformEventSource;

namespace gl {

NativeViewGLSurfaceEGLX11::NativeViewGLSurfaceEGLX11(EGLNativeWindowType window)
    : NativeViewGLSurfaceEGL(0),
      parent_window_(window) {
}

bool NativeViewGLSurfaceEGLX11::InitializeNativeWindow() {
  Display* x11_display = GetNativeDisplay();
  XWindowAttributes attributes;
  if (!XGetWindowAttributes(x11_display, parent_window_, &attributes)) {
    LOG(ERROR) << "XGetWindowAttributes failed for window " << parent_window_
        << ".";
    return false;
  }

  size_ = gfx::Size(attributes.width, attributes.height);

  // Create a child window, with a CopyFromParent visual (to avoid inducing
  // extra blits in the driver), that we can resize exactly in Resize(),
  // correctly ordered with GL, so that we don't have invalid transient states.
  // See https://crbug.com/326995.
  XSetWindowAttributes swa;
  memset(&swa, 0, sizeof(swa));
  swa.background_pixmap = 0;
  swa.bit_gravity = NorthWestGravity;
  window_ = XCreateWindow(x11_display, parent_window_, 0, 0, size_.width(),
                          size_.height(), 0, CopyFromParent, InputOutput,
                          CopyFromParent, CWBackPixmap | CWBitGravity, &swa);
  XMapWindow(x11_display, window_);

  // The event source can be nullptr in tests, when we don't care about Exposes.
  if (PlatformEventSource* source = PlatformEventSource::GetInstance()) {
    XSelectInput(x11_display, window_, ExposureMask);
    source->AddPlatformEventDispatcher(this);
  }
  XFlush(x11_display);

  return true;
}

void NativeViewGLSurfaceEGLX11::Destroy() {
  if (window_) {
    if (PlatformEventSource* source = PlatformEventSource::GetInstance())
      source->RemovePlatformEventDispatcher(this);

    Display* x11_display = GetNativeDisplay();
    XDestroyWindow(x11_display, window_);
    window_ = 0;
    XFlush(x11_display);
  }

  NativeViewGLSurfaceEGL::Destroy();
}

EGLConfig NativeViewGLSurfaceEGLX11::GetConfig() {
  if (!config_) {
    // Get a config compatible with the window
    DCHECK(window_);
    XWindowAttributes win_attribs;
    if (!XGetWindowAttributes(GetNativeDisplay(), window_, &win_attribs)) {
      return NULL;
    }

    // Try matching the window depth with an alpha channel,
    // because we're worried the destination alpha width could
    // constrain blending precision.
    const int kBufferSizeOffset = 1;
    const int kAlphaSizeOffset = 3;
    EGLint config_attribs[] = {
      EGL_BUFFER_SIZE, ~0,
      EGL_ALPHA_SIZE, 8,
      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
    };
    config_attribs[kBufferSizeOffset] = win_attribs.depth;

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

    if (num_configs) {
      EGLint config_depth;
      if (!eglGetConfigAttrib(display,
                              config_,
                              EGL_BUFFER_SIZE,
                              &config_depth)) {
        LOG(ERROR) << "eglGetConfigAttrib failed with error "
                   << GetLastEGLErrorString();
        return NULL;
      }

      if (config_depth == win_attribs.depth) {
        return config_;
      }
    }

    // Try without an alpha channel.
    config_attribs[kAlphaSizeOffset] = 0;
    if (!eglChooseConfig(display,
                         config_attribs,
                         &config_,
                         1,
                         &num_configs)) {
      LOG(ERROR) << "eglChooseConfig failed with error "
                 << GetLastEGLErrorString();
      return NULL;
    }

    if (num_configs == 0) {
      LOG(ERROR) << "No suitable EGL configs found.";
      return NULL;
    }
  }
  return config_;
}

bool NativeViewGLSurfaceEGLX11::Resize(const gfx::Size& size,
                                       float scale_factor,
                                       bool has_alpha) {
  if (size == GetSize())
    return true;

  size_ = size;

  eglWaitGL();
  XResizeWindow(GetNativeDisplay(), window_, size.width(), size.height());
  eglWaitNative(EGL_CORE_NATIVE_ENGINE);

  return true;
}

bool NativeViewGLSurfaceEGLX11::CanDispatchEvent(const PlatformEvent& event) {
  return event->type == Expose && event->xexpose.window == window_;
}

uint32_t NativeViewGLSurfaceEGLX11::DispatchEvent(const PlatformEvent& event) {
  XEvent x_event = *event;
  x_event.xexpose.window = parent_window_;

  Display* x11_display = GetNativeDisplay();
  XSendEvent(x11_display, parent_window_, False, ExposureMask, &x_event);
  XFlush(x11_display);
  return ui::POST_DISPATCH_STOP_PROPAGATION;
}

NativeViewGLSurfaceEGLX11::~NativeViewGLSurfaceEGLX11() {
  Destroy();
}

}  // namespace gl
