// Copyright 2015 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 <stdint.h>

#include <string>
#include <utility>

#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/scoped_vector.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "mojo/message_pump/handle_watcher.h"
#include "mojo/message_pump/message_pump_mojo.h"
#include "mojo/public/cpp/test_support/test_support.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace mojo {
namespace common {
namespace test {

enum MessageLoopConfig {
  MESSAGE_LOOP_CONFIG_DEFAULT = 0,
  MESSAGE_LOOP_CONFIG_MOJO = 1
};

std::unique_ptr<base::MessageLoop> CreateMessageLoop(MessageLoopConfig config) {
  std::unique_ptr<base::MessageLoop> loop;
  if (config == MESSAGE_LOOP_CONFIG_DEFAULT)
    loop.reset(new base::MessageLoop());
  else
    loop.reset(new base::MessageLoop(MessagePumpMojo::Create()));
  return loop;
}

void OnWatcherSignaled(const base::Closure& callback, MojoResult /* result */) {
  callback.Run();
}

class ScopedPerfTimer {
 public:
  ScopedPerfTimer(const std::string& test_name,
                  const std::string& sub_test_name,
                  uint64_t iterations)
      : test_name_(test_name),
        sub_test_name_(sub_test_name),
        iterations_(iterations),
        start_time_(base::TimeTicks::Now()) {}
  ~ScopedPerfTimer() {
    base::TimeTicks end_time = base::TimeTicks::Now();
    mojo::test::LogPerfResult(
        test_name_.c_str(), sub_test_name_.c_str(),
        iterations_ / (end_time - start_time_).InSecondsF(),
        "iterations/second");
  }

 private:
  const std::string test_name_;
  const std::string sub_test_name_;
  const uint64_t iterations_;
  base::TimeTicks start_time_;

  DISALLOW_COPY_AND_ASSIGN(ScopedPerfTimer);
};

class HandleWatcherPerftest : public testing::TestWithParam<MessageLoopConfig> {
 public:
  HandleWatcherPerftest() : message_loop_(CreateMessageLoop(GetParam())) {}

 protected:
  std::string GetMessageLoopName() const {
    return (GetParam() == MESSAGE_LOOP_CONFIG_DEFAULT) ? "DefaultMessageLoop"
                                                       : "MojoMessageLoop";
  }

 private:
  std::unique_ptr<base::MessageLoop> message_loop_;

  DISALLOW_COPY_AND_ASSIGN(HandleWatcherPerftest);
};

INSTANTIATE_TEST_CASE_P(MultipleMessageLoopConfigs,
                        HandleWatcherPerftest,
                        testing::Values(MESSAGE_LOOP_CONFIG_DEFAULT,
                                        MESSAGE_LOOP_CONFIG_MOJO));

void NeverReached(MojoResult result) {
  FAIL() << "Callback should never be invoked " << result;
}

TEST_P(HandleWatcherPerftest, StartStop) {
  const uint64_t kIterations = 100000;
  MessagePipe pipe;
  HandleWatcher watcher;

  ScopedPerfTimer timer("StartStop", GetMessageLoopName(), kIterations);
  for (uint64_t i = 0; i < kIterations; i++) {
    watcher.Start(pipe.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE,
                  MOJO_DEADLINE_INDEFINITE, base::Bind(&NeverReached));
    watcher.Stop();
  }
}

TEST_P(HandleWatcherPerftest, StartAllThenStop_1000Handles) {
  const uint64_t kIterations = 100;
  const uint64_t kHandles = 1000;

  struct TestData {
    MessagePipe pipe;
    HandleWatcher watcher;
  };
  ScopedVector<TestData> data_vector;
  // Create separately from the start/stop loops to avoid affecting the
  // benchmark.
  for (uint64_t i = 0; i < kHandles; i++) {
    std::unique_ptr<TestData> test_data(new TestData);
    ASSERT_TRUE(test_data->pipe.handle0.is_valid());
    data_vector.push_back(std::move(test_data));
  }

  ScopedPerfTimer timer("StartAllThenStop_1000Handles", GetMessageLoopName(),
                        kIterations * kHandles);
  for (uint64_t iter = 0; iter < kIterations; iter++) {
    for (uint64_t i = 0; i < kHandles; i++) {
      TestData* test_data = data_vector[i];
      test_data->watcher.Start(
          test_data->pipe.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE,
          MOJO_DEADLINE_INDEFINITE, base::Bind(&NeverReached));
    }
    for (uint64_t i = 0; i < kHandles; i++) {
      TestData* test_data = data_vector[i];
      test_data->watcher.Stop();
    }
  }
}

TEST_P(HandleWatcherPerftest, StartAndSignal) {
  const uint64_t kIterations = 10000;
  const std::string kMessage = "hello";
  MessagePipe pipe;
  HandleWatcher watcher;
  std::string received_message;

  ScopedPerfTimer timer("StartAndSignal", GetMessageLoopName(), kIterations);
  for (uint64_t i = 0; i < kIterations; i++) {
    base::RunLoop run_loop;
    watcher.Start(pipe.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE,
                  MOJO_DEADLINE_INDEFINITE,
                  base::Bind(&OnWatcherSignaled, run_loop.QuitClosure()));
    ASSERT_TRUE(mojo::test::WriteTextMessage(pipe.handle1.get(), kMessage));
    run_loop.Run();
    watcher.Stop();

    ASSERT_TRUE(
        mojo::test::ReadTextMessage(pipe.handle0.get(), &received_message));
    EXPECT_EQ(kMessage, received_message);
    received_message.clear();
  }
}

TEST_P(HandleWatcherPerftest, StartAndSignal_1000Waiting) {
  const uint64_t kIterations = 10000;
  const uint64_t kWaitingHandles = 1000;
  const std::string kMessage = "hello";
  MessagePipe pipe;
  HandleWatcher watcher;
  std::string received_message;

  struct TestData {
    MessagePipe pipe;
    HandleWatcher watcher;
  };
  ScopedVector<TestData> data_vector;
  for (uint64_t i = 0; i < kWaitingHandles; i++) {
    std::unique_ptr<TestData> test_data(new TestData);
    ASSERT_TRUE(test_data->pipe.handle0.is_valid());
    test_data->watcher.Start(
        test_data->pipe.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE,
        MOJO_DEADLINE_INDEFINITE, base::Bind(&NeverReached));
    data_vector.push_back(std::move(test_data));
  }

  ScopedPerfTimer timer("StartAndSignal_1000Waiting", GetMessageLoopName(),
                        kIterations);
  for (uint64_t i = 0; i < kIterations; i++) {
    base::RunLoop run_loop;
    watcher.Start(pipe.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE,
                  MOJO_DEADLINE_INDEFINITE,
                  base::Bind(&OnWatcherSignaled, run_loop.QuitClosure()));
    ASSERT_TRUE(mojo::test::WriteTextMessage(pipe.handle1.get(), kMessage));
    run_loop.Run();
    watcher.Stop();

    ASSERT_TRUE(
        mojo::test::ReadTextMessage(pipe.handle0.get(), &received_message));
    EXPECT_EQ(kMessage, received_message);
    received_message.clear();
  }
}

}  // namespace test
}  // namespace common
}  // namespace mojo