/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "base/basictypes.h"
#include "nsCocoaUtils.h"
#include "PluginModuleChild.h"
#include "nsDebug.h"
#include "PluginInterposeOSX.h"
#include <set>
#import <AppKit/AppKit.h>
#import <objc/runtime.h>
#import <Carbon/Carbon.h>

using namespace mozilla::plugins;

namespace mac_plugin_interposing {

int32_t NSCursorInfo::mNativeCursorsSupported = -1;

// This constructor may be called from the browser process or the plugin
// process.
NSCursorInfo::NSCursorInfo()
  : mType(TypeArrow)
  , mHotSpot(nsPoint(0, 0))
  , mCustomImageData(NULL)
  , mCustomImageDataLength(0)
{
}

NSCursorInfo::NSCursorInfo(NSCursor* aCursor)
  : mType(TypeArrow)
  , mHotSpot(nsPoint(0, 0))
  , mCustomImageData(NULL)
  , mCustomImageDataLength(0)
{
  // This constructor is only ever called from the plugin process, so the
  // following is safe.
  if (!GetNativeCursorsSupported()) {
    return;
  }

  NSPoint hotSpotCocoa = [aCursor hotSpot];
  mHotSpot = nsPoint(hotSpotCocoa.x, hotSpotCocoa.y);

  Class nsCursorClass = [NSCursor class];
  if ([aCursor isEqual:[NSCursor arrowCursor]]) {
    mType = TypeArrow;
  } else if ([aCursor isEqual:[NSCursor closedHandCursor]]) {
    mType = TypeClosedHand;
  } else if ([aCursor isEqual:[NSCursor crosshairCursor]]) {
    mType = TypeCrosshair;
  } else if ([aCursor isEqual:[NSCursor disappearingItemCursor]]) {
    mType = TypeDisappearingItem;
  } else if ([aCursor isEqual:[NSCursor IBeamCursor]]) {
    mType = TypeIBeam;
  } else if ([aCursor isEqual:[NSCursor openHandCursor]]) {
    mType = TypeOpenHand;
  } else if ([aCursor isEqual:[NSCursor pointingHandCursor]]) {
    mType = TypePointingHand;
  } else if ([aCursor isEqual:[NSCursor resizeDownCursor]]) {
    mType = TypeResizeDown;
  } else if ([aCursor isEqual:[NSCursor resizeLeftCursor]]) {
    mType = TypeResizeLeft;
  } else if ([aCursor isEqual:[NSCursor resizeLeftRightCursor]]) {
    mType = TypeResizeLeftRight;
  } else if ([aCursor isEqual:[NSCursor resizeRightCursor]]) {
    mType = TypeResizeRight;
  } else if ([aCursor isEqual:[NSCursor resizeUpCursor]]) {
    mType = TypeResizeUp;
  } else if ([aCursor isEqual:[NSCursor resizeUpDownCursor]]) {
    mType = TypeResizeUpDown;
  // The following cursor types are only supported on OS X 10.6 and up.
  } else if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)] &&
             [aCursor isEqual:[nsCursorClass performSelector:@selector(contextualMenuCursor)]]) {
    mType = TypeContextualMenu;
  } else if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)] &&
             [aCursor isEqual:[nsCursorClass performSelector:@selector(dragCopyCursor)]]) {
    mType = TypeDragCopy;
  } else if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)] &&
             [aCursor isEqual:[nsCursorClass performSelector:@selector(dragLinkCursor)]]) {
    mType = TypeDragLink;
  } else if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)] &&
             [aCursor isEqual:[nsCursorClass performSelector:@selector(operationNotAllowedCursor)]]) {
    mType = TypeNotAllowed;
  } else {
    NSImage* image = [aCursor image];
    NSArray* reps = image ? [image representations] : nil;
    NSUInteger repsCount = reps ? [reps count] : 0;
    if (!repsCount) {
      // If we have a custom cursor with no image representations, assume we
      // need a transparent cursor.
      mType = TypeTransparent;
    } else {
      CGImageRef cgImage = nil;
      // XXX We don't know how to deal with a cursor that doesn't have a
      //     bitmap image representation.  For now we fall back to an arrow
      //     cursor.
      for (NSUInteger i = 0; i < repsCount; ++i) {
        id rep = [reps objectAtIndex:i];
        if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
          cgImage = [(NSBitmapImageRep*)rep CGImage];
          break;
        }
      }
      if (cgImage) {
        CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0);
        if (data) {
          CGImageDestinationRef dest = ::CGImageDestinationCreateWithData(data,
                                                                          kUTTypePNG,
                                                                          1,
                                                                          NULL);
          if (dest) {
            ::CGImageDestinationAddImage(dest, cgImage, NULL);
            if (::CGImageDestinationFinalize(dest)) {
              uint32_t dataLength = (uint32_t) ::CFDataGetLength(data);
              mCustomImageData = (uint8_t*) moz_xmalloc(dataLength);
              ::CFDataGetBytes(data, ::CFRangeMake(0, dataLength), mCustomImageData);
              mCustomImageDataLength = dataLength;
              mType = TypeCustom;
            }
            ::CFRelease(dest);
          }
          ::CFRelease(data);
        }
      }
      if (!mCustomImageData) {
        mType = TypeArrow;
      }
    }
  }
}

NSCursorInfo::NSCursorInfo(const Cursor* aCursor)
  : mType(TypeArrow)
  , mHotSpot(nsPoint(0, 0))
  , mCustomImageData(NULL)
  , mCustomImageDataLength(0)
{
  // This constructor is only ever called from the plugin process, so the
  // following is safe.
  if (!GetNativeCursorsSupported()) {
    return;
  }

  mHotSpot = nsPoint(aCursor->hotSpot.h, aCursor->hotSpot.v);

  int width = 16, height = 16;
  int bytesPerPixel = 4;
  int rowBytes = width * bytesPerPixel;
  int bitmapSize = height * rowBytes;

  bool isTransparent = true;

  uint8_t* bitmap = (uint8_t*) moz_xmalloc(bitmapSize);
  // The way we create 'bitmap' is largely "borrowed" from Chrome's
  // WebCursor::InitFromCursor().
  for (int y = 0; y < height; ++y) {
    unsigned short data = aCursor->data[y];
    unsigned short mask = aCursor->mask[y];
    // Change 'data' and 'mask' from big-endian to little-endian, but output
    // big-endian data below.
    data = ((data << 8) & 0xFF00) | ((data >> 8) & 0x00FF);
    mask = ((mask << 8) & 0xFF00) | ((mask >> 8) & 0x00FF);
    // It'd be nice to use a gray-scale bitmap.  But
    // CGBitmapContextCreateImage() (used below) won't work with one that also
    // has alpha values.
    for (int x = 0; x < width; ++x) {
      int offset = (y * rowBytes) + (x * bytesPerPixel);
      // Color value
      if (data & 0x8000) {
        bitmap[offset]     = 0x0;
        bitmap[offset + 1] = 0x0;
        bitmap[offset + 2] = 0x0;
      } else {
        bitmap[offset]     = 0xFF;
        bitmap[offset + 1] = 0xFF;
        bitmap[offset + 2] = 0xFF;
      }
      // Mask value
      if (mask & 0x8000) {
        bitmap[offset + 3] = 0xFF;
        isTransparent = false;
      } else {
        bitmap[offset + 3] = 0x0;
      }
      data <<= 1;
      mask <<= 1;
    }
  }

  if (isTransparent) {
    // If aCursor is transparent, we don't need to serialize custom cursor
    // data over IPC.
    mType = TypeTransparent;
  } else {
    CGColorSpaceRef color = ::CGColorSpaceCreateDeviceRGB();
    if (color) {
      CGContextRef context =
        ::CGBitmapContextCreate(bitmap,
                                width,
                                height,
                                8,
                                rowBytes,
                                color,
                                kCGImageAlphaPremultipliedLast |
                                  kCGBitmapByteOrder32Big);
      if (context) {
        CGImageRef image = ::CGBitmapContextCreateImage(context);
        if (image) {
          ::CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0);
          if (data) {
            CGImageDestinationRef dest =
              ::CGImageDestinationCreateWithData(data,
                                                 kUTTypePNG,
                                                 1,
                                                 NULL);
            if (dest) {
              ::CGImageDestinationAddImage(dest, image, NULL);
              if (::CGImageDestinationFinalize(dest)) {
                uint32_t dataLength = (uint32_t) ::CFDataGetLength(data);
                mCustomImageData = (uint8_t*) moz_xmalloc(dataLength);
                ::CFDataGetBytes(data,
                                 ::CFRangeMake(0, dataLength),
                                 mCustomImageData);
                mCustomImageDataLength = dataLength;
                mType = TypeCustom;
              }
              ::CFRelease(dest);
            }
            ::CFRelease(data);
          }
          ::CGImageRelease(image);
        }
        ::CGContextRelease(context);
      }
      ::CGColorSpaceRelease(color);
    }
  }

  free(bitmap);
}

NSCursorInfo::~NSCursorInfo()
{
  if (mCustomImageData) {
    free(mCustomImageData);
  }
}

NSCursor* NSCursorInfo::GetNSCursor() const
{
  NSCursor* retval = nil;

  Class nsCursorClass = [NSCursor class];
  switch(mType) {
    case TypeArrow:
      retval = [NSCursor arrowCursor];
      break;
    case TypeClosedHand:
      retval = [NSCursor closedHandCursor];
      break;
    case TypeCrosshair:
      retval = [NSCursor crosshairCursor];
      break;
    case TypeDisappearingItem:
      retval = [NSCursor disappearingItemCursor];
      break;
    case TypeIBeam:
      retval = [NSCursor IBeamCursor];
      break;
    case TypeOpenHand:
      retval = [NSCursor openHandCursor];
      break;
    case TypePointingHand:
      retval = [NSCursor pointingHandCursor];
      break;
    case TypeResizeDown:
      retval = [NSCursor resizeDownCursor];
      break;
    case TypeResizeLeft:
      retval = [NSCursor resizeLeftCursor];
      break;
    case TypeResizeLeftRight:
      retval = [NSCursor resizeLeftRightCursor];
      break;
    case TypeResizeRight:
      retval = [NSCursor resizeRightCursor];
      break;
    case TypeResizeUp:
      retval = [NSCursor resizeUpCursor];
      break;
    case TypeResizeUpDown:
      retval = [NSCursor resizeUpDownCursor];
      break;
    // The following four cursor types are only supported on OS X 10.6 and up.
    case TypeContextualMenu: {
      if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)]) {
        retval = [nsCursorClass performSelector:@selector(contextualMenuCursor)];
      }
      break;
    }
    case TypeDragCopy: {
      if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)]) {
        retval = [nsCursorClass performSelector:@selector(dragCopyCursor)];
      }
      break;
    }
    case TypeDragLink: {
      if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)]) {
        retval = [nsCursorClass performSelector:@selector(dragLinkCursor)];
      }
      break;
    }
    case TypeNotAllowed: {
      if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)]) {
        retval = [nsCursorClass performSelector:@selector(operationNotAllowedCursor)];
      }
      break;
    }
    case TypeTransparent:
      retval = GetTransparentCursor();
      break;
    default:
      break;
  }

  if (!retval && mCustomImageData && mCustomImageDataLength) {
    CGDataProviderRef provider = ::CGDataProviderCreateWithData(NULL,
                                                                (const void*)mCustomImageData,
                                                                mCustomImageDataLength,
                                                                NULL);
    if (provider) {
      CGImageRef cgImage = ::CGImageCreateWithPNGDataProvider(provider,
                                                              NULL,
                                                              false,
                                                              kCGRenderingIntentDefault);
      if (cgImage) {
        NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
        if (rep) {
          NSImage* image = [[NSImage alloc] init];
          if (image) {
            [image addRepresentation:rep];
            retval = [[[NSCursor alloc] initWithImage:image 
                                              hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)]
                      autorelease];
            [image release];
          }
          [rep release];
        }
        ::CGImageRelease(cgImage);
      }
      ::CFRelease(provider);
    }
  }

  // Fall back to an arrow cursor if need be.
  if (!retval) {
    retval = [NSCursor arrowCursor];
  }

  return retval;
}

// Get a transparent cursor with the appropriate hot spot.  We need one if
// (for example) we have a custom cursor with no image data.
NSCursor* NSCursorInfo::GetTransparentCursor() const
{
  NSCursor* retval = nil;

  int width = 16, height = 16;
  int bytesPerPixel = 2;
  int rowBytes = width * bytesPerPixel;
  int dataSize = height * rowBytes;

  uint8_t* data = (uint8_t*) moz_xmalloc(dataSize);
  for (int y = 0; y < height; ++y) {
    for (int x = 0; x < width; ++x) {
      int offset = (y * rowBytes) + (x * bytesPerPixel);
      data[offset] = 0x7E;  // Arbitrary gray-scale value
      data[offset + 1] = 0; // Alpha value to make us transparent
    }
  }

  NSBitmapImageRep* imageRep =
    [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil
                                             pixelsWide:width
                                             pixelsHigh:height
                                          bitsPerSample:8
                                        samplesPerPixel:2
                                               hasAlpha:YES
                                               isPlanar:NO
                                         colorSpaceName:NSCalibratedWhiteColorSpace
                                            bytesPerRow:rowBytes
                                           bitsPerPixel:16]
     autorelease];
  if (imageRep) {
    uint8_t* repDataPtr = [imageRep bitmapData];
    if (repDataPtr) {
      memcpy(repDataPtr, data, dataSize);
      NSImage *image =
        [[[NSImage alloc] initWithSize:NSMakeSize(width, height)]
         autorelease];
      if (image) {
        [image addRepresentation:imageRep];
        retval =
          [[[NSCursor alloc] initWithImage:image
                                   hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)]
           autorelease];
      }
    }
  }

  free(data);

  // Fall back to an arrow cursor if (for some reason) the above code failed.
  if (!retval) {
    retval = [NSCursor arrowCursor];
  }

  return retval;
}

NSCursorInfo::Type NSCursorInfo::GetType() const
{
  return mType;
}

const char* NSCursorInfo::GetTypeName() const
{
  switch(mType) {
    case TypeCustom:
      return "TypeCustom";
    case TypeArrow:
      return "TypeArrow";
    case TypeClosedHand:
      return "TypeClosedHand";
    case TypeContextualMenu:
      return "TypeContextualMenu";
    case TypeCrosshair:
      return "TypeCrosshair";
    case TypeDisappearingItem:
      return "TypeDisappearingItem";
    case TypeDragCopy:
      return "TypeDragCopy";
    case TypeDragLink:
      return "TypeDragLink";
    case TypeIBeam:
      return "TypeIBeam";
    case TypeNotAllowed:
      return "TypeNotAllowed";
    case TypeOpenHand:
      return "TypeOpenHand";
    case TypePointingHand:
      return "TypePointingHand";
    case TypeResizeDown:
      return "TypeResizeDown";
    case TypeResizeLeft:
      return "TypeResizeLeft";
    case TypeResizeLeftRight:
      return "TypeResizeLeftRight";
    case TypeResizeRight:
      return "TypeResizeRight";
    case TypeResizeUp:
      return "TypeResizeUp";
    case TypeResizeUpDown:
      return "TypeResizeUpDown";
    case TypeTransparent:
      return "TypeTransparent";
    default:
      break;
  }
  return "TypeUnknown";
}

nsPoint NSCursorInfo::GetHotSpot() const
{
  return mHotSpot;
}

uint8_t* NSCursorInfo::GetCustomImageData() const
{
  return mCustomImageData;
}

uint32_t NSCursorInfo::GetCustomImageDataLength() const
{
  return mCustomImageDataLength;
}

void NSCursorInfo::SetType(Type aType)
{
  mType = aType;
}

void NSCursorInfo::SetHotSpot(nsPoint aHotSpot)
{
  mHotSpot = aHotSpot;
}

void NSCursorInfo::SetCustomImageData(uint8_t* aData, uint32_t aDataLength)
{
  if (mCustomImageData) {
    free(mCustomImageData);
  }
  if (aDataLength) {
    mCustomImageData = (uint8_t*) moz_xmalloc(aDataLength);
    memcpy(mCustomImageData, aData, aDataLength);
  } else {
    mCustomImageData = NULL;
  }
  mCustomImageDataLength = aDataLength;
}

// This should never be called from the browser process -- only from the
// plugin process.
bool NSCursorInfo::GetNativeCursorsSupported()
{
  if (mNativeCursorsSupported == -1) {
    ENSURE_PLUGIN_THREAD(false);
    PluginModuleChild *pmc = PluginModuleChild::GetChrome();
    if (pmc) {
      bool result = pmc->GetNativeCursorsSupported();
      if (result) {
        mNativeCursorsSupported = 1;
      } else {
        mNativeCursorsSupported = 0;
      }
    }
  }
  return (mNativeCursorsSupported == 1);
}

} // namespace mac_plugin_interposing

namespace mac_plugin_interposing {
namespace parent {

// Tracks plugin windows currently visible.
std::set<uint32_t> plugin_visible_windows_set_;
// Tracks full screen windows currently visible.
std::set<uint32_t> plugin_fullscreen_windows_set_;
// Tracks modal windows currently visible.
std::set<uint32_t> plugin_modal_windows_set_;

void OnPluginShowWindow(uint32_t window_id,
                        CGRect window_bounds,
                        bool modal) {
  plugin_visible_windows_set_.insert(window_id);

  if (modal)
    plugin_modal_windows_set_.insert(window_id);

  CGRect main_display_bounds = ::CGDisplayBounds(CGMainDisplayID());

  if (CGRectEqualToRect(window_bounds, main_display_bounds) &&
      (plugin_fullscreen_windows_set_.find(window_id) ==
       plugin_fullscreen_windows_set_.end())) {
    plugin_fullscreen_windows_set_.insert(window_id);

    nsCocoaUtils::HideOSChromeOnScreen(true, nil);
  }
}

static void ActivateProcess(pid_t pid) {
  ProcessSerialNumber process;
  OSStatus status = ::GetProcessForPID(pid, &process);

  if (status == noErr) {
    SetFrontProcess(&process);
  } else {
    NS_WARNING("Unable to get process for pid.");
  }
}

// Must be called on the UI thread.
// If plugin_pid is -1, the browser will be the active process on return,
// otherwise that process will be given focus back before this function returns.
static void ReleasePluginFullScreen(pid_t plugin_pid) {
  // Releasing full screen only works if we are the frontmost process; grab
  // focus, but give it back to the plugin process if requested.
  ActivateProcess(base::GetCurrentProcId());

  nsCocoaUtils::HideOSChromeOnScreen(false, nil);

  if (plugin_pid != -1) {
    ActivateProcess(plugin_pid);
  }
}

void OnPluginHideWindow(uint32_t window_id, pid_t aPluginPid) {
  bool had_windows = !plugin_visible_windows_set_.empty();
  plugin_visible_windows_set_.erase(window_id);
  bool browser_needs_activation = had_windows &&
      plugin_visible_windows_set_.empty();

  plugin_modal_windows_set_.erase(window_id);
  if (plugin_fullscreen_windows_set_.find(window_id) !=
      plugin_fullscreen_windows_set_.end()) {
    plugin_fullscreen_windows_set_.erase(window_id);
    pid_t plugin_pid = browser_needs_activation ? -1 : aPluginPid;
    browser_needs_activation = false;
    ReleasePluginFullScreen(plugin_pid);
  }

  if (browser_needs_activation) {
    ActivateProcess(getpid());
  }
}

void OnSetCursor(const NSCursorInfo& cursorInfo)
{
  NSCursor* aCursor = cursorInfo.GetNSCursor();
  if (aCursor) {
    [aCursor set];
  }
}

void OnShowCursor(bool show)
{
  if (show) {
    [NSCursor unhide];
  } else {
    [NSCursor hide];
  }
}

void OnPushCursor(const NSCursorInfo& cursorInfo)
{
  NSCursor* aCursor = cursorInfo.GetNSCursor();
  if (aCursor) {
    [aCursor push];
  }
}

void OnPopCursor()
{
  [NSCursor pop];
}

} // namespace parent
} // namespace mac_plugin_interposing

namespace mac_plugin_interposing {
namespace child {

// TODO(stuartmorgan): Make this an IPC to order the plugin process above the
// browser process only if the browser is current frontmost.
void FocusPluginProcess() {
  ProcessSerialNumber this_process, front_process;
  if ((GetCurrentProcess(&this_process) != noErr) ||
      (GetFrontProcess(&front_process) != noErr)) {
    return;
  }

  Boolean matched = false;
  if ((SameProcess(&this_process, &front_process, &matched) == noErr) &&
      !matched) {
    SetFrontProcess(&this_process);
  }
}

void NotifyBrowserOfPluginShowWindow(uint32_t window_id, CGRect bounds,
                                     bool modal) {
  ENSURE_PLUGIN_THREAD_VOID();

  PluginModuleChild *pmc = PluginModuleChild::GetChrome();
  if (pmc)
    pmc->PluginShowWindow(window_id, modal, bounds);
}

void NotifyBrowserOfPluginHideWindow(uint32_t window_id, CGRect bounds) {
  ENSURE_PLUGIN_THREAD_VOID();

  PluginModuleChild *pmc = PluginModuleChild::GetChrome();
  if (pmc)
    pmc->PluginHideWindow(window_id);
}

void NotifyBrowserOfSetCursor(NSCursorInfo& aCursorInfo)
{
  ENSURE_PLUGIN_THREAD_VOID();
  PluginModuleChild *pmc = PluginModuleChild::GetChrome();
  if (pmc) {
    pmc->SetCursor(aCursorInfo);
  }
}

void NotifyBrowserOfShowCursor(bool show)
{
  ENSURE_PLUGIN_THREAD_VOID();
  PluginModuleChild *pmc = PluginModuleChild::GetChrome();
  if (pmc) {
    pmc->ShowCursor(show);
  }
}

void NotifyBrowserOfPushCursor(NSCursorInfo& aCursorInfo)
{
  ENSURE_PLUGIN_THREAD_VOID();
  PluginModuleChild *pmc = PluginModuleChild::GetChrome();
  if (pmc) {
    pmc->PushCursor(aCursorInfo);
  }
}

void NotifyBrowserOfPopCursor()
{
  ENSURE_PLUGIN_THREAD_VOID();
  PluginModuleChild *pmc = PluginModuleChild::GetChrome();
  if (pmc) {
    pmc->PopCursor();
  }
}

struct WindowInfo {
  uint32_t window_id;
  CGRect bounds;
  explicit WindowInfo(NSWindow* aWindow) {
    NSInteger window_num = [aWindow windowNumber];
    window_id = window_num > 0 ? window_num : 0;
    bounds = NSRectToCGRect([aWindow frame]);
  }
};

static void OnPluginWindowClosed(const WindowInfo& window_info) {
  if (window_info.window_id == 0)
    return;
  mac_plugin_interposing::child::NotifyBrowserOfPluginHideWindow(window_info.window_id,
                                                                 window_info.bounds);
}

static void OnPluginWindowShown(const WindowInfo& window_info, BOOL is_modal) {
  // The window id is 0 if it has never been shown (including while it is the
  // process of being shown for the first time); when that happens, we'll catch
  // it in _setWindowNumber instead.
  static BOOL s_pending_display_is_modal = NO;
  if (window_info.window_id == 0) {
    if (is_modal)
      s_pending_display_is_modal = YES;
    return;
  }
  if (s_pending_display_is_modal) {
    is_modal = YES;
    s_pending_display_is_modal = NO;
  }
  mac_plugin_interposing::child::NotifyBrowserOfPluginShowWindow(
    window_info.window_id, window_info.bounds, is_modal);
}

static BOOL OnSetCursor(NSCursorInfo &aInfo)
{
  if (NSCursorInfo::GetNativeCursorsSupported()) {
    NotifyBrowserOfSetCursor(aInfo);
    return YES;
  }
  return NO;
}

static BOOL OnHideCursor()
{
  if (NSCursorInfo::GetNativeCursorsSupported()) {
    NotifyBrowserOfShowCursor(NO);
    return YES;
  }
  return NO;
}

static BOOL OnUnhideCursor()
{
  if (NSCursorInfo::GetNativeCursorsSupported()) {
    NotifyBrowserOfShowCursor(YES);
    return YES;
  }
  return NO;
}

static BOOL OnPushCursor(NSCursorInfo &aInfo)
{
  if (NSCursorInfo::GetNativeCursorsSupported()) {
    NotifyBrowserOfPushCursor(aInfo);
    return YES;
  }
  return NO;
}

static BOOL OnPopCursor()
{
  if (NSCursorInfo::GetNativeCursorsSupported()) {
    NotifyBrowserOfPopCursor();
    return YES;
  }
  return NO;
}

} // namespace child
} // namespace mac_plugin_interposing

using namespace mac_plugin_interposing::child;

@interface NSWindow (PluginInterposing)
- (void)pluginInterpose_orderOut:(id)sender;
- (void)pluginInterpose_orderFront:(id)sender;
- (void)pluginInterpose_makeKeyAndOrderFront:(id)sender;
- (void)pluginInterpose_setWindowNumber:(NSInteger)num;
@end

@implementation NSWindow (PluginInterposing)

- (void)pluginInterpose_orderOut:(id)sender {
  WindowInfo window_info(self);
  [self pluginInterpose_orderOut:sender];
  OnPluginWindowClosed(window_info);
}

- (void)pluginInterpose_orderFront:(id)sender {
  mac_plugin_interposing::child::FocusPluginProcess();
  [self pluginInterpose_orderFront:sender];
  OnPluginWindowShown(WindowInfo(self), NO);
}

- (void)pluginInterpose_makeKeyAndOrderFront:(id)sender {
  mac_plugin_interposing::child::FocusPluginProcess();
  [self pluginInterpose_makeKeyAndOrderFront:sender];
  OnPluginWindowShown(WindowInfo(self), NO);
}

- (void)pluginInterpose_setWindowNumber:(NSInteger)num {
  if (num > 0)
    mac_plugin_interposing::child::FocusPluginProcess();
  [self pluginInterpose_setWindowNumber:num];
  if (num > 0)
    OnPluginWindowShown(WindowInfo(self), NO);
}

@end

@interface NSApplication (PluginInterposing)
- (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window;
@end

@implementation NSApplication (PluginInterposing)

- (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window {
  mac_plugin_interposing::child::FocusPluginProcess();
  // This is out-of-order relative to the other calls, but runModalForWindow:
  // won't return until the window closes, and the order only matters for
  // full-screen windows.
  OnPluginWindowShown(WindowInfo(window), YES);
  return [self pluginInterpose_runModalForWindow:window];
}

@end

// Hook commands to manipulate the current cursor, so that they can be passed
// from the child process to the parent process.  These commands have no
// effect unless they're performed in the parent process.
@interface NSCursor (PluginInterposing)
- (void)pluginInterpose_set;
- (void)pluginInterpose_push;
- (void)pluginInterpose_pop;
+ (NSCursor*)pluginInterpose_currentCursor;
+ (void)pluginInterpose_hide;
+ (void)pluginInterpose_unhide;
+ (void)pluginInterpose_pop;
@end

// Cache the results of [NSCursor set], [NSCursor push] and [NSCursor pop].
// The last element is always the current cursor.
static NSMutableArray* gCursorStack = nil;

static BOOL initCursorStack()
{
  if (!gCursorStack) {
    gCursorStack = [[NSMutableArray arrayWithCapacity:5] retain];
  }
  return (gCursorStack != NULL);
}

static NSCursor* currentCursorFromCache()
{
  if (!initCursorStack())
    return nil;
  return (NSCursor*) [gCursorStack lastObject];
}

static void setCursorInCache(NSCursor* aCursor)
{
  if (!initCursorStack() || !aCursor)
    return;
  NSUInteger count = [gCursorStack count];
  if (count) {
    [gCursorStack replaceObjectAtIndex:count - 1 withObject:aCursor];
  } else {
    [gCursorStack addObject:aCursor];
  }
}

static void pushCursorInCache(NSCursor* aCursor)
{
  if (!initCursorStack() || !aCursor)
    return;
  [gCursorStack addObject:aCursor];
}

static void popCursorInCache()
{
  if (!initCursorStack())
    return;
  // Apple's doc on the +[NSCursor pop] method says:  "If the current cursor
  // is the only cursor on the stack, this method does nothing."
  if ([gCursorStack count] > 1) {
    [gCursorStack removeLastObject];
  }
}

@implementation NSCursor (PluginInterposing)

- (void)pluginInterpose_set
{
  NSCursorInfo info(self);
  OnSetCursor(info);
  setCursorInCache(self);
  [self pluginInterpose_set];
}

- (void)pluginInterpose_push
{
  NSCursorInfo info(self);
  OnPushCursor(info);
  pushCursorInCache(self);
  [self pluginInterpose_push];
}

- (void)pluginInterpose_pop
{
  OnPopCursor();
  popCursorInCache();
  [self pluginInterpose_pop];
}

// The currentCursor method always returns nil when running in a background
// process.  But this may confuse plugins (notably Flash, see bug 621117).  So
// if we get a nil return from the "call to super", we return a cursor that's
// been cached by previous calls to set or push.  According to Apple's docs,
// currentCursor "only returns the cursor set by your application using
// NSCursor methods".  So we don't need to worry about changes to the cursor
// made by other methods like SetThemeCursor().
+ (NSCursor*)pluginInterpose_currentCursor
{
  NSCursor* retval = [self pluginInterpose_currentCursor];
  if (!retval) {
    retval = currentCursorFromCache();
  }
  return retval;
}

+ (void)pluginInterpose_hide
{
  OnHideCursor();
  [self pluginInterpose_hide];
}

+ (void)pluginInterpose_unhide
{
  OnUnhideCursor();
  [self pluginInterpose_unhide];
}

+ (void)pluginInterpose_pop
{
  OnPopCursor();
  popCursorInCache();
  [self pluginInterpose_pop];
}

@end

static void ExchangeMethods(Class target_class,
                            BOOL class_method,
                            SEL original,
                            SEL replacement) {
  Method m1;
  Method m2;
  if (class_method) {
    m1 = class_getClassMethod(target_class, original);
    m2 = class_getClassMethod(target_class, replacement);
  } else {
    m1 = class_getInstanceMethod(target_class, original);
    m2 = class_getInstanceMethod(target_class, replacement);
  }

  if (m1 == m2)
    return;

  if (m1 && m2)
    method_exchangeImplementations(m1, m2);
  else
    NS_NOTREACHED("Cocoa swizzling failed");
}

namespace mac_plugin_interposing {
namespace child {

void SetUpCocoaInterposing() {
  Class nswindow_class = [NSWindow class];
  ExchangeMethods(nswindow_class, NO, @selector(orderOut:),
                  @selector(pluginInterpose_orderOut:));
  ExchangeMethods(nswindow_class, NO, @selector(orderFront:),
                  @selector(pluginInterpose_orderFront:));
  ExchangeMethods(nswindow_class, NO, @selector(makeKeyAndOrderFront:),
                  @selector(pluginInterpose_makeKeyAndOrderFront:));
  ExchangeMethods(nswindow_class, NO, @selector(_setWindowNumber:),
                  @selector(pluginInterpose_setWindowNumber:));

  ExchangeMethods([NSApplication class], NO, @selector(runModalForWindow:),
                  @selector(pluginInterpose_runModalForWindow:));

  Class nscursor_class = [NSCursor class];
  ExchangeMethods(nscursor_class, NO, @selector(set),
                  @selector(pluginInterpose_set));
  ExchangeMethods(nscursor_class, NO, @selector(push),
                  @selector(pluginInterpose_push));
  ExchangeMethods(nscursor_class, NO, @selector(pop),
                  @selector(pluginInterpose_pop));
  ExchangeMethods(nscursor_class, YES, @selector(currentCursor),
                  @selector(pluginInterpose_currentCursor));
  ExchangeMethods(nscursor_class, YES, @selector(hide),
                  @selector(pluginInterpose_hide));
  ExchangeMethods(nscursor_class, YES, @selector(unhide),
                  @selector(pluginInterpose_unhide));
  ExchangeMethods(nscursor_class, YES, @selector(pop),
                  @selector(pluginInterpose_pop));
}

}  // namespace child
}  // namespace mac_plugin_interposing

// Called from plugin_child_interpose.mm, which hooks calls to
// SetCursor() (the QuickDraw call) from the plugin child process.
extern "C" NS_VISIBILITY_DEFAULT BOOL
mac_plugin_interposing_child_OnSetCursor(const Cursor* cursor)
{
  NSCursorInfo info(cursor);
  return OnSetCursor(info);
}

// Called from plugin_child_interpose.mm, which hooks calls to
// SetThemeCursor() (the Appearance Manager call) from the plugin child
// process.
extern "C" NS_VISIBILITY_DEFAULT BOOL
mac_plugin_interposing_child_OnSetThemeCursor(ThemeCursor cursor)
{
  NSCursorInfo info;
  switch (cursor) {
    case kThemeArrowCursor:
      info.SetType(NSCursorInfo::TypeArrow);
      break;
    case kThemeCopyArrowCursor:
      info.SetType(NSCursorInfo::TypeDragCopy);
      break;
    case kThemeAliasArrowCursor:
      info.SetType(NSCursorInfo::TypeDragLink);
      break;
    case kThemeContextualMenuArrowCursor:
      info.SetType(NSCursorInfo::TypeContextualMenu);
      break;
    case kThemeIBeamCursor:
      info.SetType(NSCursorInfo::TypeIBeam);
      break;
    case kThemeCrossCursor:
    case kThemePlusCursor:
      info.SetType(NSCursorInfo::TypeCrosshair);
      break;
    case kThemeWatchCursor:
    case kThemeSpinningCursor:
      info.SetType(NSCursorInfo::TypeArrow);
      break;
    case kThemeClosedHandCursor:
      info.SetType(NSCursorInfo::TypeClosedHand);
      break;
    case kThemeOpenHandCursor:
      info.SetType(NSCursorInfo::TypeOpenHand);
      break;
    case kThemePointingHandCursor:
    case kThemeCountingUpHandCursor:
    case kThemeCountingDownHandCursor:
    case kThemeCountingUpAndDownHandCursor:
      info.SetType(NSCursorInfo::TypePointingHand);
      break;
    case kThemeResizeLeftCursor:
      info.SetType(NSCursorInfo::TypeResizeLeft);
      break;
    case kThemeResizeRightCursor:
      info.SetType(NSCursorInfo::TypeResizeRight);
      break;
    case kThemeResizeLeftRightCursor:
      info.SetType(NSCursorInfo::TypeResizeLeftRight);
      break;
    case kThemeNotAllowedCursor:
      info.SetType(NSCursorInfo::TypeNotAllowed);
      break;
    case kThemeResizeUpCursor:
      info.SetType(NSCursorInfo::TypeResizeUp);
      break;
    case kThemeResizeDownCursor:
      info.SetType(NSCursorInfo::TypeResizeDown);
      break;
    case kThemeResizeUpDownCursor:
      info.SetType(NSCursorInfo::TypeResizeUpDown);
      break;
    case kThemePoofCursor:
      info.SetType(NSCursorInfo::TypeDisappearingItem);
      break;
    default:
      info.SetType(NSCursorInfo::TypeArrow);
      break;
  }
  return OnSetCursor(info);
}

extern "C" NS_VISIBILITY_DEFAULT BOOL
mac_plugin_interposing_child_OnHideCursor()
{
  return OnHideCursor();
}

extern "C" NS_VISIBILITY_DEFAULT BOOL
mac_plugin_interposing_child_OnShowCursor()
{
  return OnUnhideCursor();
}
