// Copyright 2014 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 "ui/chromeos/touch_exploration_controller.h"

#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event.h"
#include "ui/events/event_processor.h"

#define VLOG_STATE() if (VLOG_IS_ON(0)) VlogState(__func__)
#define VLOG_EVENT(event) if (VLOG_IS_ON(0)) VlogEvent(event, __func__)

namespace ui {

namespace {
// In ChromeOS, VKEY_LWIN is synonymous for the search key.
const ui::KeyboardCode kChromeOSSearchKey = ui::VKEY_LWIN;
}  // namespace

TouchExplorationController::TouchExplorationController(
    aura::Window* root_window)
    : root_window_(root_window),
      state_(NO_FINGERS_DOWN),
      event_handler_for_testing_(NULL),
      gesture_provider_(this),
      prev_state_(NO_FINGERS_DOWN),
      VLOG_on_(true) {
  CHECK(root_window);
  root_window->GetHost()->GetEventSource()->AddEventRewriter(this);
}


TouchExplorationController::~TouchExplorationController() {
  root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this);
}

void TouchExplorationController::CallTapTimerNowForTesting() {
  DCHECK(tap_timer_.IsRunning());
  tap_timer_.Stop();
  OnTapTimerFired();
}

void TouchExplorationController::CallTapTimerNowIfRunningForTesting() {
  if (tap_timer_.IsRunning()) {
    tap_timer_.Stop();
    OnTapTimerFired();
  }
}

void TouchExplorationController::SetEventHandlerForTesting(
    ui::EventHandler* event_handler_for_testing) {
  event_handler_for_testing_ = event_handler_for_testing;
}

bool TouchExplorationController::IsInNoFingersDownStateForTesting() const {
  return state_ == NO_FINGERS_DOWN;
}

bool TouchExplorationController::IsInGestureInProgressStateForTesting() const {
  return state_ == GESTURE_IN_PROGRESS;
}

void TouchExplorationController::SuppressVLOGsForTesting(bool suppress) {
    VLOG_on_ = !suppress;
}

ui::EventRewriteStatus TouchExplorationController::RewriteEvent(
    const ui::Event& event,
    scoped_ptr<ui::Event>* rewritten_event) {
  if (!event.IsTouchEvent()) {
    if (event.IsKeyEvent()) {
      const ui::KeyEvent& key_event = static_cast<const ui::KeyEvent&>(event);
      VLOG(0) << "\nKeyboard event: " << key_event.name()
              << "\n Key code: " << key_event.key_code()
              << ", Flags: " << key_event.flags()
              << ", Is char: " << key_event.is_char();
    }
    return ui::EVENT_REWRITE_CONTINUE;
  }
  const ui::TouchEvent& touch_event = static_cast<const ui::TouchEvent&>(event);

  // If the tap timer should have fired by now but hasn't, run it now and
  // stop the timer. This is important so that behavior is consistent with
  // the timestamps of the events, and not dependent on the granularity of
  // the timer.
  if (tap_timer_.IsRunning() &&
      touch_event.time_stamp() - initial_press_->time_stamp() >
          gesture_detector_config_.double_tap_timeout) {
    tap_timer_.Stop();
    OnTapTimerFired();
    // Note: this may change the state. We should now continue and process
    // this event under this new state.
  }

  const ui::EventType type = touch_event.type();
  const gfx::PointF& location = touch_event.location_f();
  const int touch_id = touch_event.touch_id();

  // Always update touch ids and touch locations, so we can use those
  // no matter what state we're in.
  if (type == ui::ET_TOUCH_PRESSED) {
    current_touch_ids_.push_back(touch_id);
    touch_locations_.insert(std::pair<int, gfx::PointF>(touch_id, location));
  } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
    std::vector<int>::iterator it = std::find(
        current_touch_ids_.begin(), current_touch_ids_.end(), touch_id);

    // Can happen if touch exploration is enabled while fingers were down.
    if (it == current_touch_ids_.end())
      return ui::EVENT_REWRITE_CONTINUE;

    current_touch_ids_.erase(it);
    touch_locations_.erase(touch_id);
  } else if (type == ui::ET_TOUCH_MOVED) {
    std::vector<int>::iterator it = std::find(
        current_touch_ids_.begin(), current_touch_ids_.end(), touch_id);

    // Can happen if touch exploration is enabled while fingers were down.
    if (it == current_touch_ids_.end())
      return ui::EVENT_REWRITE_CONTINUE;

    touch_locations_[*it] = location;
  }
  VLOG_STATE();
  VLOG_EVENT(touch_event);
  // The rest of the processing depends on what state we're in.
  switch(state_) {
    case NO_FINGERS_DOWN:
      return InNoFingersDown(touch_event, rewritten_event);
    case SINGLE_TAP_PRESSED:
      return InSingleTapPressed(touch_event, rewritten_event);
    case SINGLE_TAP_RELEASED:
    case TOUCH_EXPLORE_RELEASED:
      return InSingleTapOrTouchExploreReleased(touch_event, rewritten_event);
    case DOUBLE_TAP_PRESSED:
      return InDoubleTapPressed(touch_event, rewritten_event);
    case TOUCH_EXPLORATION:
      return InTouchExploration(touch_event, rewritten_event);
    case GESTURE_IN_PROGRESS:
      return InGestureInProgress(touch_event, rewritten_event);
    case TOUCH_EXPLORE_SECOND_PRESS:
      return InTouchExploreSecondPress(touch_event, rewritten_event);
    case TWO_TO_ONE_FINGER:
      return InTwoToOneFinger(touch_event, rewritten_event);
    case PASSTHROUGH:
      return InPassthrough(touch_event, rewritten_event);
    case WAIT_FOR_RELEASE:
      return InWaitForRelease(touch_event, rewritten_event);
  }
  NOTREACHED();
  return ui::EVENT_REWRITE_CONTINUE;
}

ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent(
    const ui::Event& last_event, scoped_ptr<ui::Event>* new_event) {
  NOTREACHED();
  return ui::EVENT_REWRITE_CONTINUE;
}

ui::EventRewriteStatus TouchExplorationController::InNoFingersDown(
    const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
  const ui::EventType type = event.type();
  if (type == ui::ET_TOUCH_PRESSED) {
    initial_press_.reset(new TouchEvent(event));
    last_unused_finger_event_.reset(new TouchEvent(event));
    tap_timer_.Start(FROM_HERE,
                     gesture_detector_config_.double_tap_timeout,
                     this,
                     &TouchExplorationController::OnTapTimerFired);
    gesture_provider_.OnTouchEvent(event);
    gesture_provider_.OnTouchEventAck(false);
    ProcessGestureEvents();
    state_ = SINGLE_TAP_PRESSED;
    VLOG_STATE();
    return ui::EVENT_REWRITE_DISCARD;
  }
  NOTREACHED() << "Unexpected event type received: " << event.name();;
  return ui::EVENT_REWRITE_CONTINUE;
}

ui::EventRewriteStatus TouchExplorationController::InSingleTapPressed(
    const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
  const ui::EventType type = event.type();

  if (type == ui::ET_TOUCH_PRESSED) {
    // Adding a second finger within the timeout period switches to
    // passing through every event from the second finger and none form the
    // first. The event from the first finger is still saved in initial_press_.
    state_ = TWO_TO_ONE_FINGER;
    last_two_to_one_.reset(new TouchEvent(event));
    rewritten_event->reset(new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
                                              event.location(),
                                              event.touch_id(),
                                              event.time_stamp()));
    (*rewritten_event)->set_flags(event.flags());
    return EVENT_REWRITE_REWRITTEN;
  } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
    DCHECK_EQ(0U, current_touch_ids_.size());
    state_ = SINGLE_TAP_RELEASED;
    VLOG_STATE();
    return EVENT_REWRITE_DISCARD;
  } else if (type == ui::ET_TOUCH_MOVED) {
    float distance = (event.location() - initial_press_->location()).Length();
    // If the user does not move far enough from the original position, then the
    // resulting movement should not be considered to be a deliberate gesture or
    // touch exploration.
    if (distance <= gesture_detector_config_.touch_slop)
      return EVENT_REWRITE_DISCARD;

    float delta_time =
        (event.time_stamp() - initial_press_->time_stamp()).InSecondsF();
    float velocity = distance / delta_time;
    VLOG(0) << "\n Delta time: " << delta_time
            << "\n Distance: " << distance
            << "\n Velocity of click: " << velocity
            << "\n Minimum swipe velocity: "
            << gesture_detector_config_.minimum_swipe_velocity;

    // If the user moves fast enough from the initial touch location, start
    // gesture detection. Otherwise, jump to the touch exploration mode early.
    if (velocity > gesture_detector_config_.minimum_swipe_velocity) {
      state_ = GESTURE_IN_PROGRESS;
      VLOG_STATE();
      return InGestureInProgress(event, rewritten_event);
    }
    EnterTouchToMouseMode();
    state_ = TOUCH_EXPLORATION;
    VLOG_STATE();
    return InTouchExploration(event, rewritten_event);
  }
  NOTREACHED() << "Unexpected event type received: " << event.name();;
  return ui::EVENT_REWRITE_CONTINUE;
}

ui::EventRewriteStatus
TouchExplorationController::InSingleTapOrTouchExploreReleased(
    const ui::TouchEvent& event,
    scoped_ptr<ui::Event>* rewritten_event) {
  const ui::EventType type = event.type();
  // If there is more than one finger down, then discard to wait until only one
  // finger is or no fingers are down.
  if (current_touch_ids_.size() > 1) {
    state_ = WAIT_FOR_RELEASE;
    return ui::EVENT_REWRITE_DISCARD;
  }
  // If there is no touch exploration yet, discard.
  if (!last_touch_exploration_ || type == ui::ET_TOUCH_RELEASED) {
    if (current_touch_ids_.size() == 0) {
      ResetToNoFingersDown();
    }
    return ui::EVENT_REWRITE_DISCARD;
  }

  if (type == ui::ET_TOUCH_PRESSED) {
    // This is the second tap in a double-tap (or double tap-hold).
    // Rewrite at location of last touch exploration.
    rewritten_event->reset(
        new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
                           last_touch_exploration_->location(),
                           event.touch_id(),
                           event.time_stamp()));
    (*rewritten_event)->set_flags(event.flags());
    state_ = DOUBLE_TAP_PRESSED;
    VLOG_STATE();
    return ui::EVENT_REWRITE_REWRITTEN;
  } else if (type == ui::ET_TOUCH_RELEASED && !last_touch_exploration_) {
    // If the previous press was discarded, we need to also handle its
    // release.
    if (current_touch_ids_.size() == 0) {
      ResetToNoFingersDown();
    }
    return ui::EVENT_REWRITE_DISCARD;
  } else if (type == ui::ET_TOUCH_MOVED){
    return ui::EVENT_REWRITE_DISCARD;
  }
  NOTREACHED() << "Unexpected event type received: " << event.name();
  return ui::EVENT_REWRITE_CONTINUE;
}

ui::EventRewriteStatus TouchExplorationController::InDoubleTapPressed(
    const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
  const ui::EventType type = event.type();
  if (type == ui::ET_TOUCH_PRESSED) {
    return ui::EVENT_REWRITE_DISCARD;
  } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
    if (current_touch_ids_.size() != 0)
      return EVENT_REWRITE_DISCARD;

    // Rewrite release at location of last touch exploration with the same
    // id as the previous press.
    rewritten_event->reset(
        new ui::TouchEvent(ui::ET_TOUCH_RELEASED,
                           last_touch_exploration_->location(),
                           initial_press_->touch_id(),
                           event.time_stamp()));
    (*rewritten_event)->set_flags(event.flags());
    ResetToNoFingersDown();
    return ui::EVENT_REWRITE_REWRITTEN;
  } else if (type == ui::ET_TOUCH_MOVED) {
    return ui::EVENT_REWRITE_DISCARD;
  }
  NOTREACHED() << "Unexpected event type received: " << event.name();
  return ui::EVENT_REWRITE_CONTINUE;
}

ui::EventRewriteStatus TouchExplorationController::InTouchExploration(
    const ui::TouchEvent& event,
    scoped_ptr<ui::Event>* rewritten_event) {
  const ui::EventType type = event.type();
  if (type == ui::ET_TOUCH_PRESSED) {
    // Handle split-tap.
    initial_press_.reset(new TouchEvent(event));
    if (tap_timer_.IsRunning())
      tap_timer_.Stop();
    rewritten_event->reset(
        new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
                           last_touch_exploration_->location(),
                           event.touch_id(),
                           event.time_stamp()));
    (*rewritten_event)->set_flags(event.flags());
    state_ = TOUCH_EXPLORE_SECOND_PRESS;
    VLOG_STATE();
    return ui::EVENT_REWRITE_REWRITTEN;
  } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
    initial_press_.reset(new TouchEvent(event));
    tap_timer_.Start(FROM_HERE,
                     gesture_detector_config_.double_tap_timeout,
                     this,
                     &TouchExplorationController::OnTapTimerFired);
    state_ = TOUCH_EXPLORE_RELEASED;
    VLOG_STATE();
  } else if (type != ui::ET_TOUCH_MOVED) {
    NOTREACHED() << "Unexpected event type received: " << event.name();
    return ui::EVENT_REWRITE_CONTINUE;
  }

  // Rewrite as a mouse-move event.
  *rewritten_event = CreateMouseMoveEvent(event.location(), event.flags());
  last_touch_exploration_.reset(new TouchEvent(event));
  return ui::EVENT_REWRITE_REWRITTEN;
}

ui::EventRewriteStatus TouchExplorationController::InGestureInProgress(
    const ui::TouchEvent& event,
    scoped_ptr<ui::Event>* rewritten_event) {
  ui::EventType type = event.type();
  // If additional fingers are added before a swipe gesture has been registered,
  // then the state will no longer be GESTURE_IN_PROGRESS.
  if (type == ui::ET_TOUCH_PRESSED ||
      event.touch_id() != initial_press_->touch_id()) {
    if (tap_timer_.IsRunning())
      tap_timer_.Stop();
    // Discard any pending gestures.
    ignore_result(gesture_provider_.GetAndResetPendingGestures());
    state_ = TWO_TO_ONE_FINGER;
    last_two_to_one_.reset(new TouchEvent(event));
    rewritten_event->reset(new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
                                              event.location(),
                                              event.touch_id(),
                                              event.time_stamp()));
    (*rewritten_event)->set_flags(event.flags());
    return EVENT_REWRITE_REWRITTEN;
  }

  // There should not be more than one finger down.
  DCHECK(current_touch_ids_.size() <= 1);
  if (type == ui::ET_TOUCH_MOVED) {
    gesture_provider_.OnTouchEvent(event);
    gesture_provider_.OnTouchEventAck(false);
  }
  if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
    gesture_provider_.OnTouchEvent(event);
    gesture_provider_.OnTouchEventAck(false);
    if (current_touch_ids_.size() == 0)
      ResetToNoFingersDown();
  }

  ProcessGestureEvents();
  return ui::EVENT_REWRITE_DISCARD;
}

ui::EventRewriteStatus TouchExplorationController::InTwoToOneFinger(
    const ui::TouchEvent& event,
    scoped_ptr<ui::Event>* rewritten_event) {
  // The user should only ever be in TWO_TO_ONE_FINGER with two fingers down.
  // If the user added or removed a finger, the state is changed.
  ui::EventType type = event.type();
  if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
    DCHECK(current_touch_ids_.size() == 1);
    // Stop passing through the second finger and go to the wait state.
    if (current_touch_ids_.size() == 1) {
      rewritten_event->reset(new ui::TouchEvent(ui::ET_TOUCH_RELEASED,
                                                last_two_to_one_->location(),
                                                last_two_to_one_->touch_id(),
                                                event.time_stamp()));
      (*rewritten_event)->set_flags(event.flags());
      state_ = WAIT_FOR_RELEASE;
      return ui::EVENT_REWRITE_REWRITTEN;
    }
  } else if (type == ui::ET_TOUCH_PRESSED) {
    DCHECK(current_touch_ids_.size() == 3);
    // If a third finger is pressed, we are now going into passthrough mode
    // and now need to dispatch the first finger into a press, as well as the
    // recent press.
    if (current_touch_ids_.size() == 3){
      state_ = PASSTHROUGH;
      scoped_ptr<ui::TouchEvent> first_finger_press;
      first_finger_press.reset(
          new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
                             last_unused_finger_event_->location(),
                             last_unused_finger_event_->touch_id(),
                             event.time_stamp()));
      DispatchEvent(first_finger_press.get());
      rewritten_event->reset(new ui::TouchEvent(ui::ET_TOUCH_PRESSED,
                                                event.location(),
                                                event.touch_id(),
                                                event.time_stamp()));
      (*rewritten_event)->set_flags(event.flags());
      return ui::EVENT_REWRITE_REWRITTEN;
    }
  } else if (type == ui::ET_TOUCH_MOVED) {
    DCHECK(current_touch_ids_.size() == 2);
    // The first finger should have no events pass through, but for a proper
    // conversion to passthrough, the press of the initial finger should
    // be updated.
    if (event.touch_id() == last_unused_finger_event_->touch_id()) {
      last_unused_finger_event_.reset(new TouchEvent(event));
      return ui::EVENT_REWRITE_DISCARD;
    }
    if (event.touch_id() == last_two_to_one_->touch_id()) {
      last_two_to_one_.reset(new TouchEvent(event));
      rewritten_event->reset(new ui::TouchEvent(ui::ET_TOUCH_MOVED,
                                                event.location(),
                                                event.touch_id(),
                                                event.time_stamp()));
      (*rewritten_event)->set_flags(event.flags());
      return ui::EVENT_REWRITE_REWRITTEN;
    }
  }
  NOTREACHED() << "Unexpected event type received: " << event.name();
  return ui::EVENT_REWRITE_CONTINUE;
}

ui::EventRewriteStatus TouchExplorationController::InPassthrough(
    const ui::TouchEvent& event,
    scoped_ptr<ui::Event>* rewritten_event) {
  ui::EventType type = event.type();

  if (!(type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED ||
        type == ui::ET_TOUCH_MOVED || type == ui::ET_TOUCH_PRESSED)) {
    NOTREACHED() << "Unexpected event type received: " << event.name();
    return ui::EVENT_REWRITE_CONTINUE;
  }

  rewritten_event->reset(new ui::TouchEvent(
      type, event.location(), event.touch_id(), event.time_stamp()));
  (*rewritten_event)->set_flags(event.flags());

  if (current_touch_ids_.size() == 0) {
    ResetToNoFingersDown();
  }

  return ui::EVENT_REWRITE_REWRITTEN;
}

ui::EventRewriteStatus TouchExplorationController::InTouchExploreSecondPress(
    const ui::TouchEvent& event,
    scoped_ptr<ui::Event>* rewritten_event) {
  ui::EventType type = event.type();
  gfx::PointF location = event.location_f();
  if (type == ui::ET_TOUCH_PRESSED) {
    return ui::EVENT_REWRITE_DISCARD;
  } else if (type == ui::ET_TOUCH_MOVED) {
    // Currently this is a discard, but could be something like rotor
    // in the future.
    return ui::EVENT_REWRITE_DISCARD;
  } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
    // If the touch exploration finger is lifted, there is no option to return
    // to touch explore anymore. The remaining finger acts as a pending
    // tap or long tap for the last touch explore location.
    if (event.touch_id() == last_touch_exploration_->touch_id()){
      state_ = DOUBLE_TAP_PRESSED;
      VLOG_STATE();
      return EVENT_REWRITE_DISCARD;
    }

    // Continue to release the touch only if the touch explore finger is the
    // only finger remaining.
    if (current_touch_ids_.size() != 1)
      return EVENT_REWRITE_DISCARD;

    // Rewrite at location of last touch exploration.
    rewritten_event->reset(
        new ui::TouchEvent(ui::ET_TOUCH_RELEASED,
                           last_touch_exploration_->location(),
                           initial_press_->touch_id(),
                           event.time_stamp()));
    (*rewritten_event)->set_flags(event.flags());
    state_ = TOUCH_EXPLORATION;
    EnterTouchToMouseMode();
    VLOG_STATE();
    return ui::EVENT_REWRITE_REWRITTEN;
  }
  NOTREACHED() << "Unexpected event type received: " << event.name();
  return ui::EVENT_REWRITE_CONTINUE;
}

ui::EventRewriteStatus TouchExplorationController::InWaitForRelease(
    const ui::TouchEvent& event,
    scoped_ptr<ui::Event>* rewritten_event) {
  ui::EventType type = event.type();
  if (!(type == ui::ET_TOUCH_PRESSED || type == ui::ET_TOUCH_MOVED ||
        type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED)) {
    NOTREACHED() << "Unexpected event type received: " << event.name();
    return ui::EVENT_REWRITE_CONTINUE;
  }
  if (current_touch_ids_.size() == 0) {
    state_ = NO_FINGERS_DOWN;
    VLOG_STATE();
    ResetToNoFingersDown();
  }
  return EVENT_REWRITE_DISCARD;
}

void TouchExplorationController::OnTapTimerFired() {
  switch (state_) {
    case SINGLE_TAP_RELEASED:
      ResetToNoFingersDown();
      break;
    case TOUCH_EXPLORE_RELEASED:
      ResetToNoFingersDown();
      last_touch_exploration_.reset(new TouchEvent(*initial_press_));
      return;
    case SINGLE_TAP_PRESSED:
    case GESTURE_IN_PROGRESS:
      // Discard any pending gestures.
      ignore_result(gesture_provider_.GetAndResetPendingGestures());
      EnterTouchToMouseMode();
      state_ = TOUCH_EXPLORATION;
      VLOG_STATE();
      break;
    default:
      return;
  }
  scoped_ptr<ui::Event> mouse_move =
      CreateMouseMoveEvent(initial_press_->location(), initial_press_->flags());
  DispatchEvent(mouse_move.get());
  last_touch_exploration_.reset(new TouchEvent(*initial_press_));
 }

void TouchExplorationController::DispatchEvent(ui::Event* event) {
  if (event_handler_for_testing_) {
    event_handler_for_testing_->OnEvent(event);
    return;
  }
  ui::EventDispatchDetails result ALLOW_UNUSED =
      root_window_->GetHost()->dispatcher()->OnEventFromSource(event);
}

void TouchExplorationController::OnGestureEvent(ui::GestureEvent* gesture) {
  CHECK(gesture->IsGestureEvent());
  VLOG(0) << " \n Gesture Triggered: " << gesture->name();
  if (gesture->type() == ui::ET_GESTURE_SWIPE) {
    if (tap_timer_.IsRunning())
      tap_timer_.Stop();
    OnSwipeEvent(gesture);
    return;
  }
}

void TouchExplorationController::ProcessGestureEvents() {
  scoped_ptr<ScopedVector<ui::GestureEvent> > gestures(
      gesture_provider_.GetAndResetPendingGestures());
  if (gestures) {
    for (ScopedVector<GestureEvent>::iterator i = gestures->begin();
         i != gestures->end();
         ++i) {
      OnGestureEvent(*i);
    }
  }
}

void TouchExplorationController::OnSwipeEvent(ui::GestureEvent* swipe_gesture) {
  // A swipe gesture contains details for the direction in which the swipe
  // occurred.
  GestureEventDetails event_details = swipe_gesture->details();
  if (event_details.swipe_left()) {
    DispatchShiftSearchKeyEvent(ui::VKEY_LEFT);
    return;
  } else if (event_details.swipe_right()) {
    DispatchShiftSearchKeyEvent(ui::VKEY_RIGHT);
    return;
  } else if (event_details.swipe_up()) {
    DispatchShiftSearchKeyEvent(ui::VKEY_UP);
    return;
  } else if (event_details.swipe_down()) {
    DispatchShiftSearchKeyEvent(ui::VKEY_DOWN);
    return;
  }
}

void TouchExplorationController::DispatchShiftSearchKeyEvent(
    const ui::KeyboardCode direction) {
  // In order to activate the shortcut shift+search+<arrow key>
  // three KeyPressed events must be dispatched in succession along
  // with three KeyReleased events.
  ui::KeyEvent shift_down = ui::KeyEvent(
      ui::ET_KEY_PRESSED, ui::VKEY_SHIFT, ui::EF_SHIFT_DOWN, false);
  ui::KeyEvent search_down = ui::KeyEvent(
      ui::ET_KEY_PRESSED, kChromeOSSearchKey, ui::EF_SHIFT_DOWN, false);
  ui::KeyEvent direction_down =
      ui::KeyEvent(ui::ET_KEY_PRESSED, direction, ui::EF_SHIFT_DOWN, false);

  ui::KeyEvent direction_up =
      ui::KeyEvent(ui::ET_KEY_RELEASED, direction, ui::EF_SHIFT_DOWN, false);
  ui::KeyEvent search_up = ui::KeyEvent(
      ui::ET_KEY_RELEASED, kChromeOSSearchKey, ui::EF_SHIFT_DOWN, false);
  ui::KeyEvent shift_up =
      ui::KeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_SHIFT, ui::EF_NONE, false);

  DispatchEvent(&shift_down);
  DispatchEvent(&search_down);
  DispatchEvent(&direction_down);
  DispatchEvent(&direction_up);
  DispatchEvent(&search_up);
  DispatchEvent(&shift_up);
}

scoped_ptr<ui::Event> TouchExplorationController::CreateMouseMoveEvent(
    const gfx::PointF& location,
    int flags) {
  return scoped_ptr<ui::Event>(
      new ui::MouseEvent(
          ui::ET_MOUSE_MOVED,
          location,
          location,
          flags | ui::EF_IS_SYNTHESIZED | ui::EF_TOUCH_ACCESSIBILITY,
          0));
}

void TouchExplorationController::EnterTouchToMouseMode() {
  aura::client::CursorClient* cursor_client =
      aura::client::GetCursorClient(root_window_);
  if (cursor_client && !cursor_client->IsMouseEventsEnabled())
    cursor_client->EnableMouseEvents();
  if (cursor_client && cursor_client->IsCursorVisible())
    cursor_client->HideCursor();
}

void TouchExplorationController::ResetToNoFingersDown() {
  state_ = NO_FINGERS_DOWN;
  VLOG_STATE();
  if (tap_timer_.IsRunning())
    tap_timer_.Stop();
}

void TouchExplorationController::VlogState(const char* function_name) {
  if (!VLOG_on_)
    return;
  if (prev_state_ == state_)
    return;
  prev_state_ = state_;
  const char* state_string = EnumStateToString(state_);
  VLOG(0) << "\n Function name: " << function_name
          << "\n State: " << state_string;
}

void TouchExplorationController::VlogEvent(const ui::TouchEvent& touch_event,
                                           const char* function_name) {
  if (!VLOG_on_)
    return;

  CHECK(touch_event.IsTouchEvent());
  if (prev_event_ != NULL &&
      prev_event_->type() == touch_event.type() &&
      prev_event_->touch_id() == touch_event.touch_id()){
    return;
  }
  // The above statement prevents events of the same type and id from being
  // printed in a row. However, if two fingers are down, they would both be
  // moving and alternating printing move events unless we check for this.
  if (prev_event_ != NULL &&
      prev_event_->type() == ET_TOUCH_MOVED &&
      touch_event.type() == ET_TOUCH_MOVED){
    return;
  }
  const std::string& type = touch_event.name();
  const gfx::PointF& location = touch_event.location_f();
  const int touch_id = touch_event.touch_id();

  VLOG(0) << "\n Function name: " << function_name
          << "\n Event Type: " << type
          << "\n Location: " << location.ToString()
          << "\n Touch ID: " << touch_id;
  prev_event_.reset(new TouchEvent(touch_event));
}

const char* TouchExplorationController::EnumStateToString(State state) {
  switch (state) {
    case NO_FINGERS_DOWN:
      return "NO_FINGERS_DOWN";
    case SINGLE_TAP_PRESSED:
      return "SINGLE_TAP_PRESSED";
    case SINGLE_TAP_RELEASED:
      return "SINGLE_TAP_RELEASED";
    case TOUCH_EXPLORE_RELEASED:
      return "TOUCH_EXPLORE_RELEASED";
    case DOUBLE_TAP_PRESSED:
      return "DOUBLE_TAP_PRESSED";
    case TOUCH_EXPLORATION:
      return "TOUCH_EXPLORATION";
    case GESTURE_IN_PROGRESS:
      return "GESTURE_IN_PROGRESS";
    case TOUCH_EXPLORE_SECOND_PRESS:
      return "TOUCH_EXPLORE_SECOND_PRESS";
    case TWO_TO_ONE_FINGER:
      return "TWO_TO_ONE_FINGER";
    case PASSTHROUGH:
      return "PASSTHROUGH";
    case WAIT_FOR_RELEASE:
      return "WAIT_FOR_RELEASE";
  }
  return "Not a state";
}

}  // namespace ui