// Copyright (c) 2009 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 "chrome/browser/cocoa/status_bubble_mac.h"

#include "app/gfx/text_elider.h"
#include "base/string_util.h"
#include "base/sys_string_conversions.h"
#include "googleurl/src/gurl.h"
#import "third_party/GTM/AppKit/GTMNSBezierPath+RoundRect.h"

namespace {

const int kWindowHeight = 18;
// The width of the bubble in relation to the width of the parent window.
const float kWindowWidthPercent = 1.0f/3.0f;

// How close the mouse can get to the infobubble before it starts sliding
// off-screen.
const int kMousePadding = 20;

const int kTextPadding = 3;
const int kTextPositionX = 4;
const int kTextPositionY = 2;

const float kWindowFill = 0.8f;
const float kWindowEdge = 0.7f;

// The roundedness of the edges of our bubble.
const int kBubbleCornerRadius = 4.0f;

// How long each fade should last for.
const int kShowFadeDuration = 0.120f;
const int kHideFadeDuration = 0.200f;

}

// TODO(avi):
// - do display delay

enum BubbleStyle {
  STYLE_BOTTOM,    // Hanging off the bottom of the parent window
  STYLE_FLOATING,  // Between BOTTOM and STANDARD
  STYLE_STANDARD   // Nestled in the corner of the parent window
};

@interface StatusBubbleViewCocoa : NSView {
 @private
  NSString* content_;
  BubbleStyle style_;
}

- (void)setContent:(NSString*)content;
- (void)setStyle:(BubbleStyle)style;
- (NSFont*)font;
@end

StatusBubbleMac::StatusBubbleMac(NSWindow* parent, id delegate)
    : parent_(parent),
      delegate_(delegate),
      window_(nil),
      status_text_(nil),
      url_text_(nil),
      is_download_shelf_visible_(false) {
}

StatusBubbleMac::~StatusBubbleMac() {
  Hide();
}

void StatusBubbleMac::SetStatus(const std::wstring& status) {
  Create();

  NSString* status_ns = base::SysWideToNSString(status);

  SetStatus(status_ns, false);
}

void StatusBubbleMac::SetURL(const GURL& url, const std::wstring& languages) {
  Create();

  NSRect frame = [window_ frame];
  int text_width = static_cast<int>(frame.size.width -
                                    kTextPositionX -
                                    kTextPadding);
  NSFont* font = [[window_ contentView] font];
  gfx::Font font_chr =
      gfx::Font::CreateFont(base::SysNSStringToWide([font fontName]),
                            [font pointSize]);

  std::wstring status = gfx::ElideUrl(url, font_chr, text_width, languages);
  NSString* status_ns = base::SysWideToNSString(status);

  SetStatus(status_ns, true);
}

void StatusBubbleMac::SetStatus(NSString* status, bool is_url) {
  NSString** main;
  NSString** backup;

  if (is_url) {
    main = &url_text_;
    backup = &status_text_;
  } else {
    main = &status_text_;
    backup = &url_text_;
  }

  if ([status isEqualToString:*main])
    return;

  [*main release];
  *main = [status retain];
  if ([*main length] > 0) {
    [[window_ contentView] setContent:*main];
  } else if ([*backup length] > 0) {
    [[window_ contentView] setContent:*backup];
  } else {
    Hide();
  }

  FadeIn();
}

void StatusBubbleMac::Hide() {
  FadeOut();

  if (window_) {
    [parent_ removeChildWindow:window_];
    [window_ release];
    window_ = nil;
  }

  [status_text_ release];
  status_text_ = nil;
  [url_text_ release];
  url_text_ = nil;
}

void StatusBubbleMac::MouseMoved() {
  if (!window_)
    return;

  NSPoint cursor_location = [NSEvent mouseLocation];
  --cursor_location.y;  // docs say the y coord starts at 1 not 0; don't ask why

  // Get the normal position of the frame.
  NSRect window_frame = [window_ frame];
  window_frame.origin = [parent_ frame].origin;

  // Get the cursor position relative to the popup.
  cursor_location.x -= NSMaxX(window_frame);
  cursor_location.y -= NSMaxY(window_frame);

  // If the mouse is in a position where we think it would move the
  // status bubble, figure out where and how the bubble should be moved.
  if (cursor_location.y < kMousePadding &&
      cursor_location.x < kMousePadding) {
    int offset = kMousePadding - cursor_location.y;

    // Make the movement non-linear.
    offset = offset * offset / kMousePadding;

    // When the mouse is entering from the right, we want the offset to be
    // scaled by how horizontally far away the cursor is from the bubble.
    if (cursor_location.x > 0) {
      offset = offset * ((kMousePadding - cursor_location.x) / kMousePadding);
    }

    // Cap the offset and change the visual presentation of the bubble
    // depending on where it ends up (so that rounded corners square off
    // and mate to the edges of the tab content).
    if (offset >= NSHeight(window_frame)) {
      offset = NSHeight(window_frame);
      [[window_ contentView] setStyle:STYLE_BOTTOM];
    } else if (offset > 0) {
      [[window_ contentView] setStyle:STYLE_FLOATING];
    } else {
      [[window_ contentView] setStyle:STYLE_STANDARD];
    }

    offset_ = offset;
    window_frame.origin.y -= offset;
  } else {
    offset_ = 0;
    [[window_ contentView] setStyle:STYLE_STANDARD];
  }

  // |delegate_| can be nil during unit tests.
  if (is_download_shelf_visible_) {
    if ([delegate_ respondsToSelector:@selector(verticalOffsetForStatusBubble)])
      window_frame.origin.y += [delegate_ verticalOffsetForStatusBubble];
  }

  [window_ setFrame:window_frame display:YES];
}

void StatusBubbleMac::UpdateDownloadShelfVisibility(bool visible) {
  is_download_shelf_visible_ = visible;
}

void StatusBubbleMac::Create() {
  if (window_)
    return;

  NSRect rect = [parent_ frame];
  rect.size.height = kWindowHeight;
  rect.size.width = static_cast<int>(kWindowWidthPercent * rect.size.width);
  // TODO(avi):fix this for RTL
  window_ = [[NSWindow alloc] initWithContentRect:rect
                                        styleMask:NSBorderlessWindowMask
                                          backing:NSBackingStoreBuffered
                                            defer:YES];
  [window_ setMovableByWindowBackground:NO];
  [window_ setBackgroundColor:[NSColor clearColor]];
  [window_ setLevel:NSNormalWindowLevel];
  [window_ setOpaque:NO];
  [window_ setHasShadow:NO];

  StatusBubbleViewCocoa* view =
      [[[StatusBubbleViewCocoa alloc] initWithFrame:NSZeroRect] autorelease];
  [window_ setContentView:view];

  [parent_ addChildWindow:window_ ordered:NSWindowAbove];

  [window_ setAlphaValue:0.0f];

  offset_ = 0;
  [view setStyle:STYLE_STANDARD];
  MouseMoved();
}

void StatusBubbleMac::FadeIn() {
  [NSAnimationContext beginGrouping];
  [[NSAnimationContext currentContext] setDuration:kShowFadeDuration];
  [[window_ animator] setAlphaValue:1.0f];
  [NSAnimationContext endGrouping];
}

void StatusBubbleMac::FadeOut() {
  [NSAnimationContext beginGrouping];
  [[NSAnimationContext currentContext] setDuration:kHideFadeDuration];
  [[window_ animator] setAlphaValue:0.0f];
  [NSAnimationContext endGrouping];
}

@implementation StatusBubbleViewCocoa

- (void)dealloc {
  [content_ release];
  [super dealloc];
}

- (void)setContent:(NSString*)content {
  [content_ autorelease];
  content_ = [content copy];
  [self setNeedsDisplay:YES];
}

- (void)setStyle:(BubbleStyle)style {
  style_ = style;
  [self setNeedsDisplay:YES];
}

- (NSFont*)font {
  return [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
}

- (void)drawRect:(NSRect)rect {
  float tl_radius, tr_radius, bl_radius, br_radius;

  switch (style_) {
    case STYLE_BOTTOM:
      tl_radius = 0.0f;
      tr_radius = 0.0f;
      bl_radius = kBubbleCornerRadius;
      br_radius = kBubbleCornerRadius;
      break;
    case STYLE_FLOATING:
      tl_radius = 0.0f;
      tr_radius = kBubbleCornerRadius;
      bl_radius = kBubbleCornerRadius;
      br_radius = kBubbleCornerRadius;
      break;
    case STYLE_STANDARD:
      tl_radius = 0.0f;
      tr_radius = kBubbleCornerRadius;
      bl_radius = 0.0f;
      br_radius = 0.0f;
      break;
    default:
      NOTREACHED();
      tl_radius = 0.0f;
      tr_radius = 0.0f;
      bl_radius = 0.0f;
      br_radius = 0.0f;
  }

  // Background / Edge

  NSRect bounds = [self bounds];
  NSBezierPath *border = [NSBezierPath gtm_bezierPathWithRoundRect:bounds
                                               topLeftCornerRadius:tl_radius
                                              topRightCornerRadius:tr_radius
                                            bottomLeftCornerRadius:bl_radius
                                           bottomRightCornerRadius:br_radius];

  [[NSColor colorWithDeviceWhite:kWindowFill alpha:1.0f] set];
  [border fill];

  border = [NSBezierPath gtm_bezierPathWithRoundRect:bounds
                                 topLeftCornerRadius:tl_radius
                                topRightCornerRadius:tr_radius
                              bottomLeftCornerRadius:bl_radius
                             bottomRightCornerRadius:br_radius];

  [[NSColor colorWithDeviceWhite:kWindowEdge alpha:1.0f] set];
  [border stroke];

  // Text

  NSFont* textFont = [self font];
  NSShadow* textShadow = [[[NSShadow alloc] init] autorelease];
  [textShadow setShadowBlurRadius:1.5f];
  [textShadow setShadowColor:[NSColor whiteColor]];
  [textShadow setShadowOffset:NSMakeSize(0.0f, -1.0f)];

  NSDictionary* textDict = [NSDictionary dictionaryWithObjectsAndKeys:
    textFont, NSFontAttributeName,
    textShadow, NSShadowAttributeName,
    nil];
  [content_ drawAtPoint:NSMakePoint(kTextPositionX, kTextPositionY)
         withAttributes:textDict];
}

@end