// Copyright (c) 2013 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 "content/browser/web_contents/touch_editable_impl_aura.h"

#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_aura.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/view_messages.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/touch/selection_bound.h"
#include "ui/base/ui_base_switches_util.h"
#include "ui/gfx/range/range.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/wm/public/activation_client.h"

namespace content {

////////////////////////////////////////////////////////////////////////////////
// TouchEditableImplAura, public:

TouchEditableImplAura::~TouchEditableImplAura() {
  Cleanup();
}

// static
TouchEditableImplAura* TouchEditableImplAura::Create() {
  if (switches::IsTouchEditingEnabled())
    return new TouchEditableImplAura();
  return NULL;
}

void TouchEditableImplAura::AttachToView(RenderWidgetHostViewAura* view) {
  if (rwhva_ == view)
    return;

  Cleanup();
  if (!view)
    return;

  rwhva_ = view;
  rwhva_->set_touch_editing_client(this);
}

void TouchEditableImplAura::UpdateEditingController() {
  if (!rwhva_ || !rwhva_->HasFocus())
    return;

  if (text_input_type_ != ui::TEXT_INPUT_TYPE_NONE ||
      selection_anchor_ != selection_focus_) {
    if (touch_selection_controller_)
      touch_selection_controller_->SelectionChanged();
  } else {
    EndTouchEditing(false);
  }
}

void TouchEditableImplAura::OverscrollStarted() {
  overscroll_in_progress_ = true;
}

void TouchEditableImplAura::OverscrollCompleted() {
  overscroll_in_progress_ = false;
  StartTouchEditingIfNecessary();
}

////////////////////////////////////////////////////////////////////////////////
// TouchEditableImplAura, RenderWidgetHostViewAura::TouchEditingClient
// implementation:

void TouchEditableImplAura::StartTouchEditing() {
  if (!rwhva_ || !rwhva_->HasFocus())
    return;

  if (!touch_selection_controller_) {
    touch_selection_controller_.reset(
        ui::TouchEditingControllerDeprecated::Create(this));
  }
  if (touch_selection_controller_)
    touch_selection_controller_->SelectionChanged();
}

void TouchEditableImplAura::EndTouchEditing(bool quick) {
  if (touch_selection_controller_) {
    if (touch_selection_controller_->IsHandleDragInProgress()) {
      touch_selection_controller_->SelectionChanged();
    } else {
      selection_gesture_in_process_ = false;
      touch_selection_controller_->HideHandles(quick);
      touch_selection_controller_.reset();
    }
  }
}

void TouchEditableImplAura::OnSelectionOrCursorChanged(
    const ui::SelectionBound& anchor,
    const ui::SelectionBound& focus) {
  selection_anchor_ = anchor;
  selection_focus_ = focus;

  // If touch editing handles were not visible, we bring them up only if the
  // current event is a gesture event, no scroll/fling/overscoll is in progress,
  // and there is non-zero selection on the page
  if (selection_gesture_in_process_ && !scroll_in_progress_ &&
      !overscroll_in_progress_ && selection_anchor_ != selection_focus_) {
    StartTouchEditing();
    selection_gesture_in_process_ = false;
  }

  UpdateEditingController();
}

void TouchEditableImplAura::OnTextInputTypeChanged(ui::TextInputType type) {
  text_input_type_ = type;
}

bool TouchEditableImplAura::HandleInputEvent(const ui::Event* event) {
  DCHECK(rwhva_);
  if (!event->IsGestureEvent()) {
    // Ignore all non-gesture events. Non-gesture events that can deactivate
    // touch editing are handled in TouchSelectionControllerImpl.
    return false;
  }

  const ui::GestureEvent* gesture_event =
      static_cast<const ui::GestureEvent*>(event);
  switch (event->type()) {
    case ui::ET_GESTURE_TAP:
      // When the user taps, we want to show touch editing handles if user
      // tapped on selected text.
      if (gesture_event->details().tap_count() == 1 &&
          selection_anchor_ != selection_focus_) {
        gfx::Rect selection_rect =
            ui::RectBetweenSelectionBounds(selection_anchor_, selection_focus_);
        // When tap is on selection, show handles and mark event as handled only
        // if handles are not present or text is not editable. Otherwise, do not
        // set event as handles so that event is forwarded to the renderer to
        // update selection/cursor.
        if (selection_rect.Contains(gesture_event->location()) &&
            (text_input_type_ == ui::TEXT_INPUT_TYPE_NONE ||
                !touch_selection_controller_)) {
          StartTouchEditing();
          return true;
        }
      }
      // For single taps, not inside selected region, we want to show handles
      // only when the tap is on an already focused textfield.
      textfield_was_focused_on_tap_ =
          gesture_event->details().tap_count() == 1 &&
          text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
      break;
    case ui::ET_GESTURE_LONG_PRESS:
      selection_gesture_in_process_ = true;
      break;
    case ui::ET_GESTURE_SCROLL_BEGIN:
      scroll_in_progress_ = true;;
      // We need to hide selection handles during scroll (including fling and
      // overscroll), but they should be re-activated after scrolling if:
      //  - an existing scroll decided that handles should be shown after
      //    scrolling; or
      //  - the gesture in progress is going to end in selection; or
      //  - selection handles are currently active.
      handles_hidden_due_to_scroll_ = handles_hidden_due_to_scroll_ ||
                                      selection_gesture_in_process_ ||
                                      touch_selection_controller_ != NULL;
      selection_gesture_in_process_ = false;
      EndTouchEditing(true);
      break;
    case ui::ET_GESTURE_SCROLL_END:
      scroll_in_progress_ = false;
      StartTouchEditingIfNecessary();
      break;
    default:
      break;
  }
  return false;
}

void TouchEditableImplAura::GestureEventAck(int gesture_event_type) {
  DCHECK(rwhva_);
  if (gesture_event_type == blink::WebInputEvent::GestureTap &&
      text_input_type_ != ui::TEXT_INPUT_TYPE_NONE &&
      textfield_was_focused_on_tap_) {
    StartTouchEditing();
    UpdateEditingController();
  }
}

void TouchEditableImplAura::DidStopFlinging() {
  scroll_in_progress_ = false;
  StartTouchEditingIfNecessary();
}

void TouchEditableImplAura::OnViewDestroyed() {
  Cleanup();
}

////////////////////////////////////////////////////////////////////////////////
// TouchEditableImplAura, ui::TouchEditable implementation:

void TouchEditableImplAura::SelectRect(const gfx::Point& start,
                                       const gfx::Point& end) {
  RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
  RenderViewHost* rvh = RenderViewHost::From(host);
  WebContentsImpl* wc =
      static_cast<WebContentsImpl*>(WebContents::FromRenderViewHost(rvh));
  wc->SelectRange(start, end);
}

void TouchEditableImplAura::MoveCaretTo(const gfx::Point& point) {
  if (!rwhva_)
    return;

  RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
      rwhva_->GetRenderWidgetHost());
  host->MoveCaret(point);
}

void TouchEditableImplAura::GetSelectionEndPoints(ui::SelectionBound* anchor,
                                                  ui::SelectionBound* focus) {
  *anchor = selection_anchor_;
  *focus = selection_focus_;
}

gfx::Rect TouchEditableImplAura::GetBounds() {
  return rwhva_ ? gfx::Rect(rwhva_->GetNativeView()->bounds().size()) :
      gfx::Rect();
}

gfx::NativeView TouchEditableImplAura::GetNativeView() const {
  return rwhva_ ? rwhva_->GetNativeView()->GetToplevelWindow() : NULL;
}

void TouchEditableImplAura::ConvertPointToScreen(gfx::Point* point) {
  if (!rwhva_)
    return;
  aura::Window* window = rwhva_->GetNativeView();
  aura::client::ScreenPositionClient* screen_position_client =
      aura::client::GetScreenPositionClient(window->GetRootWindow());
  if (screen_position_client)
    screen_position_client->ConvertPointToScreen(window, point);
}

void TouchEditableImplAura::ConvertPointFromScreen(gfx::Point* point) {
  if (!rwhva_)
    return;
  aura::Window* window = rwhva_->GetNativeView();
  aura::client::ScreenPositionClient* screen_position_client =
      aura::client::GetScreenPositionClient(window->GetRootWindow());
  if (screen_position_client)
    screen_position_client->ConvertPointFromScreen(window, point);
}

bool TouchEditableImplAura::DrawsHandles() {
  return false;
}

void TouchEditableImplAura::OpenContextMenu(const gfx::Point& anchor) {
  if (!rwhva_)
    return;
  gfx::Point point = anchor;
  ConvertPointFromScreen(&point);
  RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
  host->Send(new ViewMsg_ShowContextMenu(
      host->GetRoutingID(), ui::MENU_SOURCE_TOUCH_EDIT_MENU, point));
  EndTouchEditing(false);
}

bool TouchEditableImplAura::IsCommandIdChecked(int command_id) const {
  NOTREACHED();
  return false;
}

bool TouchEditableImplAura::IsCommandIdEnabled(int command_id) const {
  if (!rwhva_)
    return false;
  bool editable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE;
  bool readable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_PASSWORD;
  gfx::Range selection_range;
  rwhva_->GetSelectionRange(&selection_range);
  bool has_selection = !selection_range.is_empty();
  switch (command_id) {
    case IDS_APP_CUT:
      return editable && readable && has_selection;
    case IDS_APP_COPY:
      return readable && has_selection;
    case IDS_APP_PASTE: {
      base::string16 result;
      ui::Clipboard::GetForCurrentThread()->ReadText(
          ui::CLIPBOARD_TYPE_COPY_PASTE, &result);
      return editable && !result.empty();
    }
    case IDS_APP_DELETE:
      return editable && has_selection;
    case IDS_APP_SELECT_ALL:
      return true;
    default:
      return false;
  }
}

bool TouchEditableImplAura::GetAcceleratorForCommandId(
    int command_id,
    ui::Accelerator* accelerator) {
  return false;
}

void TouchEditableImplAura::ExecuteCommand(int command_id, int event_flags) {
  RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
  RenderViewHost* rvh = RenderViewHost::From(host);
  WebContents* wc = WebContents::FromRenderViewHost(rvh);

  switch (command_id) {
    case IDS_APP_CUT:
      wc->Cut();
      break;
    case IDS_APP_COPY:
      wc->Copy();
      break;
    case IDS_APP_PASTE:
      wc->Paste();
      break;
    case IDS_APP_DELETE:
      wc->Delete();
      break;
    case IDS_APP_SELECT_ALL:
      wc->SelectAll();
      break;
    default:
      NOTREACHED();
      break;
  }
  EndTouchEditing(false);
}

void TouchEditableImplAura::DestroyTouchSelection() {
  EndTouchEditing(false);
}

////////////////////////////////////////////////////////////////////////////////
// TouchEditableImplAura, private:

TouchEditableImplAura::TouchEditableImplAura()
    : text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
      rwhva_(NULL),
      selection_gesture_in_process_(false),
      handles_hidden_due_to_scroll_(false),
      scroll_in_progress_(false),
      overscroll_in_progress_(false),
      textfield_was_focused_on_tap_(false) {
}

void TouchEditableImplAura::StartTouchEditingIfNecessary() {
  // If there is no scrolling left in progress, show selection handles if they
  // were hidden due to scroll and there is a selection.
  if (!scroll_in_progress_ && !overscroll_in_progress_ &&
      handles_hidden_due_to_scroll_ &&
      (selection_anchor_ != selection_focus_ ||
          text_input_type_ != ui::TEXT_INPUT_TYPE_NONE)) {
    StartTouchEditing();
    UpdateEditingController();
    handles_hidden_due_to_scroll_ = false;
  }
}

void TouchEditableImplAura::Cleanup() {
  if (rwhva_) {
    rwhva_->set_touch_editing_client(NULL);
    rwhva_ = NULL;
  }
  text_input_type_ = ui::TEXT_INPUT_TYPE_NONE;
  EndTouchEditing(true);
  selection_gesture_in_process_ = false;
  handles_hidden_due_to_scroll_ = false;
  scroll_in_progress_ = false;
  overscroll_in_progress_ = false;
}

}  // namespace content
