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

#include "webkit/tools/test_shell/webwidget_host.h"

#include "base/logging.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/mac/WebInputEventFactory.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/mac/WebScreenInfoFactory.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebPopupMenu.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebScreenInfo.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebSize.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"
#include "webkit/glue/webkit_glue.h"
#include "webkit/tools/test_shell/test_shell.h"

using WebKit::WebInputEvent;
using WebKit::WebInputEventFactory;
using WebKit::WebKeyboardEvent;
using WebKit::WebMouseEvent;
using WebKit::WebMouseWheelEvent;
using WebKit::WebPopupMenu;
using WebKit::WebScreenInfo;
using WebKit::WebScreenInfoFactory;
using WebKit::WebSize;
using WebKit::WebWidgetClient;

/*static*/
WebWidgetHost* WebWidgetHost::Create(NSView* parent_view,
                                     WebWidgetClient* client) {
  WebWidgetHost* host = new WebWidgetHost();

  NSRect content_rect = [parent_view frame];
  content_rect.origin.y += 64;
  content_rect.size.height -= 64;
  host->view_ = [[NSView alloc] initWithFrame:content_rect];
  [parent_view addSubview:host->view_];

  // ui::SetWindowUserData(host->hwnd_, host);

  host->webwidget_ = WebPopupMenu::create(client);
  host->webwidget_->resize(WebSize(NSWidth(content_rect),
                                   NSHeight(content_rect)));
  return host;
}

/*static*/
void WebWidgetHost::HandleEvent(NSView* view, NSEvent* event) {
  /* TODO(port): rig up a way to get to the host */
  WebWidgetHost* host = NULL;
  if (host) {
    switch ([event type]) {
      case NSLeftMouseDown:
      case NSLeftMouseUp:
      case NSRightMouseDown:
      case NSRightMouseUp:
      case NSOtherMouseDown:
      case NSOtherMouseUp:
      case NSMouseEntered:
      case NSMouseExited:
        host->MouseEvent(event);
        break;

      case NSScrollWheel:
        host->WheelEvent(event);
        break;

      case NSKeyDown:
      case NSKeyUp:
        host->KeyEvent(event);
        break;

      case NSAppKitDefined:
        switch ([event subtype]) {
          case NSApplicationActivatedEventType:
            host->SetFocus(true);
            break;
          case NSApplicationDeactivatedEventType:
            host->SetFocus(false);
            break;
        }
        break;
    }
  }
}

void WebWidgetHost::DidInvalidateRect(const gfx::Rect& damaged_rect) {
#ifndef NDEBUG
  DLOG_IF(WARNING, painting_) << "unexpected invalidation while painting";
#endif

  // If this invalidate overlaps with a pending scroll, then we have to
  // downgrade to invalidating the scroll rect.
  if (damaged_rect.Intersects(scroll_rect_)) {
    paint_rect_ = paint_rect_.Union(scroll_rect_);
    ResetScrollRect();
  }
  paint_rect_ = paint_rect_.Union(damaged_rect);

  NSRect r = NSRectFromCGRect(damaged_rect.ToCGRect());
  // flip to cocoa coordinates
  r.origin.y = [view_ frame].size.height - r.size.height - r.origin.y;
  [view_ setNeedsDisplayInRect:r];
}

void WebWidgetHost::DidScrollRect(int dx, int dy, const gfx::Rect& clip_rect) {
  DCHECK(dx || dy);

  // If we already have a pending scroll operation or if this scroll operation
  // intersects the existing paint region, then just failover to invalidating.
  if (!scroll_rect_.IsEmpty() || paint_rect_.Intersects(clip_rect)) {
    paint_rect_ = paint_rect_.Union(scroll_rect_);
    ResetScrollRect();
    paint_rect_ = paint_rect_.Union(clip_rect);
  }

  // We will perform scrolling lazily, when requested to actually paint.
  scroll_rect_ = clip_rect;
  scroll_dx_ = dx;
  scroll_dy_ = dy;

  NSRect r = NSRectFromCGRect(clip_rect.ToCGRect());
  // flip to cocoa coordinates
  r.origin.y = [view_ frame].size.height - r.size.height - r.origin.y;
  [view_ setNeedsDisplayInRect:r];
}

void WebWidgetHost::ScheduleComposite() {
  if (!webwidget_)
    return;
  WebSize size = webwidget_->size();
  NSRect r = NSMakeRect(0, 0, size.width, size.height);
  [view_ setNeedsDisplayInRect:r];
}

// void WebWidgetHost::SetCursor(HCURSOR cursor) {
// }

void WebWidgetHost::DiscardBackingStore() {
  canvas_.reset();
}

WebWidgetHost::WebWidgetHost()
    : view_(NULL),
      webwidget_(NULL),
      scroll_dx_(0),
      scroll_dy_(0),
      ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
  set_painting(false);
}

WebWidgetHost::~WebWidgetHost() {
  // ui::SetWindowUserData(hwnd_, 0);

  webwidget_->close();
}

void WebWidgetHost::UpdatePaintRect(const gfx::Rect& rect) {
  paint_rect_ = paint_rect_.Union(rect);
}

void WebWidgetHost::Paint() {
  gfx::Rect client_rect(NSRectToCGRect([view_ frame]));
  NSGraphicsContext* view_context = [NSGraphicsContext currentContext];
  CGContextRef context = static_cast<CGContextRef>([view_context graphicsPort]);

  // Allocate a canvas if necessary
  if (!canvas_.get()) {
    ResetScrollRect();
    paint_rect_ = client_rect;
    canvas_.reset(new skia::PlatformCanvas(
        paint_rect_.width(), paint_rect_.height(), true));
  }

  // make sure webkit draws into our bitmap, not the window
  CGContextRef bitmap_context =
      skia::GetBitmapContext(skia::GetTopDevice(*canvas_));
  [NSGraphicsContext setCurrentContext:
      [NSGraphicsContext graphicsContextWithGraphicsPort:bitmap_context
                                                 flipped:YES]];

  webwidget_->animate(0.0);

  // This may result in more invalidation
  webwidget_->layout();

  // Scroll the canvas if necessary
  scroll_rect_ = client_rect.Intersect(scroll_rect_);
  if (!scroll_rect_.IsEmpty()) {
    // add to invalidate rect, since there's no equivalent of ScrollDC.
    paint_rect_ = paint_rect_.Union(scroll_rect_);
  }
  ResetScrollRect();

  // Paint the canvas if necessary.  Allow painting to generate extra rects the
  // first time we call it.  This is necessary because some WebCore rendering
  // objects update their layout only when painted.
  for (int i = 0; i < 2; ++i) {
    paint_rect_ = client_rect.Intersect(paint_rect_);
    if (!paint_rect_.IsEmpty()) {
      gfx::Rect rect(paint_rect_);
      paint_rect_ = gfx::Rect();

//      DLOG_IF(WARNING, i == 1) << "painting caused additional invalidations";
      PaintRect(rect);
    }
  }
  DCHECK(paint_rect_.IsEmpty());

  // set the context back to our window
  [NSGraphicsContext setCurrentContext: view_context];

  // Paint to the screen
  if ([view_ lockFocusIfCanDraw]) {
    int bitmap_height = CGBitmapContextGetHeight(bitmap_context);
    int bitmap_width = CGBitmapContextGetWidth(bitmap_context);
    CGRect bitmap_rect = { { 0, 0 },
                           { bitmap_width, bitmap_height } };
    skia::DrawToNativeContext(canvas_.get(), context, 0,
        client_rect.height() - bitmap_height, &bitmap_rect);

    [view_ unlockFocus];
  }
}

WebScreenInfo WebWidgetHost::GetScreenInfo() {
  return WebScreenInfoFactory::screenInfo(view_);
}

void WebWidgetHost::Resize(const gfx::Rect& rect) {
  // Force an entire re-paint.  TODO(darin): Maybe reuse this memory buffer.
  DiscardBackingStore();
  webwidget_->resize(WebSize(rect.width(), rect.height()));
}

void WebWidgetHost::MouseEvent(NSEvent *event) {
  const WebMouseEvent& web_event = WebInputEventFactory::mouseEvent(
      event, view_);
  webwidget_->handleInputEvent(web_event);
}

void WebWidgetHost::WheelEvent(NSEvent *event) {
  webwidget_->handleInputEvent(
      WebInputEventFactory::mouseWheelEvent(event, view_));
}

void WebWidgetHost::KeyEvent(NSEvent *event) {
  WebKeyboardEvent keyboard_event(WebInputEventFactory::keyboardEvent(event));
  webwidget_->handleInputEvent(keyboard_event);
  if ([event type] == NSKeyDown) {
    // Send a Char event here to emulate the keyboard events.
    // TODO(hbono): Bug 20852 <http://crbug.com/20852> implement the
    // NSTextInput protocol and remove this code.
    keyboard_event.type = WebInputEvent::Char;
    webwidget_->handleInputEvent(keyboard_event);
  }
}

void WebWidgetHost::SetFocus(bool enable) {
  // Ignore focus calls in layout test mode so that tests don't mess with each
  // other's focus when running in parallel.
  if (!TestShell::layout_test_mode())
    webwidget_->setFocus(enable);
}

void WebWidgetHost::ResetScrollRect() {
  scroll_rect_ = gfx::Rect();
  scroll_dx_ = 0;
  scroll_dy_ = 0;
}

void WebWidgetHost::PaintRect(const gfx::Rect& rect) {
#ifndef NDEBUG
  DCHECK(!painting_);
#endif
  DCHECK(canvas_.get());

  set_painting(true);
  webwidget_->paint(webkit_glue::ToWebCanvas(canvas_.get()), rect);
  set_painting(false);
}