// Copyright 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 <stdint.h>
#include <utility>

#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
#include "mojo/public/cpp/system/wait.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "mojo/public/interfaces/bindings/tests/sample_factory.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace mojo {
namespace test {
namespace {

const char kText1[] = "hello";
const char kText2[] = "world";

void RecordString(std::string* storage,
                  const base::Closure& closure,
                  const std::string& str) {
  *storage = str;
  closure.Run();
}

base::Callback<void(const std::string&)> MakeStringRecorder(
    std::string* storage,
    const base::Closure& closure) {
  return base::Bind(&RecordString, storage, closure);
}

class ImportedInterfaceImpl : public imported::ImportedInterface {
 public:
  ImportedInterfaceImpl(
      InterfaceRequest<imported::ImportedInterface> request,
      const base::Closure& closure)
      : binding_(this, std::move(request)), closure_(closure) {}

  void DoSomething() override {
    do_something_count_++;
    closure_.Run();
  }

  static int do_something_count() { return do_something_count_; }

 private:
  static int do_something_count_;
  Binding<ImportedInterface> binding_;
  base::Closure closure_;
};
int ImportedInterfaceImpl::do_something_count_ = 0;

class SampleNamedObjectImpl : public sample::NamedObject {
 public:
  SampleNamedObjectImpl() {}

  void SetName(const std::string& name) override { name_ = name; }

  void GetName(const GetNameCallback& callback) override {
    callback.Run(name_);
  }

 private:
  std::string name_;
};

class SampleFactoryImpl : public sample::Factory {
 public:
  explicit SampleFactoryImpl(InterfaceRequest<sample::Factory> request)
      : binding_(this, std::move(request)) {}

  void DoStuff(sample::RequestPtr request,
               ScopedMessagePipeHandle pipe,
               const DoStuffCallback& callback) override {
    std::string text1;
    if (pipe.is_valid())
      EXPECT_TRUE(ReadTextMessage(pipe.get(), &text1));

    std::string text2;
    if (request->pipe.is_valid()) {
      EXPECT_TRUE(ReadTextMessage(request->pipe.get(), &text2));

      // Ensure that simply accessing request->pipe does not close it.
      EXPECT_TRUE(request->pipe.is_valid());
    }

    ScopedMessagePipeHandle pipe0;
    if (!text2.empty()) {
      CreateMessagePipe(nullptr, &pipe0, &pipe1_);
      EXPECT_TRUE(WriteTextMessage(pipe1_.get(), text2));
    }

    sample::ResponsePtr response(sample::Response::New(2, std::move(pipe0)));
    callback.Run(std::move(response), text1);

    if (request->obj) {
      imported::ImportedInterfacePtr proxy(std::move(request->obj));
      proxy->DoSomething();
    }
  }

  void DoStuff2(ScopedDataPipeConsumerHandle pipe,
                const DoStuff2Callback& callback) override {
    // Read the data from the pipe, writing the response (as a string) to
    // DidStuff2().
    ASSERT_TRUE(pipe.is_valid());
    uint32_t data_size = 0;

    MojoHandleSignalsState state;
    ASSERT_EQ(MOJO_RESULT_OK,
              mojo::Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE, &state));
    ASSERT_TRUE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
    ASSERT_EQ(MOJO_RESULT_OK,
              pipe->ReadData(nullptr, &data_size, MOJO_READ_DATA_FLAG_QUERY));
    ASSERT_NE(0, static_cast<int>(data_size));
    char data[64];
    ASSERT_LT(static_cast<int>(data_size), 64);
    ASSERT_EQ(MOJO_RESULT_OK, pipe->ReadData(data, &data_size,
                                             MOJO_READ_DATA_FLAG_ALL_OR_NONE));

    callback.Run(data);
  }

  void CreateNamedObject(
      InterfaceRequest<sample::NamedObject> object_request) override {
    EXPECT_TRUE(object_request.is_pending());
    MakeStrongBinding(std::make_unique<SampleNamedObjectImpl>(),
                      std::move(object_request));
  }

  // These aren't called or implemented, but exist here to test that the
  // methods are generated with the correct argument types for imported
  // interfaces.
  void RequestImportedInterface(
      InterfaceRequest<imported::ImportedInterface> imported,
      const RequestImportedInterfaceCallback& callback) override {}
  void TakeImportedInterface(
      imported::ImportedInterfacePtr imported,
      const TakeImportedInterfaceCallback& callback) override {}

 private:
  ScopedMessagePipeHandle pipe1_;
  Binding<sample::Factory> binding_;
};

class HandlePassingTest : public BindingsTestBase {
 public:
  HandlePassingTest() {}

  void TearDown() override { PumpMessages(); }

  void PumpMessages() { base::RunLoop().RunUntilIdle(); }

};

void DoStuff(bool* got_response,
             std::string* got_text_reply,
             const base::Closure& closure,
             sample::ResponsePtr response,
             const std::string& text_reply) {
  *got_text_reply = text_reply;

  if (response->pipe.is_valid()) {
    std::string text2;
    EXPECT_TRUE(ReadTextMessage(response->pipe.get(), &text2));

    // Ensure that simply accessing response.pipe does not close it.
    EXPECT_TRUE(response->pipe.is_valid());

    EXPECT_EQ(std::string(kText2), text2);

    // Do some more tests of handle passing:
    ScopedMessagePipeHandle p = std::move(response->pipe);
    EXPECT_TRUE(p.is_valid());
    EXPECT_FALSE(response->pipe.is_valid());
  }

  *got_response = true;
  closure.Run();
}

void DoStuff2(bool* got_response,
              std::string* got_text_reply,
              const base::Closure& closure,
              const std::string& text_reply) {
  *got_response = true;
  *got_text_reply = text_reply;
  closure.Run();
}

TEST_P(HandlePassingTest, Basic) {
  sample::FactoryPtr factory;
  SampleFactoryImpl factory_impl(MakeRequest(&factory));

  MessagePipe pipe0;
  EXPECT_TRUE(WriteTextMessage(pipe0.handle1.get(), kText1));

  MessagePipe pipe1;
  EXPECT_TRUE(WriteTextMessage(pipe1.handle1.get(), kText2));

  imported::ImportedInterfacePtrInfo imported;
  base::RunLoop run_loop;
  ImportedInterfaceImpl imported_impl(MakeRequest(&imported),
                                      run_loop.QuitClosure());

  sample::RequestPtr request(sample::Request::New(
      1, std::move(pipe1.handle0), base::nullopt, std::move(imported)));
  bool got_response = false;
  std::string got_text_reply;
  base::RunLoop run_loop2;
  factory->DoStuff(std::move(request), std::move(pipe0.handle0),
                   base::Bind(&DoStuff, &got_response, &got_text_reply,
                              run_loop2.QuitClosure()));

  EXPECT_FALSE(got_response);
  int count_before = ImportedInterfaceImpl::do_something_count();

  run_loop.Run();
  run_loop2.Run();

  EXPECT_TRUE(got_response);
  EXPECT_EQ(kText1, got_text_reply);
  EXPECT_EQ(1, ImportedInterfaceImpl::do_something_count() - count_before);
}

TEST_P(HandlePassingTest, PassInvalid) {
  sample::FactoryPtr factory;
  SampleFactoryImpl factory_impl(MakeRequest(&factory));

  sample::RequestPtr request(sample::Request::New(1, ScopedMessagePipeHandle(),
                                                  base::nullopt, nullptr));

  bool got_response = false;
  std::string got_text_reply;
  base::RunLoop run_loop;
  factory->DoStuff(std::move(request), ScopedMessagePipeHandle(),
                   base::Bind(&DoStuff, &got_response, &got_text_reply,
                              run_loop.QuitClosure()));

  EXPECT_FALSE(got_response);

  run_loop.Run();

  EXPECT_TRUE(got_response);
}

// Verifies DataPipeConsumer can be passed and read from.
TEST_P(HandlePassingTest, DataPipe) {
  sample::FactoryPtr factory;
  SampleFactoryImpl factory_impl(MakeRequest(&factory));

  // Writes a string to a data pipe and passes the data pipe (consumer) to the
  // factory.
  ScopedDataPipeProducerHandle producer_handle;
  ScopedDataPipeConsumerHandle consumer_handle;
  MojoCreateDataPipeOptions options = {sizeof(MojoCreateDataPipeOptions),
                                       MOJO_CREATE_DATA_PIPE_FLAG_NONE, 1,
                                       1024};
  ASSERT_EQ(MOJO_RESULT_OK,
            CreateDataPipe(&options, &producer_handle, &consumer_handle));
  std::string expected_text_reply = "got it";
  // +1 for \0.
  uint32_t data_size = static_cast<uint32_t>(expected_text_reply.size() + 1);
  ASSERT_EQ(MOJO_RESULT_OK,
            producer_handle->WriteData(expected_text_reply.c_str(), &data_size,
                                       MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));

  bool got_response = false;
  std::string got_text_reply;
  base::RunLoop run_loop;
  factory->DoStuff2(std::move(consumer_handle),
                    base::Bind(&DoStuff2, &got_response, &got_text_reply,
                               run_loop.QuitClosure()));

  EXPECT_FALSE(got_response);

  run_loop.Run();

  EXPECT_TRUE(got_response);
  EXPECT_EQ(expected_text_reply, got_text_reply);
}

TEST_P(HandlePassingTest, CreateNamedObject) {
  sample::FactoryPtr factory;
  SampleFactoryImpl factory_impl(MakeRequest(&factory));

  sample::NamedObjectPtr object1;
  EXPECT_FALSE(object1);

  auto object1_request = mojo::MakeRequest(&object1);
  EXPECT_TRUE(object1_request.is_pending());
  factory->CreateNamedObject(std::move(object1_request));
  EXPECT_FALSE(object1_request.is_pending());  // We've passed the request.

  ASSERT_TRUE(object1);
  object1->SetName("object1");

  sample::NamedObjectPtr object2;
  factory->CreateNamedObject(MakeRequest(&object2));
  object2->SetName("object2");

  base::RunLoop run_loop, run_loop2;
  std::string name1;
  object1->GetName(MakeStringRecorder(&name1, run_loop.QuitClosure()));

  std::string name2;
  object2->GetName(MakeStringRecorder(&name2, run_loop2.QuitClosure()));

  run_loop.Run();
  run_loop2.Run();

  EXPECT_EQ(std::string("object1"), name1);
  EXPECT_EQ(std::string("object2"), name2);
}

INSTANTIATE_MOJO_BINDINGS_TEST_CASE_P(HandlePassingTest);

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