// 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 "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "content/browser/renderer_host/input/input_ack_handler.h"
#include "content/browser/renderer_host/input/input_router_client.h"
#include "content/browser/renderer_host/input/input_router_impl.h"
#include "content/common/input/web_input_event_traits.h"
#include "content/common/input_messages.h"
#include "content/common/view_messages.h"
#include "ipc/ipc_sender.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/perf/perf_test.h"
#include "ui/gfx/geometry/vector2d_f.h"

using base::TimeDelta;
using blink::WebGestureEvent;
using blink::WebInputEvent;
using blink::WebTouchEvent;
using blink::WebTouchPoint;

namespace content {

namespace {

class NullInputAckHandler : public InputAckHandler {
 public:
  NullInputAckHandler() : ack_count_(0) {}
  virtual ~NullInputAckHandler() {}

  // InputAckHandler
  virtual void OnKeyboardEventAck(const NativeWebKeyboardEvent& event,
                                  InputEventAckState ack_result) OVERRIDE {
    ++ack_count_;
  }
  virtual void OnWheelEventAck(const MouseWheelEventWithLatencyInfo& event,
                               InputEventAckState ack_result) OVERRIDE {
    ++ack_count_;
  }
  virtual void OnTouchEventAck(const TouchEventWithLatencyInfo& event,
                               InputEventAckState ack_result) OVERRIDE {
    ++ack_count_;
  }
  virtual void OnGestureEventAck(const GestureEventWithLatencyInfo& event,
                                 InputEventAckState ack_result) OVERRIDE {
    ++ack_count_;
  }
  virtual void OnUnexpectedEventAck(UnexpectedEventAckType type) OVERRIDE {
    ++ack_count_;
  }

  size_t GetAndResetAckCount() {
    size_t ack_count = ack_count_;
    ack_count_ = 0;
    return ack_count;
  }

  size_t ack_count() const { return ack_count_; }

 private:
  size_t ack_count_;
};

class NullInputRouterClient : public InputRouterClient {
 public:
  NullInputRouterClient() {}
  virtual ~NullInputRouterClient() {}

  // InputRouterClient
  virtual InputEventAckState FilterInputEvent(
      const blink::WebInputEvent& input_event,
      const ui::LatencyInfo& latency_info) OVERRIDE {
    return INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
  }
  virtual void IncrementInFlightEventCount() OVERRIDE {}
  virtual void DecrementInFlightEventCount() OVERRIDE {}
  virtual void OnHasTouchEventHandlers(bool has_handlers) OVERRIDE {}
  virtual void DidFlush() OVERRIDE {}
  virtual void SetNeedsFlush() OVERRIDE {}
  virtual void DidOverscroll(const DidOverscrollParams& params) OVERRIDE {}
};

class NullIPCSender : public IPC::Sender {
 public:
  NullIPCSender() : sent_count_(0) {}
  virtual ~NullIPCSender() {}

  virtual bool Send(IPC::Message* message) OVERRIDE {
    delete message;
    ++sent_count_;
    return true;
  }

  size_t GetAndResetSentEventCount() {
    size_t message_count = sent_count_;
    sent_count_ = 0;
    return message_count;
  }

  bool HasMessages() const { return sent_count_ > 0; }

 private:
  size_t sent_count_;
};

// TODO(jdduke): Use synthetic gesture pipeline, crbug.com/344598.
typedef std::vector<WebGestureEvent> Gestures;
Gestures BuildScrollSequence(size_t steps,
                             gfx::Vector2dF origin,
                             gfx::Vector2dF distance) {
  Gestures gestures;
  const gfx::Vector2dF delta = ScaleVector2d(distance, 1.f / steps);

  WebGestureEvent gesture;
  gesture.type = WebInputEvent::GestureScrollBegin;
  gesture.x = origin.x();
  gesture.y = origin.y();
  gestures.push_back(gesture);

  gesture.type = WebInputEvent::GestureScrollUpdate;
  gesture.data.scrollUpdate.deltaX = delta.x();
  gesture.data.scrollUpdate.deltaY = delta.y();
  for (size_t i = 0; i < steps; ++i) {
    gesture.x += delta.x();
    gesture.y += delta.y();
    gestures.push_back(gesture);
  }

  gesture.type = WebInputEvent::GestureScrollEnd;
  gestures.push_back(gesture);
  return gestures;
}

typedef std::vector<WebTouchEvent> Touches;
Touches BuildTouchSequence(size_t steps,
                           gfx::Vector2dF origin,
                           gfx::Vector2dF distance) {
  Touches touches;
  const gfx::Vector2dF delta = ScaleVector2d(distance, 1.f / steps);

  WebTouchEvent touch;
  touch.touchesLength = 1;
  touch.type = WebInputEvent::TouchStart;
  touch.touches[0].id = 0;
  touch.touches[0].state = WebTouchPoint::StatePressed;
  touch.touches[0].position.x = origin.x();
  touch.touches[0].position.y = origin.y();
  touch.touches[0].screenPosition.x = origin.x();
  touch.touches[0].screenPosition.y = origin.y();
  touches.push_back(touch);

  touch.type = WebInputEvent::TouchMove;
  touch.touches[0].state = WebTouchPoint::StateMoved;
  for (size_t i = 0; i < steps; ++i) {
    touch.touches[0].position.x += delta.x();
    touch.touches[0].position.y += delta.y();
    touch.touches[0].screenPosition.x += delta.x();
    touch.touches[0].screenPosition.y += delta.y();
    touches.push_back(touch);
  }

  touch.type = WebInputEvent::TouchEnd;
  touch.touches[0].state = WebTouchPoint::StateReleased;
  touches.push_back(touch);
  return touches;
}

class InputEventTimer {
 public:
  InputEventTimer(const char* test_name, int64 event_count)
      : test_name_(test_name),
        event_count_(event_count),
        start_(base::TimeTicks::Now()) {}

  ~InputEventTimer() {
    perf_test::PrintResult(
        "avg_time_per_event",
        "",
        test_name_,
        static_cast<size_t>(((base::TimeTicks::Now() - start_) / event_count_)
                                .InMicroseconds()),
        "us",
        true);
  }

 private:
  const char* test_name_;
  int64 event_count_;
  base::TimeTicks start_;
  DISALLOW_COPY_AND_ASSIGN(InputEventTimer);
};

}  // namespace

class InputRouterImplPerfTest : public testing::Test {
 public:
  InputRouterImplPerfTest() : last_input_id_(0) {}
  virtual ~InputRouterImplPerfTest() {}

 protected:
  // testing::Test
  virtual void SetUp() OVERRIDE {
    sender_.reset(new NullIPCSender());
    client_.reset(new NullInputRouterClient());
    ack_handler_.reset(new NullInputAckHandler());
    input_router_.reset(new InputRouterImpl(sender_.get(),
                                            client_.get(),
                                            ack_handler_.get(),
                                            MSG_ROUTING_NONE,
                                            InputRouterImpl::Config()));
  }

  virtual void TearDown() OVERRIDE {
    base::MessageLoop::current()->RunUntilIdle();

    input_router_.reset();
    ack_handler_.reset();
    client_.reset();
    sender_.reset();
  }

  void SendEvent(const WebGestureEvent& gesture,
                 const ui::LatencyInfo& latency) {
    input_router_->SendGestureEvent(
        GestureEventWithLatencyInfo(gesture, latency));
  }

  void SendEvent(const WebTouchEvent& touch, const ui::LatencyInfo& latency) {
    input_router_->SendTouchEvent(TouchEventWithLatencyInfo(touch, latency));
  }

  void SendEventAckIfNecessary(const blink::WebInputEvent& event,
                               InputEventAckState ack_result) {
    if (WebInputEventTraits::IgnoresAckDisposition(event))
      return;
    InputHostMsg_HandleInputEvent_ACK_Params ack;
    ack.type = event.type;
    ack.state = ack_result;
    InputHostMsg_HandleInputEvent_ACK response(0, ack);
    input_router_->OnMessageReceived(response);
  }

  void OnHasTouchEventHandlers(bool has_handlers) {
    input_router_->OnMessageReceived(
        ViewHostMsg_HasTouchEventHandlers(0, has_handlers));
  }

  size_t GetAndResetSentEventCount() {
    return sender_->GetAndResetSentEventCount();
  }

  size_t GetAndResetAckCount() { return ack_handler_->GetAndResetAckCount(); }

  size_t AckCount() const { return ack_handler_->ack_count(); }

  int64 NextLatencyID() { return ++last_input_id_; }

  ui::LatencyInfo CreateLatencyInfo() {
    ui::LatencyInfo latency;
    latency.AddLatencyNumber(
        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, 1, 0);
    latency.AddLatencyNumber(
        ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_RWH_COMPONENT,
        1,
        NextLatencyID());
    return latency;
  }

  // TODO(jdduke): Use synthetic gesture pipeline, crbug.com/344598.
  template <typename EventType>
  void SimulateEventSequence(const char* test_name,
                             const std::vector<EventType>& events,
                             bool ack_delay,
                             size_t iterations) {
    OnHasTouchEventHandlers(true);

    const size_t event_count = events.size();
    const size_t total_event_count = event_count * iterations;

    InputEventTimer timer(test_name, total_event_count);
    while (iterations--) {
      size_t i = 0, ack_i = 0;
      if (ack_delay)
        SendEvent(events[i++], CreateLatencyInfo());

      for (; i < event_count; ++i, ++ack_i) {
        SendEvent(events[i], CreateLatencyInfo());
        SendEventAckIfNecessary(events[ack_i], INPUT_EVENT_ACK_STATE_CONSUMED);
      }

      if (ack_delay)
        SendEventAckIfNecessary(events.back(), INPUT_EVENT_ACK_STATE_CONSUMED);

      EXPECT_EQ(event_count, GetAndResetSentEventCount());
      EXPECT_EQ(event_count, GetAndResetAckCount());
    }
  }

  void SimulateTouchAndScrollEventSequence(const char* test_name,
                                           size_t steps,
                                           gfx::Vector2dF origin,
                                           gfx::Vector2dF distance,
                                           size_t iterations) {
    OnHasTouchEventHandlers(true);

    Gestures gestures = BuildScrollSequence(steps, origin, distance);
    Touches touches = BuildTouchSequence(steps, origin, distance);
    ASSERT_EQ(touches.size(), gestures.size());

    const size_t event_count = gestures.size();
    const size_t total_event_count = event_count * iterations * 2;

    InputEventTimer timer(test_name, total_event_count);
    while (iterations--) {
      for (size_t i = 0; i < event_count; ++i) {
        SendEvent(touches[i], CreateLatencyInfo());
        // Touches may not be forwarded after the scroll sequence has begun, so
        // only ack if necessary.
        if (!AckCount()) {
          SendEventAckIfNecessary(touches[i],
                                  INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
        }

        SendEvent(gestures[i], CreateLatencyInfo());
        SendEventAckIfNecessary(gestures[i], INPUT_EVENT_ACK_STATE_CONSUMED);
        EXPECT_EQ(2U, GetAndResetAckCount());
      }
    }
  }

 private:
  int64 last_input_id_;
  scoped_ptr<NullIPCSender> sender_;
  scoped_ptr<NullInputRouterClient> client_;
  scoped_ptr<NullInputAckHandler> ack_handler_;
  scoped_ptr<InputRouterImpl> input_router_;
  base::MessageLoopForUI message_loop_;
};

const size_t kDefaultSteps(100);
const size_t kDefaultIterations(100);
const gfx::Vector2dF kDefaultOrigin(100, 100);
const gfx::Vector2dF kDefaultDistance(500, 500);

TEST_F(InputRouterImplPerfTest, TouchSwipe) {
  SimulateEventSequence(
      "TouchSwipe ",
      BuildTouchSequence(kDefaultSteps, kDefaultOrigin, kDefaultDistance),
      false,
      kDefaultIterations);
}

TEST_F(InputRouterImplPerfTest, TouchSwipeDelayedAck) {
  SimulateEventSequence(
      "TouchSwipeDelayedAck ",
      BuildTouchSequence(kDefaultSteps, kDefaultOrigin, kDefaultDistance),
      true,
      kDefaultIterations);
}

TEST_F(InputRouterImplPerfTest, GestureScroll) {
  SimulateEventSequence(
      "GestureScroll ",
      BuildScrollSequence(kDefaultSteps, kDefaultOrigin, kDefaultDistance),
      false,
      kDefaultIterations);
}

TEST_F(InputRouterImplPerfTest, GestureScrollDelayedAck) {
  SimulateEventSequence(
      "GestureScrollDelayedAck ",
      BuildScrollSequence(kDefaultSteps, kDefaultOrigin, kDefaultDistance),
      true,
      kDefaultIterations);
}

TEST_F(InputRouterImplPerfTest, TouchSwipeToGestureScroll) {
  SimulateTouchAndScrollEventSequence("TouchSwipeToGestureScroll ",
                                      kDefaultSteps,
                                      kDefaultOrigin,
                                      kDefaultDistance,
                                      kDefaultIterations);
}

}  // namespace content
