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

#import "content/browser/renderer_host/accelerated_plugin_view_mac.h"

#include <vector>

#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "content/browser/renderer_host/render_widget_host_view_mac.h"
#include "content/public/browser/browser_thread.h"
#import "ui/base/cocoa/underlay_opengl_hosting_window.h"
#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_switches.h"
#include "ui/gl/gpu_switching_manager.h"

using content::BrowserThread;
using content::RenderWidgetHostViewMac;

@interface AcceleratedPluginView(Private)

// Initialize the OpenGL context.
- (void)initOpenGLContext;

@end  // AcceleratedPluginView(Private)

@implementation AcceleratedPluginView

- (void)drawView {
  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  TRACE_EVENT0("browser", "AcceleratedPluginViewMac::drawView");

  if (renderWidgetHostView_ && cglContext_) {
    // TODO(thakis): Pixel or view coordinates for size?
    renderWidgetHostView_->DrawAcceleratedSurfaceInstance(
        cglContext_, pluginHandle_, [super frame].size);
    CGLFlushDrawable(cglContext_);
  }

  CGLSetCurrentContext(0);
}

- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r
                         pluginHandle:(gfx::PluginWindowHandle)pluginHandle {
  if ((self = [super initWithFrame:NSZeroRect])) {
    renderWidgetHostView_ = r;
    pluginHandle_ = pluginHandle;

    [self setAutoresizingMask:NSViewMaxXMargin|NSViewMinYMargin];

    handlingGlobalFrameDidChange_ = NO;
    [[NSNotificationCenter defaultCenter]
         addObserver:self
            selector:@selector(globalFrameDidChange:)
                name:NSViewGlobalFrameDidChangeNotification
              object:self];
  }
  return self;
}

- (void)dealloc {
  if (renderWidgetHostView_)
    renderWidgetHostView_->DeallocFakePluginWindowHandle(pluginHandle_);
  [[NSNotificationCenter defaultCenter] removeObserver:self];
  [super dealloc];
}

- (void)setCutoutRects:(NSArray*)cutout_rects {
  cutoutRects_.reset([cutout_rects copy]);
}

- (void)onRenderWidgetHostViewGone {
  if (!renderWidgetHostView_)
    return;

  // Deallocate the plugin handle while we still can.
  renderWidgetHostView_->DeallocFakePluginWindowHandle(pluginHandle_);
  renderWidgetHostView_ = NULL;
}

- (void)drawRect:(NSRect)rect {
  TRACE_EVENT0("browser", "AcceleratedPluginView::drawRect");
  const NSRect* dirtyRects;
  NSInteger dirtyRectCount;
  [self getRectsBeingDrawn:&dirtyRects count:&dirtyRectCount];

  {
    gfx::ScopedNSGraphicsContextSaveGState scopedGState;

    // Mask out any cutout rects--somewhat counterintuitively cutout rects are
    // places where clearColor is *not* drawn. The trick is that drawing nothing
    // lets the parent view (i.e., the web page) show through, whereas drawing
    // clearColor punches a hole in the window (letting OpenGL show through).
    if ([cutoutRects_.get() count] > 0) {
      NSBezierPath* path = [NSBezierPath bezierPath];
      // Trace the bounds clockwise to give a base clip rect of the whole view.
      NSRect bounds = [self bounds];
      [path moveToPoint:bounds.origin];
      [path lineToPoint:NSMakePoint(NSMinX(bounds), NSMaxY(bounds))];
      [path lineToPoint:NSMakePoint(NSMaxX(bounds), NSMaxY(bounds))];
      [path lineToPoint:NSMakePoint(NSMaxX(bounds), NSMinY(bounds))];
      [path closePath];

      // Then trace each cutout rect counterclockwise to remove that region from
      // the clip region.
      for (NSValue* rectWrapper in cutoutRects_.get()) {
        [path appendBezierPathWithRect:[rectWrapper rectValue]];
      }

      [path addClip];
    }

    // Punch a hole so that the OpenGL view shows through.
    [[NSColor clearColor] set];
    NSRectFillList(dirtyRects, dirtyRectCount);
  }

  [self drawView];
}

- (void)rightMouseDown:(NSEvent*)event {
  // The NSResponder documentation: "Note: The NSView implementation of this
  // method does not pass the message up the responder chain, it handles it
  // directly."
  // That's bad, we want the next responder (RWHVMac) to handle this event to
  // dispatch it to the renderer.
  [[self nextResponder] rightMouseDown:event];
}

- (void)globalFrameDidChange:(NSNotification*)notification {
  // Short-circuit recursive calls.
  if (handlingGlobalFrameDidChange_)
    return;

  handlingGlobalFrameDidChange_ = YES;

  if (glContext_) {
    // This call to -update can call -globalFrameDidChange: again, see
    // http://crbug.com/55754 comments 22 and 24.
    [glContext_ update];

    // You would think that -update updates the viewport. You would be wrong.
    CGLSetCurrentContext(cglContext_);
    NSSize size = [self frame].size;
    glViewport(0, 0, size.width, size.height);
    CGLSetCurrentContext(0);
  }

  handlingGlobalFrameDidChange_ = NO;
}

- (void)prepareForGLRendering {
  TRACE_EVENT0("browser", "AcceleratedPluginView::prepareForGLRendering");

  // If we're using OpenGL, make sure it is connected.
  if ([glContext_ view] != self) {
    [glContext_ setView:self];
  }
}

- (void)renewGState {
  TRACE_EVENT0("browser", "AcceleratedPluginView::renewGState");
  // Synchronize with window server to avoid flashes or corrupt drawing.
  [[self window] disableScreenUpdatesUntilFlush];
  [self globalFrameDidChange:nil];
  [super renewGState];
}

- (void)setUpGState {
  TRACE_EVENT0("browser", "AcceleratedPluginView::setUpGState");
  [self prepareForGLRendering];
}

// TODO(jbates): is lockFocus needed anymore? it seems redundant with
//               setUpGState in traces.
- (void)lockFocus {
  TRACE_EVENT0("browser", "AcceleratedPluginView::lockFocus");
  [super lockFocus];
  [self prepareForGLRendering];
  [glContext_ makeCurrentContext];
}

- (void)viewDidUnhide {
  TRACE_EVENT0("browser", "AcceleratedPluginView::viewDidUnhide");
  [super viewDidUnhide];

  // Delay context creation until view unhide, see http://crbug.com/109151
  if (renderWidgetHostView_ && !glContext_) {
    [self initOpenGLContext];
  }
}

- (BOOL)acceptsFirstResponder {
  // Accept first responder if the first responder isn't the RWHVMac, and if the
  // RWHVMac accepts first responder.  If the RWHVMac does not accept first
  // responder, do not accept on its behalf.
  return ([[self window] firstResponder] != [self superview] &&
          [[self superview] acceptsFirstResponder]);
}

- (BOOL)becomeFirstResponder {
  // Delegate first responder to the RWHVMac.
  [[self window] makeFirstResponder:[self superview]];
  return YES;
}

- (void)viewDidMoveToSuperview {
  TRACE_EVENT0("browser", "AcceleratedPluginView::viewDidMoveToSuperview");
  if (![self superview])
    [self onRenderWidgetHostViewGone];
}
@end  // @implementation AcceleratedPluginView

@implementation AcceleratedPluginView(Private)

- (void)initOpenGLContext {
  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  TRACE_EVENT0("browser", "AcceleratedPluginViewMac::initOpenGLContext");
  std::vector<NSOpenGLPixelFormatAttribute> attributes;
  attributes.push_back(NSOpenGLPFADoubleBuffer);
  if (ui::GpuSwitchingManager::GetInstance()->SupportsDualGpus())
    attributes.push_back(NSOpenGLPFAAllowOfflineRenderers);
  attributes.push_back(0);

  glPixelFormat_.reset([[NSOpenGLPixelFormat alloc]
                         initWithAttributes:&attributes.front()]);
  if (!glPixelFormat_)
    LOG(ERROR) << "NSOpenGLPixelFormat initWithAttributes failed";

  glContext_.reset([[NSOpenGLContext alloc] initWithFormat:glPixelFormat_
                                              shareContext:nil]);
  if (!glContext_)
    LOG(ERROR) << "NSOpenGLContext initWithFormat failed";

  // We "punch a hole" in the window, and have the WindowServer render the
  // OpenGL surface underneath so we can draw over it.
  GLint belowWindow = -1;
  [glContext_ setValues:&belowWindow forParameter:NSOpenGLCPSurfaceOrder];

  cglContext_ = (CGLContextObj)[glContext_ CGLContextObj];
  if (!cglContext_)
    LOG(ERROR) << "CGLContextObj failed";

  cglPixelFormat_ = (CGLPixelFormatObj)[glPixelFormat_ CGLPixelFormatObj];
  if (!cglPixelFormat_)
    LOG(ERROR) << "CGLPixelFormatObj failed";

  // Draw at beam vsync.
  GLint swapInterval;
  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync))
    swapInterval = 0;
  else
    swapInterval = 1;
  [glContext_ setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
}

@end  // @implementation AcceleratedPluginView(Private)
