// Copyright (c) 2011 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 "webkit/glue/webcursor.h"

#import <AppKit/AppKit.h>
#include <Carbon/Carbon.h>

#include "app/mac/nsimage_cache.h"
#include "base/logging.h"
#include "base/mac/scoped_cftyperef.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebCursorInfo.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebImage.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebSize.h"

using WebKit::WebCursorInfo;
using WebKit::WebImage;
using WebKit::WebSize;

namespace {

// TODO: This image fetch can (and probably should) be serviced by the resource
// resource bundle instead of going through the image cache.
NSCursor* LoadCursor(const char* name, int x, int y) {
  NSString* file_name = [NSString stringWithUTF8String:name];
  DCHECK(file_name);
  NSImage* cursor_image = app::mac::GetCachedImageWithName(file_name);
  DCHECK(cursor_image);
  return [[[NSCursor alloc] initWithImage:cursor_image
                                  hotSpot:NSMakePoint(x, y)] autorelease];
}

CGImageRef CreateCGImageFromCustomData(const std::vector<char>& custom_data,
                                       const gfx::Size& custom_size) {
  base::mac::ScopedCFTypeRef<CGColorSpaceRef> cg_color(
      CGColorSpaceCreateDeviceRGB());
  // This is safe since we're not going to draw into the context we're creating.
  void* data = const_cast<char*>(&custom_data[0]);
  // The settings here match SetCustomData() below; keep in sync.
  base::mac::ScopedCFTypeRef<CGContextRef> context(
      CGBitmapContextCreate(data,
                            custom_size.width(),
                            custom_size.height(),
                            8,
                            custom_size.width()*4,
                            cg_color.get(),
                            kCGImageAlphaPremultipliedLast |
                            kCGBitmapByteOrder32Big));
  return CGBitmapContextCreateImage(context.get());
}

NSCursor* CreateCustomCursor(const std::vector<char>& custom_data,
                             const gfx::Size& custom_size,
                             const gfx::Point& hotspot) {
  // CG throws a cocoa exception if we try to create an empty image, which
  // results in an infinite loop.  This CHECK ensures that we crash instead.
  CHECK(!custom_data.empty());

  base::mac::ScopedCFTypeRef<CGImageRef> cg_image(
      CreateCGImageFromCustomData(custom_data, custom_size));

  NSBitmapImageRep* ns_bitmap =
      [[NSBitmapImageRep alloc] initWithCGImage:cg_image.get()];
  NSImage* cursor_image = [[NSImage alloc] init];
  DCHECK(cursor_image);
  [cursor_image addRepresentation:ns_bitmap];
  [ns_bitmap release];

  NSCursor* cursor = [[NSCursor alloc] initWithImage:cursor_image
                                             hotSpot:NSMakePoint(hotspot.x(),
                                                                 hotspot.y())];
  [cursor_image release];

  return [cursor autorelease];
}

}  // namespace

// We're matching Safari's cursor choices; see platform/mac/CursorMac.mm
NSCursor* WebCursor::GetCursor() const {
  switch (type_) {
    case WebCursorInfo::TypePointer:
      return [NSCursor arrowCursor];
    case WebCursorInfo::TypeCross:
      return LoadCursor("crossHairCursor", 11, 11);
    case WebCursorInfo::TypeHand:
      return LoadCursor("linkCursor", 6, 1);
    case WebCursorInfo::TypeIBeam:
      return [NSCursor IBeamCursor];
    case WebCursorInfo::TypeWait:
      return LoadCursor("waitCursor", 7, 7);
    case WebCursorInfo::TypeHelp:
      return LoadCursor("helpCursor", 8, 8);
    case WebCursorInfo::TypeEastResize:
    case WebCursorInfo::TypeEastPanning:
      return LoadCursor("eastResizeCursor", 14, 7);
    case WebCursorInfo::TypeNorthResize:
    case WebCursorInfo::TypeNorthPanning:
      return LoadCursor("northResizeCursor", 7, 1);
    case WebCursorInfo::TypeNorthEastResize:
    case WebCursorInfo::TypeNorthEastPanning:
      return LoadCursor("northEastResizeCursor", 14, 1);
    case WebCursorInfo::TypeNorthWestResize:
    case WebCursorInfo::TypeNorthWestPanning:
      return LoadCursor("northWestResizeCursor", 0, 0);
    case WebCursorInfo::TypeSouthResize:
    case WebCursorInfo::TypeSouthPanning:
      return LoadCursor("southResizeCursor", 7, 14);
    case WebCursorInfo::TypeSouthEastResize:
    case WebCursorInfo::TypeSouthEastPanning:
      return LoadCursor("southEastResizeCursor", 14, 14);
    case WebCursorInfo::TypeSouthWestResize:
    case WebCursorInfo::TypeSouthWestPanning:
      return LoadCursor("southWestResizeCursor", 1, 14);
    case WebCursorInfo::TypeWestResize:
    case WebCursorInfo::TypeWestPanning:
      return LoadCursor("westResizeCursor", 1, 7);
    case WebCursorInfo::TypeNorthSouthResize:
      return LoadCursor("northSouthResizeCursor", 7, 7);
    case WebCursorInfo::TypeEastWestResize:
      return LoadCursor("eastWestResizeCursor", 7, 7);
    case WebCursorInfo::TypeNorthEastSouthWestResize:
      return LoadCursor("northEastSouthWestResizeCursor", 7, 7);
    case WebCursorInfo::TypeNorthWestSouthEastResize:
      return LoadCursor("northWestSouthEastResizeCursor", 7, 7);
    case WebCursorInfo::TypeColumnResize:
      return [NSCursor resizeLeftRightCursor];
    case WebCursorInfo::TypeRowResize:
      return [NSCursor resizeUpDownCursor];
    case WebCursorInfo::TypeMiddlePanning:
    case WebCursorInfo::TypeMove:
      return LoadCursor("moveCursor", 7, 7);
    case WebCursorInfo::TypeVerticalText:
      return LoadCursor("verticalTextCursor", 7, 7);
    case WebCursorInfo::TypeCell:
      return LoadCursor("cellCursor", 7, 7);
    case WebCursorInfo::TypeContextMenu:
      return LoadCursor("contextMenuCursor", 3, 2);
    case WebCursorInfo::TypeAlias:
      return LoadCursor("aliasCursor", 11, 3);
    case WebCursorInfo::TypeProgress:
      return LoadCursor("progressCursor", 3, 2);
    case WebCursorInfo::TypeNoDrop:
      return LoadCursor("noDropCursor", 3, 1);
    case WebCursorInfo::TypeCopy:
      return LoadCursor("copyCursor", 3, 2);
    case WebCursorInfo::TypeNone:
      return LoadCursor("noneCursor", 7, 7);
    case WebCursorInfo::TypeNotAllowed:
      return LoadCursor("notAllowedCursor", 11, 11);
    case WebCursorInfo::TypeZoomIn:
      return LoadCursor("zoomInCursor", 7, 7);
    case WebCursorInfo::TypeZoomOut:
      return LoadCursor("zoomOutCursor", 7, 7);
    case WebCursorInfo::TypeGrab:
      return [NSCursor openHandCursor];
    case WebCursorInfo::TypeGrabbing:
      return [NSCursor closedHandCursor];
    case WebCursorInfo::TypeCustom:
      return CreateCustomCursor(custom_data_, custom_size_, hotspot_);
  }
  NOTREACHED();
  return nil;
}

gfx::NativeCursor WebCursor::GetNativeCursor() {
  return GetCursor();
}

void WebCursor::InitFromThemeCursor(ThemeCursor cursor) {
  WebKit::WebCursorInfo cursor_info;

  switch (cursor) {
    case kThemeArrowCursor:
      cursor_info.type = WebCursorInfo::TypePointer;
      break;
    case kThemeCopyArrowCursor:
      cursor_info.type = WebCursorInfo::TypeCopy;
      break;
    case kThemeAliasArrowCursor:
      cursor_info.type = WebCursorInfo::TypeAlias;
      break;
    case kThemeContextualMenuArrowCursor:
      cursor_info.type = WebCursorInfo::TypeContextMenu;
      break;
    case kThemeIBeamCursor:
      cursor_info.type = WebCursorInfo::TypeIBeam;
      break;
    case kThemeCrossCursor:
    case kThemePlusCursor:
      cursor_info.type = WebCursorInfo::TypeCross;
      break;
    case kThemeWatchCursor:
    case kThemeSpinningCursor:
      cursor_info.type = WebCursorInfo::TypeWait;
      break;
    case kThemeClosedHandCursor:
      cursor_info.type = WebCursorInfo::TypeGrabbing;
      break;
    case kThemeOpenHandCursor:
      cursor_info.type = WebCursorInfo::TypeGrab;
      break;
    case kThemePointingHandCursor:
    case kThemeCountingUpHandCursor:
    case kThemeCountingDownHandCursor:
    case kThemeCountingUpAndDownHandCursor:
      cursor_info.type = WebCursorInfo::TypeHand;
      break;
    case kThemeResizeLeftCursor:
      cursor_info.type = WebCursorInfo::TypeWestResize;
      break;
    case kThemeResizeRightCursor:
      cursor_info.type = WebCursorInfo::TypeEastResize;
      break;
    case kThemeResizeLeftRightCursor:
      cursor_info.type = WebCursorInfo::TypeEastWestResize;
      break;
    case kThemeNotAllowedCursor:
      cursor_info.type = WebCursorInfo::TypeNotAllowed;
      break;
    case kThemeResizeUpCursor:
      cursor_info.type = WebCursorInfo::TypeNorthResize;
      break;
    case kThemeResizeDownCursor:
      cursor_info.type = WebCursorInfo::TypeSouthResize;
      break;
    case kThemeResizeUpDownCursor:
      cursor_info.type = WebCursorInfo::TypeNorthSouthResize;
      break;
    case kThemePoofCursor:  // *shrug*
    default:
      cursor_info.type = WebCursorInfo::TypePointer;
      break;
  }

  InitFromCursorInfo(cursor_info);
}

void WebCursor::InitFromCursor(const Cursor* cursor) {
  // This conversion isn't perfect (in particular, the inversion effect of
  // data==1, mask==0 won't work). Not planning on fixing it.

  gfx::Size custom_size(16, 16);
  std::vector<char> raw_data;
  for (int row = 0; row < 16; ++row) {
    unsigned short data = cursor->data[row];
    unsigned short mask = cursor->mask[row];

    // The Core Endian flipper callback for 'CURS' doesn't flip Bits16 as if it
    // were a short (which it is), so we flip it here.
    data = ((data << 8) & 0xFF00) | ((data >> 8) & 0x00FF);
    mask = ((mask << 8) & 0xFF00) | ((mask >> 8) & 0x00FF);

    for (int bit = 0; bit < 16; ++bit) {
      if (data & 0x8000) {
        raw_data.push_back(0x00);
        raw_data.push_back(0x00);
        raw_data.push_back(0x00);
      } else {
        raw_data.push_back(0xFF);
        raw_data.push_back(0xFF);
        raw_data.push_back(0xFF);
      }
      if (mask & 0x8000)
        raw_data.push_back(0xFF);
      else
        raw_data.push_back(0x00);
      data <<= 1;
      mask <<= 1;
    }
  }

  base::mac::ScopedCFTypeRef<CGImageRef> cg_image(
      CreateCGImageFromCustomData(raw_data, custom_size));

  WebKit::WebCursorInfo cursor_info;
  cursor_info.type = WebCursorInfo::TypeCustom;
  cursor_info.hotSpot = WebKit::WebPoint(cursor->hotSpot.h, cursor->hotSpot.v);
  cursor_info.customImage = cg_image.get();

  InitFromCursorInfo(cursor_info);
}

void WebCursor::InitFromNSCursor(NSCursor* cursor) {
  WebKit::WebCursorInfo cursor_info;

  if ([cursor isEqual:[NSCursor arrowCursor]]) {
    cursor_info.type = WebCursorInfo::TypePointer;
  } else if ([cursor isEqual:[NSCursor IBeamCursor]]) {
    cursor_info.type = WebCursorInfo::TypeIBeam;
  } else if ([cursor isEqual:[NSCursor crosshairCursor]]) {
    cursor_info.type = WebCursorInfo::TypeCross;
  } else if ([cursor isEqual:[NSCursor pointingHandCursor]]) {
    cursor_info.type = WebCursorInfo::TypeHand;
  } else if ([cursor isEqual:[NSCursor resizeLeftCursor]]) {
    cursor_info.type = WebCursorInfo::TypeWestResize;
  } else if ([cursor isEqual:[NSCursor resizeRightCursor]]) {
    cursor_info.type = WebCursorInfo::TypeEastResize;
  } else if ([cursor isEqual:[NSCursor resizeLeftRightCursor]]) {
    cursor_info.type = WebCursorInfo::TypeEastWestResize;
  } else if ([cursor isEqual:[NSCursor resizeUpCursor]]) {
    cursor_info.type = WebCursorInfo::TypeNorthResize;
  } else if ([cursor isEqual:[NSCursor resizeDownCursor]]) {
    cursor_info.type = WebCursorInfo::TypeSouthResize;
  } else if ([cursor isEqual:[NSCursor resizeUpDownCursor]]) {
    cursor_info.type = WebCursorInfo::TypeNorthSouthResize;
  } else if ([cursor isEqual:[NSCursor openHandCursor]]) {
    cursor_info.type = WebCursorInfo::TypeGrab;
  } else if ([cursor isEqual:[NSCursor closedHandCursor]]) {
    cursor_info.type = WebCursorInfo::TypeGrabbing;
  } else {
    // Also handles the [NSCursor disappearingItemCursor] case. Quick-and-dirty
    // image conversion; TODO(avi): do better.
    CGImageRef cg_image = nil;
    NSImage* image = [cursor image];
    for (id rep in [image representations]) {
      if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
        cg_image = [rep CGImage];
        break;
      }
    }

    if (cg_image) {
      cursor_info.type = WebCursorInfo::TypeCustom;
      NSPoint hot_spot = [cursor hotSpot];
      cursor_info.hotSpot = WebKit::WebPoint(hot_spot.x, hot_spot.y);
      cursor_info.customImage = cg_image;
    } else {
      cursor_info.type = WebCursorInfo::TypePointer;
    }
  }

  InitFromCursorInfo(cursor_info);
}

void WebCursor::SetCustomData(const WebImage& image) {
  if (image.isNull())
    return;

  base::mac::ScopedCFTypeRef<CGColorSpaceRef> cg_color(
      CGColorSpaceCreateDeviceRGB());

  const WebSize& image_dimensions = image.size();
  int image_width = image_dimensions.width;
  int image_height = image_dimensions.height;

  size_t size = image_height * image_width * 4;
  custom_data_.resize(size);
  custom_size_.set_width(image_width);
  custom_size_.set_height(image_height);

  // These settings match up with the code in CreateCustomCursor() above; keep
  // them in sync.
  // TODO(avi): test to ensure that the flags here are correct for RGBA
  base::mac::ScopedCFTypeRef<CGContextRef> context(
      CGBitmapContextCreate(&custom_data_[0],
                            image_width,
                            image_height,
                            8,
                            image_width * 4,
                            cg_color.get(),
                            kCGImageAlphaPremultipliedLast |
                            kCGBitmapByteOrder32Big));
  CGRect rect = CGRectMake(0, 0, image_width, image_height);
  CGContextDrawImage(context.get(), rect, image.getCGImageRef());
}

void WebCursor::ImageFromCustomData(WebImage* image) const {
  if (custom_data_.empty())
    return;

  base::mac::ScopedCFTypeRef<CGImageRef> cg_image(
      CreateCGImageFromCustomData(custom_data_, custom_size_));
  *image = cg_image.get();
}

void WebCursor::InitPlatformData() {
  return;
}

bool WebCursor::SerializePlatformData(Pickle* pickle) const {
  return true;
}

bool WebCursor::DeserializePlatformData(const Pickle* pickle, void** iter) {
  return true;
}

bool WebCursor::IsPlatformDataEqual(const WebCursor& other) const {
  return true;
}

void WebCursor::CleanupPlatformData() {
  return;
}

void WebCursor::CopyPlatformData(const WebCursor& other) {
  return;
}