// Copyright (c) 2009 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 "chrome/browser/gtk/nine_box.h"

#include "app/resource_bundle.h"
#include "base/gfx/gtk_util.h"
#include "base/gfx/point.h"
#include "base/logging.h"

namespace {

// Get the (x,y) coordinates of the widget relative to its GdkWindow.
gfx::Point GetWindowRelativeCoords(GtkWidget* widget) {
  // GtkAllocation (x, y) is relative to widget->window for NO_WINDOW widgets.
  if (GTK_WIDGET_NO_WINDOW(widget))
    return gfx::Point(widget->allocation.x, widget->allocation.y);

  return gfx::Point();
}

// Draw pixbuf |src| into |dst| at position (x, y).
void DrawPixbuf(GtkWidget* dst, GdkPixbuf* src, int x, int y) {
  gfx::Point offset = GetWindowRelativeCoords(dst);

  GdkGC* gc = dst->style->fg_gc[GTK_WIDGET_STATE(dst)];
  gdk_draw_pixbuf(dst->window,  // The destination drawable.
                  gc,  // Graphics context.
                  src, 0, 0,  // Source image and x,y offset.
                  offset.x() + x, offset.y() + y, -1, -1,  // x, y, width, height.
                  GDK_RGB_DITHER_NONE, 0, 0);  // Dithering mode, x,y offsets.
}

}  // anonymous namespace

NineBox::NineBox(int top_left, int top, int top_right, int left, int center,
                 int right, int bottom_left, int bottom, int bottom_right) {
  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
  images_[0] = top_left ? rb.GetPixbufNamed(top_left) : NULL;
  images_[1] = top ? rb.GetPixbufNamed(top) : NULL;
  images_[2] = top_right ? rb.GetPixbufNamed(top_right) : NULL;
  images_[3] = left ? rb.GetPixbufNamed(left) : NULL;
  images_[4] = center ? rb.GetPixbufNamed(center) : NULL;
  images_[5] = right ? rb.GetPixbufNamed(right) : NULL;
  images_[6] = bottom_left ? rb.GetPixbufNamed(bottom_left) : NULL;
  images_[7] = bottom ? rb.GetPixbufNamed(bottom) : NULL;
  images_[8] = bottom_right ? rb.GetPixbufNamed(bottom_right) : NULL;
}

NineBox::~NineBox() {
}

void NineBox::RenderToWidget(GtkWidget* dst) const {
  // TODO(evanm): this is stupid; it should just be implemented with SkBitmaps
  // and convert to a GdkPixbuf at the last second.

  int dst_width = dst->allocation.width;
  int dst_height = dst->allocation.height;

  // This function paints one row at a time.
  // To make indexing sane, |images| points at the current row of images,
  // so images[0] always refers to the left-most image of the current row.
  GdkPixbuf* const* images = &images_[0];

  // The upper-left and lower-right corners of the center square in the
  // rendering of the ninebox.
  const int x1 = gdk_pixbuf_get_width(images[0]);
  const int y1 = gdk_pixbuf_get_height(images[0]);
  const int x2 = images[2] ? dst_width - gdk_pixbuf_get_width(images[2]) : x1;
  const int y2 = images[6] ? dst_height - gdk_pixbuf_get_height(images[6]) : y1;
  DCHECK_GE(x2, x1);
  DCHECK_GE(y2, y1);

  if (images[0])
    DrawPixbuf(dst, images[0], 0, 0);
  if (images[1])
    RenderTopCenterStrip(dst, x1, x2, 0);
  if (images[2])
    DrawPixbuf(dst, images[2], x2, 0);

  // Center row.  Needs vertical tiling.
  images = &images_[1 * 3];
  if (images[0]) {
    TileImage(dst, images[0],
              0, y1,
              0, y2);
  }
  if (images[1]) {
    const int delta_y = gdk_pixbuf_get_height(images[1]);
    for (int y = y1; y < y2; y += delta_y) {
      TileImage(dst, images[1],
                x1, y,
                x2, y);
    }
  }
  if (images[2]) {
    TileImage(dst, images[2],
              x2, y1,
              x2, y2);
  }

  // Bottom row.
  images = &images_[2 * 3];
  if (images[0])
    DrawPixbuf(dst, images[0], 0, y2);
  if (images[1]) {
    TileImage(dst, images[1],
              x1, y2,
              x2, y2);
  }
  if (images[2])
    DrawPixbuf(dst, images[2], x2, y2);
}

void NineBox::RenderTopCenterStrip(GtkWidget* dst, int x1,
                                   int x2, int y1) const {
  TileImage(dst, images_[1],
            x1, y1,
            x2, y1);
}

void NineBox::ChangeWhiteToTransparent() {
  for (int image_idx = 0; image_idx < 9; ++image_idx) {
    GdkPixbuf* pixbuf = images_[image_idx];
    if (!pixbuf)
      continue;

    guchar* pixels = gdk_pixbuf_get_pixels(pixbuf);
    int rowstride = gdk_pixbuf_get_rowstride(pixbuf);

    for (int i = 0; i < gdk_pixbuf_get_height(pixbuf); ++i) {
      for (int j = 0; j < gdk_pixbuf_get_width(pixbuf); ++j) {
         guchar* pixel = &pixels[i * rowstride + j * 4];
         if (pixel[0] == 0xff && pixel[1] == 0xff && pixel[2] == 0xff) {
           pixel[3] = 0;
         }
      }
    }
  }
}

void NineBox::ContourWidget(GtkWidget* widget) const {
  int x1 = gdk_pixbuf_get_width(images_[0]);
  int x2 = widget->allocation.width - gdk_pixbuf_get_width(images_[2]);

  // Paint the left and right sides.
  GdkBitmap* mask = gdk_pixmap_new(NULL, widget->allocation.width,
                                   widget->allocation.height, 1);
  gdk_pixbuf_render_threshold_alpha(images_[0], mask,
                                    0, 0,
                                    0, 0, -1, -1,
                                    1);
  gdk_pixbuf_render_threshold_alpha(images_[2], mask,
                                    0, 0,
                                    x2, 0, -1, -1,
                                    1);

  // Assume no transparency in the middle rectangle.
  cairo_t* cr = gdk_cairo_create(mask);
  cairo_rectangle(cr, x1, 0, x2 - x1, widget->allocation.height);
  cairo_fill(cr);

  // Mask the widget's window's shape.
  gtk_widget_shape_combine_mask(widget, mask, 0, 0);

  g_object_unref(mask);
  cairo_destroy(cr);
}

void NineBox::TileImage(GtkWidget* dst, GdkPixbuf* src,
                        int x1, int y1, int x2, int y2) const {
  GdkGC* gc = dst->style->fg_gc[GTK_WIDGET_STATE(dst)];
  const int src_width = gdk_pixbuf_get_width(src);
  const int src_height = gdk_pixbuf_get_height(src);
  const int dst_width = dst->allocation.width;
  const int dst_height = dst->allocation.height;

  gfx::Point offset = GetWindowRelativeCoords(dst);

  // We only tile along one axis (see above TODO about nuking all this code),
  // dx or dy will be nonzero along that axis.
  int dx = 0, dy = 0;
  if (x2 > x1)
    dx = src_width;
  if (y2 > y1)
    dy = src_height;
  DCHECK(dx == 0 || dy == 0);

  for (int x = x1, y = y1; x < x2 || y < y2; x += dx, y += dy) {
    gdk_draw_pixbuf(dst->window, gc, src, 0, 0,
                    offset.x() + x, offset.y() + y,
                    dx ? std::min(src_width,  dst_width - x) : src_width,
                    dy ? std::min(src_height, dst_height - y) : src_height,
                    GDK_RGB_DITHER_NONE, 0, 0);
  }
}