// Copyright (c) 2012 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/bind.h"
#include "base/bind_helpers.h"
#include "base/message_loop/message_loop.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "ipc/ipc_message.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/host/host_message_context.h"
#include "ppapi/host/resource_host.h"
#include "ppapi/host/resource_message_filter.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ppapi {
namespace host {
namespace {

base::WaitableEvent g_handler_completion(true, false);

enum TestMessageTypes {
  MSG1_TYPE = 1,
  MSG2_TYPE,
  MSG3_TYPE,
  REPLY_MSG1_TYPE,
  REPLY_MSG2_TYPE,
  REPLY_MSG3_TYPE,
};

// Dummy resource host which simply stores a copy of messages it handles.
// |SendReply| is overridden to store a copy of the outgoing message and the
// message loop on which it was sent.
class MyResourceHost : public ResourceHost {
 public:
  // Messages of type |msg_type| will be handled (simply by replying with a
  // message of type |reply_msg_type|).
  MyResourceHost(PpapiHost* host,
                 PP_Instance instance,
                 PP_Resource resource,
                 uint32 msg_type,
                 uint32 reply_msg_type)
      : ResourceHost(host, instance, resource),
        msg_type_(msg_type),
        reply_msg_type_(reply_msg_type),
        last_reply_message_loop_(NULL) {
  }

  const IPC::Message& last_handled_msg() const { return last_handled_msg_; }
  const IPC::Message& last_reply_msg() const { return last_reply_msg_; }
  base::MessageLoop* last_reply_message_loop() const {
    return last_reply_message_loop_;
  }

  void AddMessageFilter(scoped_refptr<ResourceMessageFilter> filter) {
    AddFilter(filter);
  }

  virtual int32_t OnResourceMessageReceived(
      const IPC::Message& msg,
      HostMessageContext* context) override {
    last_handled_msg_ = msg;
    if (msg.type() == msg_type_) {
      context->reply_msg = IPC::Message(0, reply_msg_type_,
                                        IPC::Message::PRIORITY_NORMAL);
      return PP_OK;
    }
    return PP_ERROR_FAILED;
  }

  virtual void SendReply(const ReplyMessageContext& context,
                         const IPC::Message& msg) override {
    last_reply_msg_ = msg;
    last_reply_message_loop_ = base::MessageLoop::current();
    g_handler_completion.Signal();
  }

 private:
  uint32 msg_type_;
  uint32 reply_msg_type_;

  IPC::Message last_handled_msg_;
  IPC::Message last_reply_msg_;
  base::MessageLoop* last_reply_message_loop_;
};

// Dummy message filter which simply stores a copy of messages it handles.
// The message loop on which the message is handled is also stored for checking
// later.
class MyResourceFilter : public ResourceMessageFilter {
 public:
  // Messages of type |msg_type| will be handled (simply by replying with a
  // message of type |reply_msg_type|). |io_thread| is the thread on which
  // replies should be sent. |bg_thread| is the thread on which the message
  // should be handled.
  MyResourceFilter(const base::Thread& io_thread,
                   const base::Thread& bg_thread,
                   uint32 msg_type,
                   uint32 reply_msg_type)
      : ResourceMessageFilter(io_thread.message_loop_proxy()),
        message_loop_proxy_(bg_thread.message_loop_proxy()),
        msg_type_(msg_type),
        reply_msg_type_(reply_msg_type),
        last_message_loop_(NULL) {
  }

  const IPC::Message& last_handled_msg() const { return last_handled_msg_; }
  base::MessageLoop* last_message_loop() const { return last_message_loop_; }

  virtual scoped_refptr<base::TaskRunner> OverrideTaskRunnerForMessage(
      const IPC::Message& msg) override {
    if (msg.type() == msg_type_)
      return message_loop_proxy_;
    return NULL;
  }

  virtual int32_t OnResourceMessageReceived(
      const IPC::Message& msg,
      HostMessageContext* context) override {
    last_handled_msg_ = msg;
    last_message_loop_ = base::MessageLoop::current();
    if (msg.type() == msg_type_) {
      context->reply_msg = IPC::Message(0, reply_msg_type_,
                                        IPC::Message::PRIORITY_NORMAL);
      return PP_OK;
    }
    return PP_ERROR_FAILED;
  }

 private:
  scoped_refptr<base::MessageLoopProxy> message_loop_proxy_;
  uint32 msg_type_;
  uint32 reply_msg_type_;

  IPC::Message last_handled_msg_;
  base::MessageLoop* last_message_loop_;
};

}  // namespace

class ResourceMessageFilterTest : public testing::Test {
 public:
  void TestHandleMessageImpl() {
    base::Thread io_thread("test_io_thread");
    ASSERT_TRUE(io_thread.Start());

    base::Thread bg_thread1("test_background_thread1");
    ASSERT_TRUE(bg_thread1.Start());
    scoped_refptr<MyResourceFilter> filter1 =
        new MyResourceFilter(io_thread, bg_thread1, MSG1_TYPE, REPLY_MSG1_TYPE);

    base::Thread bg_thread2("test_background_thread2");
    ASSERT_TRUE(bg_thread2.Start());
    scoped_refptr<MyResourceFilter> filter2 =
        new MyResourceFilter(io_thread, bg_thread2, MSG2_TYPE, REPLY_MSG2_TYPE);

    PP_Instance instance = 12345;
    PP_Resource resource = 67890;
    MyResourceHost host(NULL, instance, resource, MSG3_TYPE, REPLY_MSG3_TYPE);
    host.AddMessageFilter(filter1);
    host.AddMessageFilter(filter2);

    proxy::ResourceMessageCallParams params(resource, 1);
    params.set_has_callback();
    HostMessageContext context(params);
    IPC::Message message1(0, MSG1_TYPE, IPC::Message::PRIORITY_NORMAL);
    IPC::Message message2(0, MSG2_TYPE, IPC::Message::PRIORITY_NORMAL);
    IPC::Message message3(0, MSG3_TYPE, IPC::Message::PRIORITY_NORMAL);

    // Message 1 handled by the first filter.
    host.HandleMessage(message1, &context);
    g_handler_completion.Wait();
    EXPECT_EQ(filter1->last_handled_msg().type(), message1.type());
    EXPECT_EQ(filter1->last_message_loop(), bg_thread1.message_loop());
    EXPECT_EQ(host.last_reply_msg().type(),
              static_cast<uint32>(REPLY_MSG1_TYPE));
    EXPECT_EQ(host.last_reply_message_loop(), io_thread.message_loop());
    g_handler_completion.Reset();

    // Message 2 handled by the second filter.
    host.HandleMessage(message2, &context);
    g_handler_completion.Wait();
    EXPECT_EQ(filter2->last_handled_msg().type(), message2.type());
    EXPECT_EQ(filter2->last_message_loop(), bg_thread2.message_loop());
    EXPECT_EQ(host.last_reply_msg().type(),
              static_cast<uint32>(REPLY_MSG2_TYPE));
    EXPECT_EQ(host.last_reply_message_loop(), io_thread.message_loop());
    g_handler_completion.Reset();

    // Message 3 handled by the resource host.
    host.HandleMessage(message3, &context);
    EXPECT_EQ(host.last_handled_msg().type(), message3.type());
    EXPECT_EQ(host.last_reply_msg().type(),
              static_cast<uint32>(REPLY_MSG3_TYPE));

    io_thread.Stop();
    bg_thread1.Stop();
    bg_thread2.Stop();
  }
};

// Test that messages are filtered correctly and handlers are run on the correct
// threads.
TEST_F(ResourceMessageFilterTest, TestHandleMessage) {
  // ResourceMessageFilter instances need to be created on a thread with message
  // loop. Therefore, we create a message loop and run the testing logic as a
  // task on it.
  base::MessageLoop main_message_loop;

  // It should be safe to use base::Unretained() because the object won't be
  // destroyed before the task is run.
  main_message_loop.PostTask(
      FROM_HERE,
      base::Bind(&ResourceMessageFilterTest::TestHandleMessageImpl,
                 base::Unretained(this)));

  base::RunLoop().RunUntilIdle();
}

}  // namespace proxy
}  // namespace ppapi
