// Copyright 2016 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/renderer/mus/compositor_mus_connection.h"

#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/test/test_simple_task_runner.h"
#include "base/time/time.h"
#include "components/mus/public/cpp/tests/test_window.h"
#include "components/mus/public/interfaces/input_event_constants.mojom.h"
#include "components/mus/public/interfaces/input_events.mojom.h"
#include "components/mus/public/interfaces/input_key_codes.mojom.h"
#include "content/common/input/did_overscroll_params.h"
#include "content/common/input/input_event_ack.h"
#include "content/common/input/input_event_ack_state.h"
#include "content/public/test/mock_render_thread.h"
#include "content/renderer/input/input_handler_manager.h"
#include "content/renderer/input/input_handler_manager_client.h"
#include "content/renderer/input/render_widget_input_handler.h"
#include "content/renderer/mus/render_widget_mus_connection.h"
#include "content/renderer/render_widget.h"
#include "content/test/fake_compositor_dependencies.h"
#include "content/test/fake_renderer_scheduler.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

// Wrapper for the callback provided to
// CompositorMusConnection:OnWindowInputEvent. This tracks whether the it was
// called, along with the result.
class TestCallback : public base::RefCounted<TestCallback> {
 public:
  TestCallback() : called_(false), result_(false) {}

  bool called() { return called_; }
  bool result() { return result_; }

  void BoolCallback(bool result) {
    called_ = true;
    result_ = result;
  }

 private:
  friend class base::RefCounted<TestCallback>;

  ~TestCallback() {}

  bool called_;
  bool result_;

  DISALLOW_COPY_AND_ASSIGN(TestCallback);
};

// Allows for overriding the behaviour of HandleInputEvent, to simulate input
// handlers which consume events before they are sent to the renderer.
class TestInputHandlerManager : public content::InputHandlerManager {
 public:
  TestInputHandlerManager(
      const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
      content::InputHandlerManagerClient* client,
      scheduler::RendererScheduler* renderer_scheduler)
      : InputHandlerManager(task_runner, client, renderer_scheduler),
        override_result_(false),
        result_(content::InputEventAckState::INPUT_EVENT_ACK_STATE_UNKNOWN) {}
  ~TestInputHandlerManager() override {}

  // Stops overriding the behaviour of HandleInputEvent
  void ClearHandleInputEventOverride();

  // Overrides the behaviour of HandleInputEvent, returing |result|.
  void SetHandleInputEventResult(content::InputEventAckState result);

  // content::InputHandlerManager:
  content::InputEventAckState HandleInputEvent(
      int routing_id,
      const blink::WebInputEvent* input_event,
      ui::LatencyInfo* latency_info) override;

 private:
  // If true content::InputHandlerManager::HandleInputEvent is not called.
  bool override_result_;

  // The result to return in HandleInputEvent if |override_result_|.
  content::InputEventAckState result_;

  DISALLOW_COPY_AND_ASSIGN(TestInputHandlerManager);
};

void TestInputHandlerManager::ClearHandleInputEventOverride() {
  override_result_ = false;
}

void TestInputHandlerManager::SetHandleInputEventResult(
    content::InputEventAckState result) {
  override_result_ = true;
  result_ = result;
}

content::InputEventAckState TestInputHandlerManager::HandleInputEvent(
    int routing_id,
    const blink::WebInputEvent* input_event,
    ui::LatencyInfo* latency_info) {
  if (override_result_)
    return result_;
  return content::InputHandlerManager::HandleInputEvent(routing_id, input_event,
                                                        latency_info);
}

// Empty implementation of InputHandlerManagerClient.
class TestInputHandlerManagerClient
    : public content::InputHandlerManagerClient {
 public:
  TestInputHandlerManagerClient() {}
  ~TestInputHandlerManagerClient() override{};

  // content::InputHandlerManagerClient:
  void SetBoundHandler(const Handler& handler) override {}
  void DidAddInputHandler(
      int routing_id,
      ui::SynchronousInputHandlerProxy* synchronous_handler) override {}
  void DidRemoveInputHandler(int routing_id) override {}
  void DidOverscroll(int routing_id,
                     const content::DidOverscrollParams& params) override {}
  void DidStopFlinging(int routing_id) override {}
  void NonBlockingInputEventHandled(int routing_id,
                                    blink::WebInputEvent::Type type) override {}

 private:
  DISALLOW_COPY_AND_ASSIGN(TestInputHandlerManagerClient);
};

// Implementation of RenderWidget for testing, performs no initialization.
class TestRenderWidget : public content::RenderWidget {
 public:
  explicit TestRenderWidget(content::CompositorDependencies* compositor_deps)
      : content::RenderWidget(compositor_deps,
                              blink::WebPopupTypeNone,
                              blink::WebScreenInfo(),
                              true,
                              false,
                              false) {}

 protected:
  ~TestRenderWidget() override {}

 private:
  DISALLOW_COPY_AND_ASSIGN(TestRenderWidget);
};

// Test override of RenderWidgetInputHandler to allow the control of
// HandleInputEvent. This will perform no actions on input until a
// RenderWidgetInputHandlerDelegate is set. Once set this will always ack
// received events.
class TestRenderWidgetInputHandler : public content::RenderWidgetInputHandler {
 public:
  TestRenderWidgetInputHandler(content::RenderWidget* render_widget);
  ~TestRenderWidgetInputHandler() override {}

  void set_delegate(content::RenderWidgetInputHandlerDelegate* delegate) {
    delegate_ = delegate;
  }
  void set_state(content::InputEventAckState state) { state_ = state; }

  // content::RenderWidgetInputHandler:
  void HandleInputEvent(const blink::WebInputEvent& input_event,
                        const ui::LatencyInfo& latency_info,
                        content::InputEventDispatchType dispatch_type) override;

 private:
  // The input delegate which receives event acks.
  content::RenderWidgetInputHandlerDelegate* delegate_;

  // The result of input handling to send to |delegate_| during the ack.
  content::InputEventAckState state_;

  DISALLOW_COPY_AND_ASSIGN(TestRenderWidgetInputHandler);
};

TestRenderWidgetInputHandler::TestRenderWidgetInputHandler(
    content::RenderWidget* render_widget)
    : content::RenderWidgetInputHandler(render_widget, render_widget),
      delegate_(nullptr),
      state_(content::InputEventAckState::INPUT_EVENT_ACK_STATE_UNKNOWN) {}

void TestRenderWidgetInputHandler::HandleInputEvent(
    const blink::WebInputEvent& input_event,
    const ui::LatencyInfo& latency_info,
    content::InputEventDispatchType dispatch_type) {
  if (delegate_) {
    scoped_ptr<content::InputEventAck> ack(
        new content::InputEventAck(input_event.type, state_));
    delegate_->OnInputEventAck(std::move(ack));
  }
}

}  // namespace

namespace content {

// Test suite for CompositorMusConnection, this does not setup a full renderer
// environment. This does not establish a connection to a mus server, nor does
// it initialize one.
class CompositorMusConnectionTest : public testing::Test {
 public:
  CompositorMusConnectionTest() {}
  ~CompositorMusConnectionTest() override {}

  // Initializes |event| with valid parameters for a key event, so that it can
  // be converted to a web event by CompositorMusConnection.
  void GenerateKeyEvent(mus::mojom::EventPtr& event);

  // Calls CompositorMusConnection::OnWindowInputEvent.
  void OnWindowInputEvent(mus::Window* window,
                          mus::mojom::EventPtr event,
                          scoped_ptr<base::Callback<void(bool)>>* ack_callback);

  // Confirms the state of pending tasks enqueued on each task runner, and runs
  // until idle.
  void VerifyAndRunQueues(bool main_task_runner_enqueued,
                          bool compositor_task_runner_enqueued);

  CompositorMusConnection* compositor_connection() {
    return compositor_connection_.get();
  }
  RenderWidgetMusConnection* connection() { return connection_; }
  TestInputHandlerManager* input_handler_manager() {
    return input_handler_manager_.get();
  }
  TestRenderWidgetInputHandler* render_widget_input_handler() {
    return render_widget_input_handler_.get();
  }

  // testing::Test:
  void SetUp() override;
  void TearDown() override;

 private:
  // Mocks/Fakes of the testing environment.
  TestInputHandlerManagerClient input_handler_manager_client_;
  FakeCompositorDependencies compositor_dependencies_;
  FakeRendererScheduler renderer_scheduler_;
  MockRenderThread render_thread_;
  scoped_refptr<TestRenderWidget> render_widget_;
  mojo::InterfaceRequest<mus::mojom::WindowTreeClient> request_;

  // Not owned, RenderWidgetMusConnection tracks in static state. Cleared during
  // TearDown.
  RenderWidgetMusConnection* connection_;

  // Test versions of task runners, see VerifyAndRunQueues to use in testing.
  scoped_refptr<base::TestSimpleTaskRunner> main_task_runner_;
  scoped_refptr<base::TestSimpleTaskRunner> compositor_task_runner_;

  // Actual CompositorMusConnection for testing.
  scoped_refptr<CompositorMusConnection> compositor_connection_;

  // Test implementations, to control input given to |compositor_connection_|.
  scoped_ptr<TestInputHandlerManager> input_handler_manager_;
  scoped_ptr<TestRenderWidgetInputHandler> render_widget_input_handler_;

  DISALLOW_COPY_AND_ASSIGN(CompositorMusConnectionTest);
};

void CompositorMusConnectionTest::GenerateKeyEvent(
    mus::mojom::EventPtr& event) {
  event->action = mus::mojom::EventType::KEY_PRESSED;
  event->time_stamp = base::TimeTicks::Now().ToInternalValue();
  event->key_data = mus::mojom::KeyData::New();
  event->key_data->is_char = true;
  event->key_data->windows_key_code = mus::mojom::KeyboardCode::A;
}

void CompositorMusConnectionTest::OnWindowInputEvent(
    mus::Window* window,
    mus::mojom::EventPtr event,
    scoped_ptr<base::Callback<void(bool)>>* ack_callback) {
  compositor_connection_->OnWindowInputEvent(window, std::move(event),
                                             ack_callback);
}

void CompositorMusConnectionTest::VerifyAndRunQueues(
    bool main_task_runner_enqueued,
    bool compositor_task_runner_enqueued) {
  // Run through the enqueued actions.
  EXPECT_EQ(main_task_runner_enqueued, main_task_runner_->HasPendingTask());
  main_task_runner_->RunUntilIdle();

  EXPECT_EQ(compositor_task_runner_enqueued,
            compositor_task_runner_->HasPendingTask());
  compositor_task_runner_->RunUntilIdle();
}

void CompositorMusConnectionTest::SetUp() {
  testing::Test::SetUp();

  main_task_runner_ = new base::TestSimpleTaskRunner();
  compositor_task_runner_ = new base::TestSimpleTaskRunner();

  input_handler_manager_.reset(new TestInputHandlerManager(
      compositor_task_runner_, &input_handler_manager_client_,
      &renderer_scheduler_));

  const int routing_id = 42;
  compositor_connection_ = new CompositorMusConnection(
      routing_id, main_task_runner_, compositor_task_runner_,
      std::move(request_), input_handler_manager_.get());

  // CompositorMusConnection attempts to create connection to the non-existant
  // server. Clear that.
  compositor_task_runner_->ClearPendingTasks();

  render_widget_ = new TestRenderWidget(&compositor_dependencies_);
  render_widget_input_handler_.reset(
      new TestRenderWidgetInputHandler(render_widget_.get()));
  connection_ = RenderWidgetMusConnection::GetOrCreate(routing_id);
  connection_->SetInputHandler(render_widget_input_handler_.get());
}

void CompositorMusConnectionTest::TearDown() {
  // Clear static state.
  connection_->OnConnectionLost();
  testing::Test::TearDown();
}

// Tests that for events which the renderer will ack, yet not consume, that
// CompositorMusConnection consumes the ack during OnWindowInputEvent, and calls
// it with the correct state once processed.
TEST_F(CompositorMusConnectionTest, NotConsumed) {
  TestRenderWidgetInputHandler* input_handler = render_widget_input_handler();
  input_handler->set_delegate(connection());
  input_handler->set_state(
      InputEventAckState::INPUT_EVENT_ACK_STATE_NOT_CONSUMED);

  mus::TestWindow test_window;
  mus::mojom::EventPtr event = mus::mojom::Event::New();
  GenerateKeyEvent(event);
  scoped_refptr<TestCallback> test_callback(new TestCallback);
  scoped_ptr<base::Callback<void(bool)>> ack_callback(
      new base::Callback<void(bool)>(
          base::Bind(&::TestCallback::BoolCallback, test_callback)));

  OnWindowInputEvent(&test_window, std::move(event), &ack_callback);
  // OnWindowInputEvent is expected to clear the callback if it plans on
  // handling the ack.
  EXPECT_FALSE(ack_callback.get());

  VerifyAndRunQueues(true, true);

  // The ack callback should have been called
  EXPECT_TRUE(test_callback->called());
  EXPECT_FALSE(test_callback->result());
}

// Tests that for events which the renderer will ack, and consume, that
// CompositorMusConnection consumes the ack during OnWindowInputEvent, and calls
// it with the correct state once processed.
TEST_F(CompositorMusConnectionTest, Consumed) {
  TestRenderWidgetInputHandler* input_handler = render_widget_input_handler();
  input_handler->set_delegate(connection());
  input_handler->set_state(InputEventAckState::INPUT_EVENT_ACK_STATE_CONSUMED);

  mus::TestWindow test_window;
  mus::mojom::EventPtr event = mus::mojom::Event::New();
  GenerateKeyEvent(event);
  scoped_refptr<TestCallback> test_callback(new TestCallback);
  scoped_ptr<base::Callback<void(bool)>> ack_callback(
      new base::Callback<void(bool)>(
          base::Bind(&::TestCallback::BoolCallback, test_callback)));

  OnWindowInputEvent(&test_window, std::move(event), &ack_callback);
  // OnWindowInputEvent is expected to clear the callback if it plans on
  // handling the ack.
  EXPECT_FALSE(ack_callback.get());

  VerifyAndRunQueues(true, true);

  // The ack callback should have been called
  EXPECT_TRUE(test_callback->called());
  EXPECT_TRUE(test_callback->result());
}

// Tests that when the RenderWidgetInputHandler does not ack before a new event
// arrives, that only the most recent ack is fired.
TEST_F(CompositorMusConnectionTest, LostAck) {
  mus::TestWindow test_window;
  mus::mojom::EventPtr event1 = mus::mojom::Event::New();
  GenerateKeyEvent(event1);
  scoped_refptr<TestCallback> test_callback1(new TestCallback);
  scoped_ptr<base::Callback<void(bool)>> ack_callback1(
      new base::Callback<void(bool)>(
          base::Bind(&::TestCallback::BoolCallback, test_callback1)));

  OnWindowInputEvent(&test_window, std::move(event1), &ack_callback1);
  EXPECT_FALSE(ack_callback1.get());
  // When simulating the timeout the ack is never enqueued
  VerifyAndRunQueues(true, false);

  // Setting a delegate will lead to the next event being acked. Having a
  // cleared queue simulates the input handler timing out on an event.
  TestRenderWidgetInputHandler* input_handler = render_widget_input_handler();
  input_handler->set_delegate(connection());
  input_handler->set_state(InputEventAckState::INPUT_EVENT_ACK_STATE_CONSUMED);

  mus::mojom::EventPtr event2 = mus::mojom::Event::New();
  GenerateKeyEvent(event2);
  scoped_refptr<TestCallback> test_callback2(new TestCallback);
  scoped_ptr<base::Callback<void(bool)>> ack_callback2(
      new base::Callback<void(bool)>(
          base::Bind(&::TestCallback::BoolCallback, test_callback2)));
  OnWindowInputEvent(&test_window, std::move(event2), &ack_callback2);
  EXPECT_FALSE(ack_callback2.get());

  VerifyAndRunQueues(true, true);

  // Only the most recent ack was called.
  EXPECT_FALSE(test_callback1->called());
  EXPECT_TRUE(test_callback2->called());
  EXPECT_TRUE(test_callback2->result());
}

// Tests that when an input handler consumes the event, that
// CompositorMusConnection does not consume the ack, nor calls it.
TEST_F(CompositorMusConnectionTest, InputHandlerConsumes) {
  input_handler_manager()->SetHandleInputEventResult(
      InputEventAckState::INPUT_EVENT_ACK_STATE_CONSUMED);
  mus::TestWindow test_window;
  mus::mojom::EventPtr event = mus::mojom::Event::New();
  GenerateKeyEvent(event);
  scoped_refptr<TestCallback> test_callback(new TestCallback);
  scoped_ptr<base::Callback<void(bool)>> ack_callback(
      new base::Callback<void(bool)>(
          base::Bind(&::TestCallback::BoolCallback, test_callback)));

  OnWindowInputEvent(&test_window, std::move(event), &ack_callback);

  EXPECT_TRUE(ack_callback.get());
  VerifyAndRunQueues(false, false);
  EXPECT_FALSE(test_callback->called());
}

// Tests that when the renderer will not ack an event, that
// CompositorMusConnection does not consume the ack, nor calls it.
TEST_F(CompositorMusConnectionTest, RendererWillNotSendAck) {
  mus::TestWindow test_window;
  mus::mojom::EventPtr event = mus::mojom::Event::New();
  event->action = mus::mojom::EventType::POINTER_DOWN;
  event->time_stamp = base::TimeTicks::Now().ToInternalValue();
  event->pointer_data = mus::mojom::PointerData::New();

  scoped_refptr<TestCallback> test_callback(new TestCallback);
  scoped_ptr<base::Callback<void(bool)>> ack_callback(
      new base::Callback<void(bool)>(
          base::Bind(&::TestCallback::BoolCallback, test_callback)));

  OnWindowInputEvent(&test_window, std::move(event), &ack_callback);
  EXPECT_TRUE(ack_callback.get());

  VerifyAndRunQueues(true, false);
  EXPECT_FALSE(test_callback->called());
}

}  // namespace content