// Copyright (c) 2006-2008 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/platform_device.h"
#include "skia/ext/bitmap_platform_device.h"

#import <ApplicationServices/ApplicationServices.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/SkTypes.h"
#include "third_party/skia/include/core/SkUtils.h"

namespace skia {

CGContextRef GetBitmapContext(SkBaseDevice* device) {
  PlatformDevice* platform_device = GetPlatformDevice(device);
  if (platform_device)
    return platform_device->GetBitmapContext();

  return NULL;
}

CGContextRef PlatformDevice::BeginPlatformPaint() {
  return GetBitmapContext();
}

void PlatformDevice::EndPlatformPaint() {
  // Flushing will be done in onAccessBitmap.
}

// Set up the CGContextRef for peaceful coexistence with Skia
void PlatformDevice::InitializeCGContext(CGContextRef context) {
  // CG defaults to the same settings as Skia
}

// static
void PlatformDevice::LoadPathToCGContext(CGContextRef context,
                                         const SkPath& path) {
  // instead of a persistent attribute of the context, CG specifies the fill
  // type per call, so we just have to load up the geometry.
  CGContextBeginPath(context);

  SkPoint points[4] = { {0, 0}, {0, 0}, {0, 0}, {0, 0} };
  SkPath::Iter iter(path, false);
  for (SkPath::Verb verb = iter.next(points); verb != SkPath::kDone_Verb;
       verb = iter.next(points)) {
    switch (verb) {
      case SkPath::kMove_Verb: {  // iter.next returns 1 point
        CGContextMoveToPoint(context, points[0].fX, points[0].fY);
        break;
      }
      case SkPath::kLine_Verb: {  // iter.next returns 2 points
        CGContextAddLineToPoint(context, points[1].fX, points[1].fY);
        break;
      }
      case SkPath::kQuad_Verb: {  // iter.next returns 3 points
        CGContextAddQuadCurveToPoint(context, points[1].fX, points[1].fY,
                                     points[2].fX, points[2].fY);
        break;
      }
      case SkPath::kCubic_Verb: {  // iter.next returns 4 points
        CGContextAddCurveToPoint(context, points[1].fX, points[1].fY,
                                 points[2].fX, points[2].fY,
                                 points[3].fX, points[3].fY);
        break;
      }
      case SkPath::kClose_Verb: {  // iter.next returns 1 point (the last point)
        break;
      }
      case SkPath::kDone_Verb:  // iter.next returns 0 points
      default: {
        SkASSERT(false);
        break;
      }
    }
  }
  CGContextClosePath(context);
}

// static
void PlatformDevice::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() * (SkScalar)-1;
  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 = gfx::SkMatrixToCGAffineTransform(
      transformed_matrix);

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

// static
void PlatformDevice::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, gfx::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, gfx::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);
  }
}

}  // namespace skia