// 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.

#include "skia/ext/bitmap_platform_device_mac.h"

#import <ApplicationServices/ApplicationServices.h>
#include <stddef.h>
#include <time.h>

#include "base/mac/mac_util.h"
#include "base/memory/ref_counted.h"
#include "skia/ext/bitmap_platform_device.h"
#include "skia/ext/platform_canvas.h"
#include "skia/ext/skia_utils_mac.h"
#include "third_party/skia/include/core/SkMatrix.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkRegion.h"
#include "third_party/skia/include/core/SkTypes.h"

namespace skia {

namespace {

// Returns true if it is unsafe to attempt to allocate an offscreen buffer
// given these dimensions.
bool RasterDeviceTooBigToAllocate(int width, int height) {

#ifndef SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX
#define SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX    (2 * 256 * 1024 * 1024)
#endif

    int bytesPerPixel = 4;
    int64_t bytes = (int64_t)width * height * bytesPerPixel;
    return bytes > SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX;
}

static CGContextRef CGContextForData(void* data, int width, int height) {
#define HAS_ARGB_SHIFTS(a, r, g, b) \
            (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \
             && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b))
#if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0)
  // Allocate a bitmap context with 4 components per pixel (BGRA).  Apple
  // recommends these flags for improved CG performance.

  // CGBitmapContextCreate returns NULL if width/height are 0. However, our
  // callers expect to get a canvas back (which they later resize/reallocate)
  // so we pin the dimensions here.
  width = SkMax32(1, width);
  height = SkMax32(1, height);
  CGContextRef context =
      CGBitmapContextCreate(data, width, height, 8, width * 4,
                            base::mac::GetSystemColorSpace(),
                            kCGImageAlphaPremultipliedFirst |
                                kCGBitmapByteOrder32Host);
#else
#error We require that Skia's and CoreGraphics's recommended \
       image memory layout match.
#endif
#undef HAS_ARGB_SHIFTS

  if (!context)
    return NULL;

  // Change the coordinate system to match WebCore's
  CGContextTranslateCTM(context, 0, height);
  CGContextScaleCTM(context, 1.0, -1.0);

  return context;
}

}  // namespace

void BitmapPlatformDevice::ReleaseBitmapContext() {
  SkASSERT(bitmap_context_);
  CGContextRelease(bitmap_context_);
  bitmap_context_ = NULL;
}

void BitmapPlatformDevice::SetMatrixClip(
    const SkMatrix& transform,
    const SkRegion& region) {
  transform_ = transform;
  clip_region_ = region;
  config_dirty_ = true;
}

// Loads the specified Skia transform into the device context
static void LoadTransformToCGContext(CGContextRef context,
                                     const SkMatrix& matrix) {
  // CoreGraphics can concatenate transforms, but not reset the current one.
  // So in order to get the required behavior here, we need to first make
  // the current transformation matrix identity and only then load the new one.

  // Reset matrix to identity.
  CGAffineTransform orig_cg_matrix = CGContextGetCTM(context);
  CGAffineTransform orig_cg_matrix_inv =
      CGAffineTransformInvert(orig_cg_matrix);
  CGContextConcatCTM(context, orig_cg_matrix_inv);

  // assert that we have indeed returned to the identity Matrix.
  SkASSERT(CGAffineTransformIsIdentity(CGContextGetCTM(context)));

  // Convert xform to CG-land.
  // Our coordinate system is flipped to match WebKit's so we need to modify
  // the xform to match that.
  SkMatrix transformed_matrix = matrix;
  SkScalar sy = -matrix.getScaleY();
  transformed_matrix.setScaleY(sy);
  size_t height = CGBitmapContextGetHeight(context);
  SkScalar ty = -matrix.getTranslateY();  // y axis is flipped.
  transformed_matrix.setTranslateY(ty + (SkScalar)height);

  CGAffineTransform cg_matrix =
      skia::SkMatrixToCGAffineTransform(transformed_matrix);

  // Load final transform into context.
  CGContextConcatCTM(context, cg_matrix);
}

// Loads a SkRegion into the CG context.
static void LoadClippingRegionToCGContext(CGContextRef context,
                                          const SkRegion& region,
                                          const SkMatrix& transformation) {
  if (region.isEmpty()) {
    // region can be empty, in which case everything will be clipped.
    SkRect rect;
    rect.setEmpty();
    CGContextClipToRect(context, skia::SkRectToCGRect(rect));
  } else if (region.isRect()) {
    // CoreGraphics applies the current transform to clip rects, which is
    // unwanted. Inverse-transform the rect before sending it to CG. This only
    // works for translations and scaling, but not for rotations (but the
    // viewport is never rotated anyway).
    SkMatrix t;
    bool did_invert = transformation.invert(&t);
    if (!did_invert)
      t.reset();
    // Do the transformation.
    SkRect rect;
    rect.set(region.getBounds());
    t.mapRect(&rect);
    SkIRect irect;
    rect.round(&irect);
    CGContextClipToRect(context, skia::SkIRectToCGRect(irect));
  } else {
    // It is complex.
    SkPath path;
    region.getBoundaryPath(&path);
    // Clip. Note that windows clipping regions are not affected by the
    // transform so apply it manually.
    path.transform(transformation);
    // TODO(playmobil): Implement.
    SkASSERT(false);
    // LoadPathToDC(context, path);
    // hrgn = PathToRegion(context);
  }
}

void BitmapPlatformDevice::LoadConfig() {
  if (!config_dirty_ || !bitmap_context_)
    return;  // Nothing to do.
  config_dirty_ = false;

  // We must restore and then save the state of the graphics context since the
  // calls to Load the clipping region to the context are strictly cummulative,
  // i.e., you can't replace a clip rect, other than with a save/restore.
  // But this implies that no other changes to the state are done elsewhere.
  // If we ever get to need to change this, then we must replace the clip rect
  // calls in LoadClippingRegionToCGContext() with an image mask instead.
  CGContextRestoreGState(bitmap_context_);
  CGContextSaveGState(bitmap_context_);
  LoadTransformToCGContext(bitmap_context_, transform_);
  LoadClippingRegionToCGContext(bitmap_context_, clip_region_, transform_);
}


// We use this static factory function instead of the regular constructor so
// that we can create the pixel data before calling the constructor. This is
// required so that we can call the base class' constructor with the pixel
// data.
BitmapPlatformDevice* BitmapPlatformDevice::Create(CGContextRef context,
                                                   int width,
                                                   int height,
                                                   bool is_opaque,
                                                   bool do_clear) {
  if (RasterDeviceTooBigToAllocate(width, height))
    return NULL;

  SkBitmap bitmap;
  // TODO: verify that the CG Context's pixels will have tight rowbytes or pass in the correct
  // rowbytes for the case when context != NULL.
  bitmap.setInfo(SkImageInfo::MakeN32(width, height, is_opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType));

  void* data;
  if (context) {
    data = CGBitmapContextGetData(context);
    bitmap.setPixels(data);
  } else {
    if (!bitmap.tryAllocPixels())
      return NULL;
    data = bitmap.getPixels();
  }
  if (do_clear)
    memset(data, 0, bitmap.getSafeSize());

  // If we were given data, then don't clobber it!
#ifndef NDEBUG
  if (!context && is_opaque) {
    // To aid in finding bugs, we set the background color to something
    // obviously wrong so it will be noticable when it is not cleared
    bitmap.eraseARGB(255, 0, 255, 128);  // bright bluish green
  }
#endif

  if (!context) {
    context = CGContextForData(data, width, height);
    if (!context)
      return NULL;
  } else
    CGContextRetain(context);

  BitmapPlatformDevice* rv = new BitmapPlatformDevice(context, bitmap);

  // The device object took ownership of the graphics context with its own
  // CGContextRetain call.
  CGContextRelease(context);

  return rv;
}

BitmapPlatformDevice* BitmapPlatformDevice::CreateWithData(uint8_t* data,
                                                           int width,
                                                           int height,
                                                           bool is_opaque) {
  CGContextRef context = NULL;
  if (data)
    context = CGContextForData(data, width, height);

  BitmapPlatformDevice* rv = Create(context, width, height, is_opaque, false);

  // The device object took ownership of the graphics context with its own
  // CGContextRetain call.
  if (context)
    CGContextRelease(context);

  return rv;
}

// The device will own the bitmap, which corresponds to also owning the pixel
// data. Therefore, we do not transfer ownership to the SkBitmapDevice's bitmap.
BitmapPlatformDevice::BitmapPlatformDevice(
    CGContextRef context, const SkBitmap& bitmap)
    : SkBitmapDevice(bitmap),
      bitmap_context_(context),
      config_dirty_(true),  // Want to load the config next time.
      transform_(SkMatrix::I()) {
  SetPlatformDevice(this, this);
  SkASSERT(bitmap_context_);
  // Initialize the clip region to the entire bitmap.

  SkIRect rect;
  rect.set(0, 0,
           CGBitmapContextGetWidth(bitmap_context_),
           CGBitmapContextGetHeight(bitmap_context_));
  clip_region_ = SkRegion(rect);
  CGContextRetain(bitmap_context_);
  // We must save the state once so that we can use the restore/save trick
  // in LoadConfig().
  CGContextSaveGState(bitmap_context_);
}

BitmapPlatformDevice::~BitmapPlatformDevice() {
  if (bitmap_context_)
    CGContextRelease(bitmap_context_);
}

CGContextRef BitmapPlatformDevice::GetBitmapContext() {
  LoadConfig();
  return bitmap_context_;
}

void BitmapPlatformDevice::setMatrixClip(const SkMatrix& transform,
                                         const SkRegion& region,
                                         const SkClipStack&) {
  SetMatrixClip(transform, region);
}

SkBaseDevice* BitmapPlatformDevice::onCreateDevice(const CreateInfo& cinfo,
                                                   const SkPaint*) {
  const SkImageInfo& info = cinfo.fInfo;
  const bool do_clear = !info.isOpaque();
  SkASSERT(info.colorType() == kN32_SkColorType);
  return Create(NULL, info.width(), info.height(), info.isOpaque(), do_clear);
}

// PlatformCanvas impl

SkCanvas* CreatePlatformCanvas(CGContextRef ctx, int width, int height,
                               bool is_opaque, OnFailureType failureType) {
  const bool do_clear = false;
  skia::RefPtr<SkBaseDevice> dev = skia::AdoptRef(
      BitmapPlatformDevice::Create(ctx, width, height, is_opaque, do_clear));
  return CreateCanvas(dev, failureType);
}

SkCanvas* CreatePlatformCanvas(int width, int height, bool is_opaque,
                               uint8_t* data, OnFailureType failureType) {
  skia::RefPtr<SkBaseDevice> dev = skia::AdoptRef(
      BitmapPlatformDevice::CreateWithData(data, width, height, is_opaque));
  return CreateCanvas(dev, failureType);
}

}  // namespace skia