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

#import "chrome/browser/ui/cocoa/framed_browser_window.h"

#include "base/logging.h"
#include "chrome/browser/global_keyboard_shortcuts_mac.h"
#import "chrome/browser/ui/cocoa/browser_frame_view.h"
#import "chrome/browser/ui/cocoa/browser_window_controller.h"
#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
#import "chrome/browser/ui/cocoa/themed_window.h"
#import "chrome/browser/renderer_host/render_widget_host_view_mac.h"
#include "chrome/browser/themes/theme_service.h"

// Implementer's note: Moving the window controls is tricky. When altering the
// code, ensure that:
// - accessibility hit testing works
// - the accessibility hierarchy is correct
// - close/min in the background don't bring the window forward
// - rollover effects work correctly

namespace {

// Size of the gradient. Empirically determined so that the gradient looks
// like what the heuristic does when there are just a few tabs.
const CGFloat kWindowGradientHeight = 24.0;

}

@interface FramedBrowserWindow (Private)

- (void)adjustCloseButton:(NSNotification*)notification;
- (void)adjustMiniaturizeButton:(NSNotification*)notification;
- (void)adjustZoomButton:(NSNotification*)notification;
- (void)adjustButton:(NSButton*)button
              ofKind:(NSWindowButton)kind;
- (NSView*)frameView;

@end

@implementation FramedBrowserWindow

- (id)initWithContentRect:(NSRect)contentRect
                styleMask:(NSUInteger)aStyle
                  backing:(NSBackingStoreType)bufferingType
                    defer:(BOOL)flag {
  if ((self = [super initWithContentRect:contentRect
                               styleMask:aStyle
                                 backing:bufferingType
                                   defer:flag])) {
    if (aStyle & NSTexturedBackgroundWindowMask) {
      // The following two calls fix http://www.crbug.com/25684 by preventing
      // the window from recalculating the border thickness as the window is
      // resized.
      // This was causing the window tint to change for the default system theme
      // when the window was being resized.
      [self setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
      [self setContentBorderThickness:kWindowGradientHeight forEdge:NSMaxYEdge];
    }

    closeButton_ = [self standardWindowButton:NSWindowCloseButton];
    [closeButton_ setPostsFrameChangedNotifications:YES];
    miniaturizeButton_ = [self standardWindowButton:NSWindowMiniaturizeButton];
    [miniaturizeButton_ setPostsFrameChangedNotifications:YES];
    zoomButton_ = [self standardWindowButton:NSWindowZoomButton];
    [zoomButton_ setPostsFrameChangedNotifications:YES];

    NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
    [center addObserver:self
               selector:@selector(adjustCloseButton:)
                   name:NSViewFrameDidChangeNotification
                 object:closeButton_];
    [center addObserver:self
               selector:@selector(adjustMiniaturizeButton:)
                   name:NSViewFrameDidChangeNotification
                 object:miniaturizeButton_];
    [center addObserver:self
               selector:@selector(adjustZoomButton:)
                   name:NSViewFrameDidChangeNotification
                 object:zoomButton_];
    [center addObserver:self
               selector:@selector(themeDidChangeNotification:)
                   name:kBrowserThemeDidChangeNotification
                 object:nil];
  }

  return self;
}

- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self];
  [super dealloc];
}

- (void)setWindowController:(NSWindowController*)controller {
  if (controller == [self windowController]) {
    return;
  }

  [super setWindowController:controller];

  BrowserWindowController* browserController
      = static_cast<BrowserWindowController*>(controller);
  if ([browserController isKindOfClass:[BrowserWindowController class]]) {
    hasTabStrip_ = [browserController hasTabStrip];
  } else {
    hasTabStrip_ = NO;
  }

  // Force re-layout of the window buttons by wiggling the size of the frame
  // view.
  NSView* frameView = [[self contentView] superview];
  BOOL frameViewDidAutoresizeSubviews = [frameView autoresizesSubviews];
  [frameView setAutoresizesSubviews:NO];
  NSRect oldFrame = [frameView frame];
  [frameView setFrame:NSZeroRect];
  [frameView setFrame:oldFrame];
  [frameView setAutoresizesSubviews:frameViewDidAutoresizeSubviews];
}

- (void)adjustCloseButton:(NSNotification*)notification {
  [self adjustButton:[notification object]
              ofKind:NSWindowCloseButton];
}

- (void)adjustMiniaturizeButton:(NSNotification*)notification {
  [self adjustButton:[notification object]
              ofKind:NSWindowMiniaturizeButton];
}

- (void)adjustZoomButton:(NSNotification*)notification {
  [self adjustButton:[notification object]
              ofKind:NSWindowZoomButton];
}

- (void)adjustButton:(NSButton*)button
              ofKind:(NSWindowButton)kind {
  NSRect buttonFrame = [button frame];
  NSRect frameViewBounds = [[self frameView] bounds];

  CGFloat xOffset = hasTabStrip_
      ? kFramedWindowButtonsWithTabStripOffsetFromLeft
      : kFramedWindowButtonsWithoutTabStripOffsetFromLeft;
  CGFloat yOffset = hasTabStrip_
      ? kFramedWindowButtonsWithTabStripOffsetFromTop
      : kFramedWindowButtonsWithoutTabStripOffsetFromTop;
  buttonFrame.origin =
      NSMakePoint(xOffset, (NSHeight(frameViewBounds) -
                            NSHeight(buttonFrame) - yOffset));

  switch (kind) {
    case NSWindowZoomButton:
      buttonFrame.origin.x += NSWidth([miniaturizeButton_ frame]);
      buttonFrame.origin.x += kFramedWindowButtonsInterButtonSpacing;
      // fallthrough
    case NSWindowMiniaturizeButton:
      buttonFrame.origin.x += NSWidth([closeButton_ frame]);
      buttonFrame.origin.x += kFramedWindowButtonsInterButtonSpacing;
      // fallthrough
    default:
      break;
  }

  BOOL didPost = [button postsBoundsChangedNotifications];
  [button setPostsFrameChangedNotifications:NO];
  [button setFrame:buttonFrame];
  [button setPostsFrameChangedNotifications:didPost];
}

- (NSView*)frameView {
  return [[self contentView] superview];
}

// The tab strip view covers our window buttons. So we add hit testing here
// to find them properly and return them to the accessibility system.
- (id)accessibilityHitTest:(NSPoint)point {
  NSPoint windowPoint = [self convertScreenToBase:point];
  NSControl* controls[] = { closeButton_, zoomButton_, miniaturizeButton_ };
  id value = nil;
  for (size_t i = 0; i < sizeof(controls) / sizeof(controls[0]); ++i) {
    if (NSPointInRect(windowPoint, [controls[i] frame])) {
      value = [controls[i] accessibilityHitTest:point];
      break;
    }
  }
  if (!value) {
    value = [super accessibilityHitTest:point];
  }
  return value;
}

- (void)windowMainStatusChanged {
  NSView* frameView = [self frameView];
  NSView* contentView = [self contentView];
  NSRect updateRect = [frameView frame];
  NSRect contentRect = [contentView frame];
  CGFloat tabStripHeight = [TabStripController defaultTabHeight];
  updateRect.size.height -= NSHeight(contentRect) - tabStripHeight;
  updateRect.origin.y = NSMaxY(contentRect) - tabStripHeight;
  [[self frameView] setNeedsDisplayInRect:updateRect];
}

- (void)becomeMainWindow {
  [self windowMainStatusChanged];
  [super becomeMainWindow];
}

- (void)resignMainWindow {
  [self windowMainStatusChanged];
  [super resignMainWindow];
}

// Called after the current theme has changed.
- (void)themeDidChangeNotification:(NSNotification*)aNotification {
  [[self frameView] setNeedsDisplay:YES];
}

- (void)sendEvent:(NSEvent*)event {
  // For Cocoa windows, clicking on the close and the miniaturize buttons (but
  // not the zoom button) while a window is in the background does NOT bring
  // that window to the front. We don't get that behavior for free (probably
  // because the tab strip view covers those buttons), so we handle it here.
  // Zoom buttons do bring the window to the front. Note that Finder windows (in
  // Leopard) behave differently in this regard in that zoom buttons don't bring
  // the window to the foreground.
  BOOL eventHandled = NO;
  if (![self isMainWindow]) {
    if ([event type] == NSLeftMouseDown) {
      NSView* frameView = [self frameView];
      NSPoint mouse = [frameView convertPoint:[event locationInWindow]
                                     fromView:nil];
      if (NSPointInRect(mouse, [closeButton_ frame])) {
        [closeButton_ mouseDown:event];
        eventHandled = YES;
      } else if (NSPointInRect(mouse, [miniaturizeButton_ frame])) {
        [miniaturizeButton_ mouseDown:event];
        eventHandled = YES;
      }
    }
  }
  if (!eventHandled) {
    [super sendEvent:event];
  }
}

- (void)setShouldHideTitle:(BOOL)flag {
  shouldHideTitle_ = flag;
}

- (BOOL)_isTitleHidden {
  return shouldHideTitle_;
}

// This method is called whenever a window is moved in order to ensure it fits
// on the screen.  We cannot always handle resizes without breaking, so we
// prevent frame constraining in those cases.
- (NSRect)constrainFrameRect:(NSRect)frame toScreen:(NSScreen*)screen {
  // Do not constrain the frame rect if our delegate says no.  In this case,
  // return the original (unconstrained) frame.
  id delegate = [self delegate];
  if ([delegate respondsToSelector:@selector(shouldConstrainFrameRect)] &&
      ![delegate shouldConstrainFrameRect])
    return frame;

  return [super constrainFrameRect:frame toScreen:screen];
}

@end