// Copyright (c) 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.

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

#include <memory>

#include "base/logging.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_image_glx.h"
#include "ui/gl/gl_surface_glx.h"

namespace gl {
namespace {

bool ValidFormat(unsigned internalformat) {
  switch (internalformat) {
    case GL_RGB:
    case GL_RGBA:
      return true;
    default:
      return false;
  }
}

int TextureFormat(unsigned internalformat) {
  switch (internalformat) {
    case GL_RGB:
      return GLX_TEXTURE_FORMAT_RGB_EXT;
    case GL_RGBA:
      return GLX_TEXTURE_FORMAT_RGBA_EXT;
    default:
      NOTREACHED();
      return 0;
  }
}

int BindToTextureFormat(unsigned internalformat) {
  switch (internalformat) {
    case GL_RGB:
      return GLX_BIND_TO_TEXTURE_RGB_EXT;
    case GL_RGBA:
      return GLX_BIND_TO_TEXTURE_RGBA_EXT;
    default:
      NOTREACHED();
      return 0;
  }
}

unsigned PixmapDepth(unsigned internalformat) {
  switch (internalformat) {
    case GL_RGBA:
      return 32u;
    case GL_RGB:
      return 24u;
    default:
      NOTREACHED();
      return 0u;
  }
}

bool ActualPixmapGeometry(XID pixmap, gfx::Size* size, unsigned* depth) {
  XID root_return;
  int x_return;
  int y_return;
  unsigned width_return;
  unsigned height_return;
  unsigned border_width_return;
  unsigned depth_return;
  if (!XGetGeometry(gfx::GetXDisplay(), pixmap, &root_return, &x_return,
                    &y_return, &width_return, &height_return,
                    &border_width_return, &depth_return))
    return false;

  if (size)
    *size = gfx::Size(width_return, height_return);
  if (depth)
    *depth = depth_return;
  return true;
}

unsigned ActualPixmapDepth(XID pixmap) {
  unsigned depth;
  if (!ActualPixmapGeometry(pixmap, NULL, &depth))
    return -1;

  return depth;
}

gfx::Size ActualPixmapSize(XID pixmap) {
  gfx::Size size;
  if (!ActualPixmapGeometry(pixmap, &size, NULL))
    return gfx::Size();

  return size;
}

}  // namespace

GLImageGLX::GLImageGLX(const gfx::Size& size, unsigned internalformat)
    : glx_pixmap_(0), size_(size), internalformat_(internalformat) {}

GLImageGLX::~GLImageGLX() {
  if (glx_pixmap_)
    glXDestroyGLXPixmap(gfx::GetXDisplay(), glx_pixmap_);
}

bool GLImageGLX::Initialize(XID pixmap) {
  if (!GLSurfaceGLX::IsTextureFromPixmapSupported()) {
    DVLOG(0) << "GLX_EXT_texture_from_pixmap not supported.";
    return false;
  }

  if (!ValidFormat(internalformat_)) {
    DVLOG(0) << "Invalid format: " << internalformat_;
    return false;
  }

  DCHECK_EQ(PixmapDepth(internalformat_), ActualPixmapDepth(pixmap));
  DCHECK_EQ(size_.ToString(), ActualPixmapSize(pixmap).ToString());

  int config_attribs[] = {
      GLX_DRAWABLE_TYPE,                    GLX_PIXMAP_BIT,
      GLX_BIND_TO_TEXTURE_TARGETS_EXT,      GLX_TEXTURE_2D_EXT,
      BindToTextureFormat(internalformat_), GL_TRUE,
      0};
  int num_elements = 0;
  gfx::XScopedPtr<GLXFBConfig> config(
      glXChooseFBConfig(gfx::GetXDisplay(), DefaultScreen(gfx::GetXDisplay()),
                        config_attribs, &num_elements));
  if (!config.get()) {
    DVLOG(0) << "glXChooseFBConfig failed.";
    return false;
  }
  if (!num_elements) {
    DVLOG(0) << "glXChooseFBConfig returned 0 elements.";
    return false;
  }

  int pixmap_attribs[] = {GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT,
                          GLX_TEXTURE_FORMAT_EXT,
                          TextureFormat(internalformat_), 0};
  glx_pixmap_ = glXCreatePixmap(gfx::GetXDisplay(), *config.get(), pixmap,
                                pixmap_attribs);
  if (!glx_pixmap_) {
    DVLOG(0) << "glXCreatePixmap failed.";
    return false;
  }

  return true;
}

gfx::Size GLImageGLX::GetSize() {
  return size_;
}

unsigned GLImageGLX::GetInternalFormat() { return internalformat_; }

bool GLImageGLX::BindTexImage(unsigned target) {
  if (!glx_pixmap_)
    return false;

  // Requires TEXTURE_2D target.
  if (target != GL_TEXTURE_2D)
    return false;

  glXBindTexImageEXT(gfx::GetXDisplay(), glx_pixmap_, GLX_FRONT_LEFT_EXT, 0);
  return true;
}

void GLImageGLX::ReleaseTexImage(unsigned target) {
  DCHECK_NE(0u, glx_pixmap_);
  DCHECK_EQ(static_cast<GLenum>(GL_TEXTURE_2D), target);

  glXReleaseTexImageEXT(gfx::GetXDisplay(), glx_pixmap_, GLX_FRONT_LEFT_EXT);
}

bool GLImageGLX::CopyTexImage(unsigned target) {
  return false;
}

bool GLImageGLX::CopyTexSubImage(unsigned target,
                                 const gfx::Point& offset,
                                 const gfx::Rect& rect) {
  return false;
}

bool GLImageGLX::ScheduleOverlayPlane(gfx::AcceleratedWidget widget,
                                      int z_order,
                                      gfx::OverlayTransform transform,
                                      const gfx::Rect& bounds_rect,
                                      const gfx::RectF& crop_rect) {
  return false;
}

void GLImageGLX::OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd,
                              uint64_t process_tracing_id,
                              const std::string& dump_name) {
  // TODO(ericrk): Implement GLImage OnMemoryDump. crbug.com/514914
}

}  // namespace gl
