// Copyright 2008, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "chrome/browser/tabs/dragged_tab_controller.h"

#include <math.h>

#include "chrome/browser/browser_window.h"
#include "chrome/browser/frame_util.h"
#include "chrome/browser/tab_contents.h"
#include "chrome/browser/tabs/dragged_tab_view.h"
#include "chrome/browser/tabs/hwnd_photobooth.h"
#include "chrome/browser/tabs/tab.h"
#include "chrome/browser/tabs/tab_strip.h"
#include "chrome/browser/web_contents.h"
#include "chrome/views/event.h"
#include "chrome/views/root_view.h"
#include "skia/include/SkBitmap.h"

static const int kHorizontalMoveThreshold = 16; // pixels

namespace {

///////////////////////////////////////////////////////////////////////////////
// WindowFinder
//  A WindowForPoint facility that can ignore 2 provided window HWNDs.
//
class WindowFinder {
 public:
  static HWND WindowForPoint(const gfx::Point& screen_point, HWND ignore1) {
    WindowFinder instance(screen_point, ignore1);
    return instance.GetResult();
  }
 private:
  WindowFinder(const gfx::Point& screen_point, HWND ignore1)
      : screen_point_(screen_point.ToPOINT()),
    ignore1_(ignore1),
    result_(NULL) {
  }

  static BOOL CALLBACK WindowEnumProc(HWND hwnd, LPARAM lParam) {
    WindowFinder* wf = reinterpret_cast<WindowFinder*>(lParam);
    if (hwnd == wf->ignore1_)
      return true;

    if (::IsWindowVisible(hwnd)) {
      CRect r;
      ::GetWindowRect(hwnd, &r);
      if (r.PtInRect(wf->screen_point_)) {
        // We always deal with the root HWND.
        wf->result_ = GetAncestor(hwnd, GA_ROOT);
        return FALSE;
      }
    }
    return TRUE;
  }

  HWND GetResult() {
    EnumThreadWindows(GetCurrentThreadId(), WindowEnumProc,
                      reinterpret_cast<LPARAM>(this));
    return result_;
  }

  POINT screen_point_;
  HWND ignore1_;
  HWND result_;

  DISALLOW_EVIL_CONSTRUCTORS(WindowFinder);
};

gfx::Point ConvertScreenPointToTabStripPoint(TabStrip* tabstrip,
                                             const gfx::Point& screen_point) {
  CPoint tabstrip_topleft(0, 0);
  ChromeViews::View::ConvertPointToScreen(tabstrip, &tabstrip_topleft);
  return gfx::Point(screen_point.x() - tabstrip_topleft.x,
                    screen_point.y() - tabstrip_topleft.y);
}

}

///////////////////////////////////////////////////////////////////////////////
// DraggedTabController, public:

DraggedTabController::DraggedTabController(Tab* source_tab,
                                           TabStrip* source_tabstrip)
    : dragged_contents_(NULL),
      original_delegate_(NULL),
      source_tab_(source_tab),
      source_tabstrip_(source_tabstrip),
      source_model_index_(source_tabstrip->GetIndexOfTab(source_tab)),
      attached_tabstrip_(source_tabstrip),
      old_focused_view_(NULL),
      in_destructor_(false),
      last_move_screen_x_(0) {
  ChangeDraggedContents(
      source_tabstrip_->model()->GetTabContentsAt(source_model_index_));
  // Listen for Esc key presses.
  MessageLoop::current()->AddObserver(this);
}

DraggedTabController::~DraggedTabController() {
  in_destructor_ = true;
  CleanUpSourceTab();
  MessageLoop::current()->RemoveObserver(this);
  ChangeDraggedContents(NULL); // This removes our observer.
}

void DraggedTabController::CaptureDragInfo(const gfx::Point& mouse_offset) {
  start_screen_point_ = GetCursorScreenPoint();
  mouse_offset_ = mouse_offset;
}

void DraggedTabController::Drag() {
  // Before we get to dragging anywhere, ensure that we consider ourselves
  // attached to the source tabstrip.
  if (source_tab_->IsVisible() && CanStartDrag())
    Attach(source_tabstrip_, gfx::Point());

  if (!source_tab_->IsVisible()) {
    SaveFocus();
    ContinueDragging();
  }
}

void DraggedTabController::EndDrag(bool canceled) {
  EndDragImpl(canceled ? CANCELED : NORMAL);
}

Tab* DraggedTabController::GetDragSourceTabForContents(
    TabContents* contents) const {
  if (attached_tabstrip_ == source_tabstrip_)
    return contents == dragged_contents_ ? source_tab_ : NULL;
  return NULL;
}

bool DraggedTabController::IsDragSourceTab(Tab* tab) const {
  return source_tab_ == tab;
}

///////////////////////////////////////////////////////////////////////////////
// DraggedTabController, PageNavigator implementation:

void DraggedTabController::OpenURLFromTab(
    TabContents* source,
    const GURL& url,
    WindowOpenDisposition disposition,
    PageTransition::Type transition,
    const std::wstring& override_encoding) {
  if (original_delegate_) {
    if (disposition == CURRENT_TAB)
      disposition = NEW_WINDOW;

    original_delegate_->OpenURLFromTab(source, url, disposition, transition,
                                       override_encoding);
  }
}

///////////////////////////////////////////////////////////////////////////////
// DraggedTabController, TabContentsDelegate implementation:

void DraggedTabController::NavigationStateChanged(const TabContents* source,
                                                  unsigned changed_flags) {
  if (view_.get())
    view_->Update();
}

void DraggedTabController::ReplaceContents(TabContents* source,
                                           TabContents* new_contents) {
  DCHECK(dragged_contents_ == source);
  source->set_delegate(NULL);
  new_contents->set_delegate(this);

  // If we're attached to a TabStrip, we need to tell the TabStrip that this
  // TabContents was replaced.
  if (attached_tabstrip_ && attached_tabstrip_->model() && dragged_contents_) {
    int index =
        attached_tabstrip_->model()->GetIndexOfTabContents(dragged_contents_);
    if (index != TabStripModel::kNoTab)
      attached_tabstrip_->model()->ReplaceTabContentsAt(index, new_contents);
  }

  // Update our internal state.
  ChangeDraggedContents(new_contents);

  if (view_.get())
    view_->Update();
}

void DraggedTabController::AddNewContents(TabContents* source,
                                          TabContents* new_contents,
                                          WindowOpenDisposition disposition,
                                          const gfx::Rect& initial_pos,
                                          bool user_gesture) {
  // Theoretically could be called while dragging if the page tries to
  // spawn a window. Route this message back to the browser in most cases.
  if (disposition == CURRENT_TAB) {
    ReplaceContents(source, new_contents);
  } else if (original_delegate_) {
    original_delegate_->AddNewContents(source, new_contents, disposition,
                                       initial_pos, user_gesture);
  }
}

void DraggedTabController::ActivateContents(TabContents* contents) {
  // Ignored.
}

void DraggedTabController::LoadingStateChanged(TabContents* source) {
  // It would be nice to respond to this message by changing the
  // screen shot in the dragged tab.
  if (view_.get())
    view_->Update();
}

void DraggedTabController::CloseContents(TabContents* source) {
  // Theoretically could be called by a window. Should be ignored
  // because window.close() is ignored (usually, even though this
  // method gets called.)
}

void DraggedTabController::MoveContents(TabContents* source,
                                        const gfx::Rect& pos) {
  // Theoretically could be called by a web page trying to move its
  // own window. Should be ignored since we're moving the window...
}

bool DraggedTabController::IsPopup(TabContents* source) {
  return false;
}

void DraggedTabController::ToolbarSizeChanged(TabContents* source,
                                            bool finished) {
  // Dragged tabs don't care about this.
}

void DraggedTabController::URLStarredChanged(TabContents* source,
                                             bool starred) {
  // Ignored.
}

void DraggedTabController::UpdateTargetURL(TabContents* source,
                                           const GURL& url) {
  // Ignored.
}

///////////////////////////////////////////////////////////////////////////////
// DraggedTabController, NotificationObserver implementation:

void DraggedTabController::Observe(NotificationType type,
                                   const NotificationSource& source,
                                   const NotificationDetails& details) {
  DCHECK(type == NOTIFY_TAB_CONTENTS_DESTROYED);
  DCHECK(Source<TabContents>(source).ptr() == dragged_contents_);
  EndDragImpl(TAB_DESTROYED);
}

///////////////////////////////////////////////////////////////////////////////
// DraggedTabController, MessageLoop::Observer implementation:

void DraggedTabController::WillProcessMessage(const MSG& msg) {
}

void DraggedTabController::DidProcessMessage(const MSG& msg) {
  // If the user presses ESC during a drag, we need to abort and revert things
  // to the way they were. This is the most reliable way to do this since no
  // single view or window reliably receives events throughout all the various
  // kinds of tab dragging.
  if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)
    EndDrag(true);
}

///////////////////////////////////////////////////////////////////////////////
// DraggedTabController, private:

void DraggedTabController::InitWindowCreatePoint() {
  CPoint mouse_offset_cpoint(mouse_offset_.x(), mouse_offset_.y());
  Tab* first_tab = attached_tabstrip_->GetTabAt(0);
  ChromeViews::View::ConvertPointToViewContainer(first_tab,
                                                 &mouse_offset_cpoint);
  window_create_point_.SetPoint(mouse_offset_cpoint.x, mouse_offset_cpoint.y);
}

gfx::Point DraggedTabController::GetWindowCreatePoint() const {
  POINT pt;
  GetCursorPos(&pt);
  return gfx::Point(pt.x - window_create_point_.x(),
                    pt.y - window_create_point_.y());
}

void DraggedTabController::ChangeDraggedContents(TabContents* new_contents) {
  if (dragged_contents_) {
    NotificationService::current()->RemoveObserver(this,
      NOTIFY_TAB_CONTENTS_DESTROYED,
      Source<TabContents>(dragged_contents_));
  }
  dragged_contents_ = new_contents;
  if (dragged_contents_) {
    NotificationService::current()->AddObserver(this,
      NOTIFY_TAB_CONTENTS_DESTROYED,
      Source<TabContents>(dragged_contents_));
  }
}

void DraggedTabController::SaveFocus() {
  if (!old_focused_view_) {
    old_focused_view_ = source_tab_->GetRootView()->GetFocusedView();
    source_tab_->GetRootView()->FocusView(source_tab_);
  }
}

void DraggedTabController::RestoreFocus() {
  if (old_focused_view_ && attached_tabstrip_ == source_tabstrip_)
    old_focused_view_->GetRootView()->FocusView(old_focused_view_);
  old_focused_view_ = NULL;
}

bool DraggedTabController::CanStartDrag() const {
  // Determine if the mouse has moved beyond a minimum elasticity distance in
  // any direction from the starting point.
  static const int kMinimumDragDistance = 10;
  gfx::Point screen_point = GetCursorScreenPoint();
  int x_offset = abs(screen_point.x() - start_screen_point_.x());
  int y_offset = abs(screen_point.y() - start_screen_point_.y());
  return sqrt(pow(static_cast<float>(x_offset), 2) +
              pow(static_cast<float>(y_offset), 2)) > kMinimumDragDistance;
}

void DraggedTabController::ContinueDragging() {
  EnsureDraggedView();

  // Note that the coordinates given to us by |drag_event| are basically
  // useless, since they're in source_tab_ coordinates. On the surface, you'd
  // think we could just convert them to screen coordinates, however in the
  // situation where we're dragging the last tab in a window when multiple
  // windows are open, the coordinates of |source_tab_| are way off in
  // hyperspace since the window was moved there instead of being closed so
  // that we'd keep receiving events. And our ConvertPointToScreen methods
  // aren't really multi-screen aware. So really it's just safer to get the
  // actual position of the mouse cursor directly from Windows here, which is
  // guaranteed to be correct regardless of monitor config.
  gfx::Point screen_point = GetCursorScreenPoint();

  // Determine whether or not we have dragged over a compatible TabStrip in
  // another browser window. If we have, we should attach to it and start
  // dragging within it.
  TabStrip* target_tabstrip = GetTabStripForPoint(screen_point);
  if (target_tabstrip != attached_tabstrip_) {
    if (target_tabstrip) {
      // We may receive this event before we're fully detached from the source,
      // we check for that and force a detach now.
      if (attached_tabstrip_)
        Detach();
      Attach(target_tabstrip, screen_point);
    } else {
      Detach();
    }
  }
  MoveTab(screen_point);
}

void DraggedTabController::MoveTab(const gfx::Point& screen_point) {
  gfx::Point dragged_view_point = GetDraggedViewPoint(screen_point);

  if (attached_tabstrip_) {
    // Determine the horizontal move threshold. This is dependent on the width
    // of tabs. The smaller the tabs compared to the standard size, the smaller
    // the threshold.
    double unselected, selected;
    attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected);
    double ratio = unselected / Tab::GetStandardSize().width();
    int threshold = static_cast<int>(ratio * kHorizontalMoveThreshold);

    // Update the model, moving the TabContents from one index to another. Do
    // this only if we have moved a minimum distance since the last reorder (to
    // prevent jitter).
    if (abs(screen_point.x() - last_move_screen_x_) > threshold) {
      TabStripModel* attached_model = attached_tabstrip_->model();
      int from_index =
          attached_model->GetIndexOfTabContents(dragged_contents_);
      gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point);
      int to_index = GetInsertionIndexForDraggedBounds(bounds);
      to_index = NormalizeIndexToAttachedTabStrip(to_index);
      if (from_index != to_index) {
        last_move_screen_x_ = screen_point.x();
        attached_model->MoveTabContentsAt(from_index, to_index);
      }
    }
  }
  // Move the View. There are no changes to the model if we're detached.
  view_->MoveTo(dragged_view_point);
}

TabStrip* DraggedTabController::GetTabStripForPoint(
    const gfx::Point& screen_point) const {
  HWND dragged_hwnd = view_->GetViewContainer()->GetHWND();
  HWND other_hwnd = WindowFinder::WindowForPoint(screen_point, dragged_hwnd);
  if (!other_hwnd)
    return NULL;

  BrowserWindow* other_frame = FrameUtil::GetBrowserWindowForHWND(other_hwnd);
  if (other_frame) {
    TabStrip* other_tabstrip = other_frame->GetTabStrip();
    if (!other_tabstrip->IsCompatibleWith(source_tabstrip_))
      return NULL;
    return GetTabStripIfItContains(other_tabstrip, screen_point);
  }
  return NULL;
}

TabStrip* DraggedTabController::GetTabStripIfItContains(
    TabStrip* tabstrip, const gfx::Point& screen_point) const {
  static const int kVerticalDetachMagnetism = 15;
  // Make sure the specified screen point is actually within the bounds of the
  // specified tabstrip...
  gfx::Rect tabstrip_bounds = GetViewScreenBounds(tabstrip);
  if (screen_point.x() < tabstrip_bounds.right() &&
      screen_point.x() >= tabstrip_bounds.x()) {
    // TODO(beng): make this be relative to the start position of the mouse for
    // the source TabStrip.
    int upper_threshold = tabstrip_bounds.bottom() + kVerticalDetachMagnetism;
    int lower_threshold = tabstrip_bounds.y() - kVerticalDetachMagnetism;
    if (screen_point.y() >= lower_threshold &&
        screen_point.y() <= upper_threshold) {
      return tabstrip;
    }
  }
  return NULL;
}

void DraggedTabController::Attach(TabStrip* attached_tabstrip,
                                  const gfx::Point& screen_point) {
  attached_tabstrip_ = attached_tabstrip;
  InitWindowCreatePoint();
  attached_tabstrip_->GenerateIdealBounds();

  // We don't need the photo-booth while we're attached.
  photobooth_.reset(NULL);

  Tab* tab = GetTabMatchingDraggedContents(attached_tabstrip_);

  // Update the View first, so we can ask it for its bounds and determine
  // where to insert the hidden Tab.

  // If this is the first time Attach is called for this drag, we're attaching
  // to the source TabStrip, and we should assume the tab count already
  // includes this Tab since we haven't been detached yet. If we don't do this,
  // the dragged representation will be a different size to others in the
  // TabStrip.
  int tab_count = attached_tabstrip_->GetTabCount();
  if (!tab)
    ++tab_count;
  double unselected_width, selected_width = 0;
  attached_tabstrip_->GetDesiredTabWidths(tab_count, &unselected_width,
                                          &selected_width);
  EnsureDraggedView();
  view_->Attach(static_cast<int>(selected_width));

  if (!tab) {
    // There is no Tab in |attached_tabstrip| that corresponds to the dragged
    // TabContents. We must now create one.

    // Remove ourselves as the delegate now that the dragged TabContents is
    // being inserted back into a Browser.
    dragged_contents_->set_delegate(NULL);
    original_delegate_ = NULL;

    // Return the TabContents' to normalcy.
    dragged_contents_->DidCaptureContents();

    // We need to ask the TabStrip we're attached to to ensure that the ideal
    // bounds for all its tabs are correctly generated, because the calculation
    // in GetInsertionIndexForDraggedBounds needs them to be to figure out the
    // appropriate insertion index.
    attached_tabstrip_->GenerateIdealBounds();

    // Inserting counts as a move. We don't want the tabs to jitter when the
    // user moves the tab immediately after attaching it.
    last_move_screen_x_ = screen_point.x();

    // Figure out where to insert the tab based on the bounds of the dragged
    // representation and the ideal bounds of the other Tabs already in the
    // strip. ("ideal bounds" are stable even if the Tabs' actual bounds are
    // changing due to animation).
    gfx::Rect bounds = GetDraggedViewTabStripBounds(screen_point);
    int index = GetInsertionIndexForDraggedBounds(bounds);
    index = std::max(std::min(index, attached_tabstrip_->model()->count()), 0);
    attached_tabstrip_->model()->InsertTabContentsAt(index, dragged_contents_,
        true, false);

    tab = GetTabMatchingDraggedContents(attached_tabstrip_);
  }
  DCHECK(tab); // We should now have a tab.
  tab->SetVisible(false);

  // Move the corresponding window to the front.
  attached_tabstrip_->GetViewContainer()->MoveToFront(true);
}

void DraggedTabController::Detach() {
  // Prevent the TabContents' HWND from being hidden by any of the model
  // operations performed during the drag.
  dragged_contents_->WillCaptureContents();

  // Update the Model.
  TabStripModel* attached_model = attached_tabstrip_->model();
  int index = attached_model->GetIndexOfTabContents(dragged_contents_);
  if (index >= 0 && index < attached_model->count()) {
    attached_model->DetachTabContentsAt(index);
    attached_tabstrip_->SchedulePaint();
  }

  // If we've removed the last Tab from the TabStrip, hide the frame now.
  if (attached_model->empty())
    HideFrame();

  // Set up the photo booth to start capturing the contents of the dragged
  // TabContents.
  if (!photobooth_.get())
    photobooth_.reset(new HWNDPhotobooth(dragged_contents_->GetContainerHWND()));

  // Update the View.
  view_->Detach(photobooth_.get());

  // We need to be the delegate so we receive messages about stuff,
  // otherwise our dragged_contents() may be replaced and subsequently
  // collected/destroyed while the drag is in process, leading to
  // nasty crashes.
  original_delegate_ = dragged_contents_->delegate();
  dragged_contents_->set_delegate(this);

  attached_tabstrip_ = NULL;
}

int DraggedTabController::GetInsertionIndexForDraggedBounds(
    const gfx::Rect& dragged_bounds) const {
  int right_tab_x = 0;

  // If the UI layout of the tab strip is right-to-left, we need to mirror the
  // bounds of the dragged tab before performing the drag/drop related
  // calculations. We mirror the dragged bounds because we determine the
  // position of each tab on the tab strip by calling GetBounds() (without the
  // mirroring transformation flag) which effectively means that even though
  // the tabs are rendered from right to left, the code performs the
  // calculation as if the tabs are laid out from left to right. Mirroring the
  // dragged bounds adjusts the coordinates of the tab we are dragging so that
  // it uses the same orientation used by the tabs on the tab strip.
  gfx::Rect adjusted_bounds(dragged_bounds);
  adjusted_bounds.set_x(
      attached_tabstrip_->MirroredLeftPointForRect(adjusted_bounds));

  for (int i = 0; i < attached_tabstrip_->GetTabCount(); ++i) {
    gfx::Rect ideal_bounds = attached_tabstrip_->GetIdealBounds(i);
    gfx::Rect left_half = ideal_bounds;
    left_half.set_width(left_half.width() / 2);
    gfx::Rect right_half = ideal_bounds;
    right_half.set_width(ideal_bounds.width() - left_half.width());
    right_half.set_x(left_half.right());
    right_tab_x = right_half.right();
    if (adjusted_bounds.x() >= right_half.x() &&
        adjusted_bounds.x() < right_half.right()) {
      return i + 1;
    } else if (adjusted_bounds.x() >= left_half.x() &&
               adjusted_bounds.x() < left_half.right()) {
      return i;
    }
  }
  if (adjusted_bounds.right() > right_tab_x)
    return attached_tabstrip_->model()->count();
  return TabStripModel::kNoTab;
}

gfx::Rect DraggedTabController::GetDraggedViewTabStripBounds(
    const gfx::Point& screen_point) {
  gfx::Point client_point =
      ConvertScreenPointToTabStripPoint(attached_tabstrip_, screen_point);
  gfx::Size view_size = view_->attached_tab_size();
  return gfx::Rect(client_point.x(), client_point.y(),
                   view_size.width(), view_size.height());
}

gfx::Point DraggedTabController::GetDraggedViewPoint(
    const gfx::Point& screen_point) {
  int x = screen_point.x() - mouse_offset_.x();
  int y = screen_point.y() - mouse_offset_.y();

  // If we're not attached, we just use x and y from above.
  if (attached_tabstrip_) {
    gfx::Rect tabstrip_bounds = GetViewScreenBounds(attached_tabstrip_);
    // Snap the dragged Tab to the TabStrip if we are attached, detaching
    // only when the mouse position (screen_point) exceeds the screen bounds
    // of the TabStrip.
    if (x < tabstrip_bounds.x() && screen_point.x() >= tabstrip_bounds.x())
      x = tabstrip_bounds.x();

    gfx::Size tab_size = view_->attached_tab_size();
    int vertical_drag_magnetism = tab_size.height() * 2;
    int vertical_detach_point = tabstrip_bounds.y() - vertical_drag_magnetism;
    if (y < tabstrip_bounds.y() && screen_point.y() >= vertical_detach_point)
      y = tabstrip_bounds.y();

    // Make sure the Tab can't be dragged off the right side of the TabStrip
    // unless the mouse pointer passes outside the bounds of the strip by
    // clamping the position of the dragged window to the tabstrip width less
    // the width of one tab until the mouse pointer (screen_point) exceeds the
    // screen bounds of the TabStrip.
    int max_x = tabstrip_bounds.right() - tab_size.width();
    int max_y = tabstrip_bounds.bottom() - tab_size.height();
    if (x > max_x && screen_point.x() <= tabstrip_bounds.right())
      x = max_x;
    if (y > max_y && screen_point.y() <=
        (tabstrip_bounds.bottom() + vertical_drag_magnetism)) {
      y = max_y;
    }
  }
  return gfx::Point(x, y);
}


Tab* DraggedTabController::GetTabMatchingDraggedContents(
    TabStrip* tabstrip) const {
  int index = tabstrip->model()->GetIndexOfTabContents(dragged_contents_);
  return index == TabStripModel::kNoTab ? NULL : tabstrip->GetTabAt(index);
}

void DraggedTabController::EndDragImpl(EndDragType type) {
  bool destroy_now = true;
  if (type != TAB_DESTROYED) {
    // We only finish up the drag if we were actually dragging. If we never
    // constructed a view, the user just clicked and released and didn't move the
    // mouse enough to trigger a drag.
    if (view_.get()) {
      RestoreFocus();
      if (type == CANCELED) {
        RevertDrag();
      } else {
        destroy_now = CompleteDrag();
      }
    }
  } else {
    // If we get here it means the NavigationController is going down. Don't
    // attempt to do any cleanup other than resetting the delegate (if we're
    // still the delegate).
    if (dragged_contents_ && dragged_contents_->delegate() == this)
      dragged_contents_->set_delegate(NULL);
    dragged_contents_ = NULL;
    attached_tabstrip_ = NULL;
  }
  // If we're not destroyed now, we'll be destroyed asynchronously later.
  if (destroy_now)
    source_tabstrip_->DestroyDragController();
}

void DraggedTabController::RevertDrag() {
  // We save this here because code below will modify |attached_tabstrip_|.
  bool restore_frame = attached_tabstrip_ != source_tabstrip_;
  if (attached_tabstrip_) {
    int index = attached_tabstrip_->model()->GetIndexOfTabContents(
        dragged_contents_);
    if (attached_tabstrip_ != source_tabstrip_) {
      // The Tab was inserted into another TabStrip. We need to put it back
      // into the original one.
      attached_tabstrip_->model()->DetachTabContentsAt(index);
      // TODO(beng): (Cleanup) seems like we should use Attach() for this
      //             somehow.
      attached_tabstrip_ = source_tabstrip_;
      source_tabstrip_->model()->InsertTabContentsAt(source_model_index_,
          dragged_contents_, true, false);
    } else {
      // The Tab was moved within the TabStrip where the drag was initiated.
      // Move it back to the starting location.
      source_tabstrip_->model()->MoveTabContentsAt(index, source_model_index_);
    }
  } else {
    // TODO(beng): (Cleanup) seems like we should use Attach() for this
    //             somehow.
    attached_tabstrip_ = source_tabstrip_;
    // The Tab was detached from the TabStrip where the drag began, and has not
    // been attached to any other TabStrip. We need to put it back into the
    // source TabStrip.
    source_tabstrip_->model()->InsertTabContentsAt(source_model_index_,
        dragged_contents_, true, false);
  }
  // If we're not attached to any TabStrip, or attached to some other TabStrip,
  // we need to restore the bounds of the original TabStrip's frame, in case
  // it has been hidden.
  if (restore_frame) {
    if (!restore_bounds_.IsEmpty()) {
      HWND frame_hwnd = source_tabstrip_->GetViewContainer()->GetHWND();
      MoveWindow(frame_hwnd, restore_bounds_.x(), restore_bounds_.y(),
                 restore_bounds_.width(), restore_bounds_.height(), TRUE);
    }
  }
  source_tab_->SetVisible(true);
}

bool DraggedTabController::CompleteDrag() {
  bool destroy_immediately = true;
  if (attached_tabstrip_) {
    // We don't need to do anything other than make the Tab visible again,
    // since the dragged View is going away.
    Tab* tab = GetTabMatchingDraggedContents(attached_tabstrip_);
    view_->AnimateToBounds(
        GetViewScreenBounds(tab),
        NewCallback(this, &DraggedTabController::OnAnimateToBoundsComplete));
    destroy_immediately = false;
  } else {
    // Compel the model to construct a new window for the detached TabContents.
    source_tabstrip_->model()->TearOffTabContents(
        dragged_contents_,
        GetWindowCreatePoint());
    CleanUpHiddenFrame();
  }

  return destroy_immediately;
}

void DraggedTabController::EnsureDraggedView() {
  if (!view_.get()) {
    RECT wr;
    GetWindowRect(dragged_contents_->GetContainerHWND(), &wr);

    view_.reset(new DraggedTabView(dragged_contents_, mouse_offset_,
        gfx::Size(wr.right - wr.left, wr.bottom - wr.top)));
  }
}

gfx::Point DraggedTabController::GetCursorScreenPoint() const {
  POINT pt;
  GetCursorPos(&pt);
  return gfx::Point(pt);
}

gfx::Rect DraggedTabController::GetViewScreenBounds(
    ChromeViews::View* view) const {
  CPoint view_topleft(0, 0);
  ChromeViews::View::ConvertPointToScreen(view, &view_topleft);
  CRect view_screen_bounds;
  view->GetLocalBounds(&view_screen_bounds, true);
  view_screen_bounds.OffsetRect(view_topleft);
  return gfx::Rect(view_screen_bounds);
}

int DraggedTabController::NormalizeIndexToAttachedTabStrip(int index) const {
  DCHECK(attached_tabstrip_) << "Can only be called when attached!";
  TabStripModel* attached_model = attached_tabstrip_->model();
  if (index >= attached_model->count())
    return attached_model->count() - 1;
  if (index == TabStripModel::kNoTab)
    return 0;
  return index;
}

void DraggedTabController::HideFrame() {
  // We don't actually hide the window, rather we just move it way off-screen.
  // If we actually hide it, we stop receiving drag events.
  HWND frame_hwnd = source_tabstrip_->GetViewContainer()->GetHWND();
  RECT wr;
  GetWindowRect(frame_hwnd, &wr);
  MoveWindow(frame_hwnd, 0xFFFF, 0xFFFF, wr.right - wr.left,
             wr.bottom - wr.top, TRUE);

  // We also save the bounds of the window prior to it being moved, so that if
  // the drag session is aborted we can restore them.
  restore_bounds_ = gfx::Rect(wr);
}

void DraggedTabController::CleanUpHiddenFrame() {
  // If the model we started dragging from is now empty, we must ask the
  // delegate to close the frame.
  if (source_tabstrip_->model()->empty())
    source_tabstrip_->model()->delegate()->CloseFrameAfterDragSession();
}

void DraggedTabController::CleanUpSourceTab() {
  // If we were attached to the source TabStrip, source Tab will be in use
  // as the Tab. If we were detached or attached to another TabStrip, we can
  // safely remove this item and delete it now.
  if (attached_tabstrip_ != source_tabstrip_) {
    source_tabstrip_->DestroyDraggedSourceTab(source_tab_);
    source_tab_ = NULL;
  }
}

void DraggedTabController::OnAnimateToBoundsComplete() {
  // Sometimes, for some reason, in automation we can be called back on a
  // detach even though we aren't attached to a TabStrip. Guard against that.
  if (attached_tabstrip_) {
    Tab* tab = GetTabMatchingDraggedContents(attached_tabstrip_);
    if (tab)
      tab->SetVisible(true);
  }
  CleanUpHiddenFrame();

  if (!in_destructor_)
    source_tabstrip_->DestroyDragController();
}