// 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/styled_text_field_cell.h"

#include "base/logging.h"
#include "chrome/browser/themes/theme_service.h"
#import "chrome/browser/ui/cocoa/nsview_additions.h"
#import "chrome/browser/ui/cocoa/themed_window.h"
#include "grit/theme_resources.h"
#import "third_party/GTM/AppKit/GTMNSBezierPath+RoundRect.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/font.h"

namespace {

NSBezierPath* RectPathWithInset(StyledTextFieldCellRoundedFlags roundedFlags,
                                const NSRect frame,
                                const CGFloat inset,
                                const CGFloat outerRadius) {
  NSRect insetFrame = NSInsetRect(frame, inset, inset);

  if (outerRadius > 0.0) {
    CGFloat leftRadius = outerRadius - inset;
    CGFloat rightRadius =
        (roundedFlags == StyledTextFieldCellRoundedLeft) ? 0 : leftRadius;

    return [NSBezierPath gtm_bezierPathWithRoundRect:insetFrame
                                 topLeftCornerRadius:leftRadius
                                topRightCornerRadius:rightRadius
                              bottomLeftCornerRadius:leftRadius
                             bottomRightCornerRadius:rightRadius];
  } else {
    return [NSBezierPath bezierPathWithRect:insetFrame];
  }
}

// Similar to |NSRectFill()|, additionally sets |color| as the fill
// color.  |outerRadius| greater than 0.0 uses rounded corners, with
// inset backed out of the radius.
void FillRectWithInset(StyledTextFieldCellRoundedFlags roundedFlags,
                       const NSRect frame,
                       const CGFloat inset,
                       const CGFloat outerRadius,
                       NSColor* color) {
  NSBezierPath* path =
      RectPathWithInset(roundedFlags, frame, inset, outerRadius);
  [color setFill];
  [path fill];
}

// Similar to |NSFrameRectWithWidth()|, additionally sets |color| as
// the stroke color (as opposed to the fill color).  |outerRadius|
// greater than 0.0 uses rounded corners, with inset backed out of the
// radius.
void FrameRectWithInset(StyledTextFieldCellRoundedFlags roundedFlags,
                        const NSRect frame,
                        const CGFloat inset,
                        const CGFloat outerRadius,
                        const CGFloat lineWidth,
                        NSColor* color) {
  const CGFloat finalInset = inset + (lineWidth / 2.0);
  NSBezierPath* path =
      RectPathWithInset(roundedFlags, frame, finalInset, outerRadius);
  [color setStroke];
  [path setLineWidth:lineWidth];
  [path stroke];
}

// TODO(shess): Maybe we need a |cocoa_util.h|?
class ScopedSaveGraphicsState {
 public:
  ScopedSaveGraphicsState()
      : context_([NSGraphicsContext currentContext]) {
    [context_ saveGraphicsState];
  }
  explicit ScopedSaveGraphicsState(NSGraphicsContext* context)
      : context_(context) {
    [context_ saveGraphicsState];
  }
  ~ScopedSaveGraphicsState() {
    [context_ restoreGraphicsState];
  }

private:
  NSGraphicsContext* context_;
};

}  // namespace

@implementation StyledTextFieldCell

- (CGFloat)baselineAdjust {
  return 0.0;
}

- (CGFloat)cornerRadius {
  return 0.0;
}

- (StyledTextFieldCellRoundedFlags)roundedFlags {
  return StyledTextFieldCellRoundedAll;
}

- (BOOL)shouldDrawBezel {
  return NO;
}

// Returns the same value as textCursorFrameForFrame, but does not call it
// directly to avoid potential infinite loops.
- (NSRect)textFrameForFrame:(NSRect)cellFrame {
  return NSInsetRect(cellFrame, 0, [self baselineAdjust]);
}

// Returns the same value as textFrameForFrame, but does not call it directly to
// avoid potential infinite loops.
- (NSRect)textCursorFrameForFrame:(NSRect)cellFrame {
  return NSInsetRect(cellFrame, 0, [self baselineAdjust]);
}

// Override to show the I-beam cursor only in the area given by
// |textCursorFrameForFrame:|.
- (void)resetCursorRect:(NSRect)cellFrame inView:(NSView *)controlView {
  [super resetCursorRect:[self textCursorFrameForFrame:cellFrame]
                  inView:controlView];
}

// For NSTextFieldCell this is the area within the borders.  For our
// purposes, we count the info decorations as being part of the
// border.
- (NSRect)drawingRectForBounds:(NSRect)theRect {
  return [super drawingRectForBounds:[self textFrameForFrame:theRect]];
}

// TODO(shess): This code is manually drawing the cell's border area,
// but otherwise the cell assumes -setBordered:YES for purposes of
// calculating things like the editing area.  This is probably
// incorrect.  I know that this affects -drawingRectForBounds:.
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
  const CGFloat lineWidth = [controlView cr_lineWidth];
  const CGFloat halfLineWidth = lineWidth / 2.0;

  DCHECK([controlView isFlipped]);
  StyledTextFieldCellRoundedFlags roundedFlags = [self roundedFlags];

  // TODO(shess): This inset is also reflected by |kFieldVisualInset|
  // in autocomplete_popup_view_mac.mm.
  const NSRect frame = NSInsetRect(cellFrame, 0, lineWidth);
  const CGFloat radius = [self cornerRadius];

  // Paint button background image if there is one (otherwise the border won't
  // look right).
  ThemeService* themeProvider =
      static_cast<ThemeService*>([[controlView window] themeProvider]);
  if (themeProvider) {
    NSColor* backgroundImageColor =
        themeProvider->GetNSImageColorNamed(IDR_THEME_BUTTON_BACKGROUND, false);
    if (backgroundImageColor) {
      // Set the phase to match window.
      NSRect trueRect = [controlView convertRect:cellFrame toView:nil];
      NSPoint midPoint = NSMakePoint(NSMinX(trueRect), NSMaxY(trueRect));
      [[NSGraphicsContext currentContext] setPatternPhase:midPoint];

      // NOTE(shess): This seems like it should be using a 0.0 inset,
      // but AFAICT using a halfLineWidth inset is important in mixing the
      // toolbar background and the omnibox background.
      FillRectWithInset(roundedFlags, frame, halfLineWidth, radius,
                        backgroundImageColor);
    }

    // Draw the outer stroke (over the background).
    BOOL active = [[controlView window] isMainWindow];
    NSColor* strokeColor = themeProvider->GetNSColor(
        active ? ThemeService::COLOR_TOOLBAR_BUTTON_STROKE :
                 ThemeService::COLOR_TOOLBAR_BUTTON_STROKE_INACTIVE,
        true);
    FrameRectWithInset(roundedFlags, frame, 0.0, radius, lineWidth,
                       strokeColor);
  }

  // Fill interior with background color.
  FillRectWithInset(roundedFlags, frame, lineWidth, radius,
                    [self backgroundColor]);

  // Draw the shadow.  For the rounded-rect case, the shadow needs to
  // slightly turn in at the corners.  |shadowFrame| is at the same
  // midline as the inner border line on the top and left, but at the
  // outer border line on the bottom and right.  The clipping change
  // will clip the bottom and right edges (and corner).
  {
    ScopedSaveGraphicsState state;
    [RectPathWithInset(roundedFlags, frame, lineWidth, radius) addClip];
    const NSRect shadowFrame =
        NSOffsetRect(frame, halfLineWidth, halfLineWidth);
    NSColor* shadowShade = [NSColor colorWithCalibratedWhite:0.0 alpha:0.05];
    FrameRectWithInset(roundedFlags, shadowFrame, halfLineWidth,
                       radius - halfLineWidth, lineWidth, shadowShade);
  }

  // Draw optional bezel below bottom stroke.
  if ([self shouldDrawBezel] && themeProvider &&
      themeProvider->UsingDefaultTheme()) {

    NSColor* bezelColor = themeProvider->GetNSColor(
        ThemeService::COLOR_TOOLBAR_BEZEL, true);
    [[bezelColor colorWithAlphaComponent:0.5] set];
    NSRect bezelRect = NSMakeRect(cellFrame.origin.x,
                                  NSMaxY(cellFrame) - lineWidth,
                                  NSWidth(cellFrame),
                                  lineWidth);
    bezelRect = NSInsetRect(bezelRect, radius - halfLineWidth, 0.0);
    NSRectFillUsingOperation(bezelRect, NSCompositeSourceOver);
  }

  // Draw the focus ring if needed.
  if ([self showsFirstResponder]) {
    NSColor* color =
        [[NSColor keyboardFocusIndicatorColor] colorWithAlphaComponent:0.5];
    FrameRectWithInset(roundedFlags, frame, 0.0, radius, lineWidth * 2, color);
  }

  [self drawInteriorWithFrame:cellFrame inView:controlView];
}

@end