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

#include "chrome/browser/cocoa/tab_window_controller.h"

@implementation TabView

- (id)initWithFrame:(NSRect)frame {
  self = [super initWithFrame:frame];
  if (self) {
    // TODO(alcor): register for theming, either here or the cell
    // [self gtm_registerForThemeNotifications];
  }
  return self;
}

- (void)dealloc {
  // [self gtm_unregisterForThemeNotifications];
  [super dealloc];
}

// Overridden so that mouse clicks come to this view (the parent of the
// hierarchy) first. We want to handle clicks and drags in this class and
// leave the background button for display purposes only.
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent {
  return YES;
}

// Determines which view a click in our frame actually hit. It's either this
// view or our child close button.
- (NSView *)hitTest:(NSPoint)aPoint {
  NSPoint viewPoint = [self convertPoint:aPoint fromView:[self superview]];
  if (NSPointInRect(viewPoint, [closeButton_ frame])) return closeButton_;
  if (NSPointInRect(aPoint, [self frame])) return self;
  return nil;
}

// Handle clicks and drags in this button. We get here because we have
// overridden acceptsFirstMouse: and the click is within our bounds.
// TODO(pinkerton/alcor): This routine needs *a lot* of work to marry Cole's
// ideas of dragging cocoa views between windows and how the Browser and
// TabStrip models want to manage tabs.
- (void)mouseDown:(NSEvent *)theEvent {
  // Make sure the controller doesn't go away while we're doing this.
  // TODO(pinkerton): cole had this, not sure why it's necessary.
  [[controller_ retain] autorelease];

  // Fire the action to select the tab.
  if ([[controller_ target] respondsToSelector:[controller_ action]])
    [[controller_ target] performSelector:[controller_ action]
                               withObject:self];

  // TODO(pinkerton): necessary to pre-arrange the tabs here?

  // Resolve overlay back to original window.
  NSWindow* sourceWindow = [self window];
  if ([sourceWindow isKindOfClass:[NSPanel class]]) {
    sourceWindow = [sourceWindow parentWindow];
  }
  TabWindowController* sourceController = [sourceWindow windowController];
  TabWindowController* draggedController = nil;
  TabWindowController* targetController = nil;

  // We don't want to "tear off" a tab if there's only one in the window. Treat
  // it like we're dragging around a tab we've already detached.
  BOOL isLastRemainingTab = [sourceController numberOfTabs] == 1;

  NSWindow* dragWindow = nil;
  NSWindow* dragOverlay = nil;
  BOOL dragging = YES;
  BOOL moved = NO;

  // Do not start dragging until the user has "torn" the tab off by
  // moving more than 3 pixels.
  BOOL torn = NO;
  static const double kDragStartDistance = 3.0;

  NSDate* targetDwellDate = nil; // The date this target was first chosen
  NSMutableArray* targets = [NSMutableArray array];

  NSPoint lastPoint =
      [[theEvent window] convertBaseToScreen:[theEvent locationInWindow]];

  while (dragging) {
    theEvent =
        [NSApp nextEventMatchingMask:NSLeftMouseUpMask | NSLeftMouseDraggedMask
                           untilDate:[NSDate distantFuture]
                              inMode:NSDefaultRunLoopMode dequeue:YES];
    NSPoint thisPoint = [NSEvent mouseLocation];

    // TODO(alcor): Pinkerton indicated that cole is adding a more
    // formal concept of "magnetism" to tab detachment, which would
    // be an alternative solution.
    if (!torn) {
      double dx = thisPoint.x - lastPoint.x;
      double dy = thisPoint.y - lastPoint.y;

      if (dx * dx + dy * dy < kDragStartDistance * kDragStartDistance
	  && [theEvent type] == NSLeftMouseDragged) {
	continue;
      }
      torn = YES;
    }

    // Find all the windows that could be a target. It has to be of the
    // appropriate class, and visible (obviously).
    if (![targets count]) {
      for (NSWindow* window in [NSApp windows]) {
        if (window == sourceWindow && isLastRemainingTab) continue;
        if (window == dragWindow) continue;
        if (![window isVisible]) continue;
        NSWindowController *controller = [window windowController];
        if ([controller isKindOfClass:[TabWindowController class]]) {
          [targets addObject:controller];
        }
      }
    }

    // Iterate over possible targets checking for the one the mouse is in.
    // The mouse can be in either the tab or window frame.
    TabWindowController* newTarget = nil;
    for (TabWindowController* target in targets) {
      NSRect windowFrame = [[target window] frame];
      if (NSPointInRect(thisPoint, windowFrame)) {
        if (NSPointInRect(thisPoint, [[target tabStripView] frame])) {
          newTarget = target;
        }
        break;
      }
    }

    // If we're now targeting a new window, re-layout the tabs in the old
    // target and reset how long we've been hovering over this new one.
    if (targetController != newTarget) {
      targetDwellDate = [NSDate date];
      [targetController arrangeTabs];
      targetController = newTarget;
    }

    NSEventType type = [theEvent type];
    if (type == NSLeftMouseDragged) {
      moved = YES;
      if (!draggedController) {
        if (isLastRemainingTab) {
          draggedController = sourceController;
          dragWindow = [draggedController window];
        } else {
          // Detach from the current window and put it in a new window.
          draggedController = [sourceController detachTabToNewWindow:self];
          dragWindow = [draggedController window];
          [dragWindow setAlphaValue:0.0];
        }

        // Bring the target window to the front and make sure it has a border.
        [dragWindow setLevel:NSFloatingWindowLevel];
        [dragWindow orderFront:nil];
        [dragWindow makeMainWindow];
        [draggedController showOverlay];
        dragOverlay = [draggedController overlayWindow];

        if (![targets count])
          [dragOverlay setHasShadow:NO];
      } else {
        NSPoint origin = [dragWindow frame].origin;
        origin.x += thisPoint.x - lastPoint.x;
        origin.y += thisPoint.y - lastPoint.y;
        [dragWindow setFrameOrigin:NSMakePoint(origin.x, origin.y)];
      }

      // If we're not hovering over any window, make the window is fully
      // opaque. Otherwise, find where the tab might be dropped and insert
      // a placeholder so it appears like it's part of that window.
      if (!targetController) {
        [[dragWindow animator] setAlphaValue:1.0];
      } else {
        if (![[targetController window] isKeyWindow]) {
          // && ([targetDwellDate timeIntervalSinceNow] < -REQUIRED_DWELL)) {
          [[targetController window] makeKeyAndOrderFront:nil];
          [targets removeAllObjects];
          targetDwellDate = nil;
        }

        // Compute where placeholder should go and insert it into the
        // destination tab strip.
        NSRect dropTabFrame = [[targetController tabStripView] frame];
        NSPoint point =
            [sourceWindow convertBaseToScreen:
                [self convertPointToBase:NSZeroPoint]];
        int x = NSWidth([self bounds]) / 2 + point.x - dropTabFrame.origin.x;
        [targetController insertPlaceholderForTab:self atLocation:x];
        [targetController arrangeTabs];

        if (!targetController)
          [dragWindow makeKeyAndOrderFront:nil];
        [[dragWindow animator] setAlphaValue:targetController ? 0.0 : 0.333];

        [[[draggedController overlayWindow] animator]
            setAlphaValue:targetController ? 0.85 : 1.0];
        // [setAlphaValue:targetController ? 0.0 : 0.6];
      }
    } else if (type == NSLeftMouseUp) {
      // Mouse up, break out of the drag event tracking loop
      dragging = NO;
    }
    lastPoint = thisPoint;
  }  // while tracking mouse

  // The drag/click is done. If the user dragged the mouse, finalize the drag
  // and clean up.
  if (moved) {
    TabWindowController *dropController = targetController;
    if (dropController) {
#if 0
// TODO(alcor/pinkerton): hookup drops on existing windows
      NSRect adjustedFrame = [self bounds];
      NSRect dropTabFrame =  [[dropController tabStripView] frame];
      adjustedFrame.origin = [self convertPointToBase:NSZeroPoint];
      adjustedFrame.origin =
          [sourceWindow convertBaseToScreen:adjustedFrame.origin];
      adjustedFrame.origin.x = adjustedFrame.origin.x - dropTabFrame.origin.x;
//    adjustedFrame.origin.y = adjustedFrame.origin.y - dropTabFrame.origin.y;
//    adjustedFrame.size.height += adjustedFrame.origin.y;
      adjustedFrame.origin.y = 0;

      // TODO(alcor): get add tab stuff working
      // [dropController addTab:tab_];

      [self setFrame:adjustedFrame];
      [dropController arrangeTabs];
      [draggedController close];
      [dropController showWindow:nil];
#endif
    } else {
      [[dragWindow animator] setAlphaValue:1.0];
      [dragOverlay setHasShadow:NO];
      [draggedController removeOverlayAfterDelay:
          [[NSAnimationContext currentContext] duration]];
      [dragWindow makeKeyAndOrderFront:nil];

      [[draggedController window] setLevel:NSNormalWindowLevel];
      [draggedController arrangeTabs];
    }
    [sourceController arrangeTabs];
  }
}

@end