// Copyright (c) 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/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/test_simple_task_runner.h"
#include "content/common/gpu/gpu_channel.h"
#include "content/common/gpu/gpu_channel_manager.h"
#include "content/common/gpu/gpu_messages.h"
#include "content/common/message_router.h"
#include "gpu/command_buffer/common/value_state.h"
#include "gpu/command_buffer/service/gl_utils.h"
#include "gpu/command_buffer/service/valuebuffer_manager.h"
#include "ipc/ipc_sync_channel.h"

#include "testing/gtest/include/gtest/gtest.h"

using base::WaitableEvent;
using gpu::gles2::ValuebufferManager;
using gpu::ValueState;

namespace IPC {

class SimpleWorker : public Listener, public Sender {
 public:
  SimpleWorker(Channel::Mode mode, const std::string& thread_name)
      : mode_(mode),
        ipc_thread_((thread_name + "_ipc").c_str()),
        shutdown_event_(true, false) {
  }

  ~SimpleWorker() override {
  }

  void AddRef() { }
  void Release() { }
  bool Send(Message* msg) override { return channel_->Send(msg); }

  virtual void Start() {
    StartThread(&ipc_thread_, base::MessageLoop::TYPE_IO);
    channel_.reset(CreateChannel());
  }

  virtual void Shutdown() {
    WaitableEvent ipc_done(false, false);
    ipc_thread_.task_runner()->PostTask(
        FROM_HERE, base::Bind(&SimpleWorker::OnShutdown, this, &ipc_done));

    channel_.reset();

    ipc_done.Wait();
    ipc_thread_.Stop();
  }

 protected:
  SyncChannel* CreateChannel() {
    scoped_ptr<SyncChannel> channel = SyncChannel::Create(
        channel_name_, mode_, this, ipc_thread_.task_runner().get(), true,
        &shutdown_event_, nullptr);
    return channel.release();
  }

  void OnShutdown(WaitableEvent* ipc_event) {
    base::RunLoop().RunUntilIdle();
    ipc_event->Signal();
  }

  const base::Thread& ipc_thread() const { return ipc_thread_; }

  WaitableEvent* shutdown_event() { return &shutdown_event_; }

  SyncChannel* channel() { return channel_.get(); }

 private:
  bool OnMessageReceived(const Message& message) override { return false; }

  void StartThread(base::Thread* thread, base::MessageLoop::Type type) {
    base::Thread::Options options;
    options.message_loop_type = type;
    thread->StartWithOptions(options);
  }

  std::string channel_name_;
  Channel::Mode mode_;
  scoped_ptr<SyncChannel> channel_;
  base::Thread ipc_thread_;

  base::WaitableEvent shutdown_event_;

  DISALLOW_COPY_AND_ASSIGN(SimpleWorker);
};

class SimpleServer : public SimpleWorker {
 public:
  explicit SimpleServer()
    : SimpleWorker(Channel::MODE_SERVER, "simpler_server") { }
};

} // namespace IPC

namespace content {

class SimpleGpuClient : public IPC::SimpleWorker {
 public:
  SimpleGpuClient()
   : IPC::SimpleWorker(IPC::Channel::MODE_CLIENT, "simple_client"),
     router_(this) {
  }

  void Start() override {
    IPC::SimpleWorker::Start();
    gpu_channel_manager_.reset(
        new GpuChannelManager(&router_, NULL, ipc_thread().task_runner().get(),
                              shutdown_event(), channel(), nullptr, nullptr));
  }

  void Shutdown() override {
    gpu_channel_manager_.reset();
    IPC::SimpleWorker::Shutdown();
  }

  GpuChannelManager* gpu_channel_manager() {
    return gpu_channel_manager_.get();
  }

 private:
  class SimpleMessageRouter : public MessageRouter {
   public:
    explicit SimpleMessageRouter(IPC::Sender* sender)
      : sender_(sender) {
    }

    bool Send(IPC::Message* msg) override { return sender_->Send(msg); }

   private:
    IPC::Sender* const sender_;
  };

  SimpleMessageRouter router_;

  scoped_ptr<GpuChannelManager> gpu_channel_manager_;
};

class GpuChannelManagerTest : public testing::Test {
 public:
  GpuChannelManagerTest() {}

  void SetUp() override {
    simple_client_.reset(new SimpleGpuClient());
    simple_server_.reset(new IPC::SimpleServer());
    simple_server_->Start();
    simple_client_->Start();
  }

  void TearDown() override {
    simple_client_->Shutdown();
    simple_server_->Shutdown();
  }

 protected:
  scoped_ptr<SimpleGpuClient> simple_client_;
  scoped_ptr<IPC::SimpleServer> simple_server_;

 private:
  base::MessageLoop message_loop_;
};

TEST_F(GpuChannelManagerTest, SecureValueStateForwarding) {
  const int kClientId1 = 111;
  const int kClientId2 = 222;
  ValueState value_state1;
  value_state1.int_value[0] = 1111;
  value_state1.int_value[1] = 0;
  value_state1.int_value[2] = 0;
  value_state1.int_value[3] = 0;
  ValueState value_state2;
  value_state2.int_value[0] = 3333;
  value_state2.int_value[1] = 0;
  value_state2.int_value[2] = 0;
  value_state2.int_value[3] = 0;

  ASSERT_TRUE(simple_client_->gpu_channel_manager() != NULL);

  // Initialize gpu channels
  simple_client_->gpu_channel_manager()->OnMessageReceived(
      GpuMsg_EstablishChannel(kClientId1, false, false));
  GpuChannel *channel1 =
      simple_client_->gpu_channel_manager()->LookupChannel(kClientId1);
  simple_client_->gpu_channel_manager()->OnMessageReceived(
      GpuMsg_EstablishChannel(kClientId2, false, false));
  GpuChannel *channel2 =
      simple_client_->gpu_channel_manager()->LookupChannel(kClientId2);
  ASSERT_TRUE(channel1 != NULL);
  ASSERT_TRUE(channel2 != NULL);
  ASSERT_NE(channel1, channel2);

  // Make sure value states are only accessible by proper channels
  simple_client_->gpu_channel_manager()->OnMessageReceived(
      GpuMsg_UpdateValueState(
          kClientId1, GL_MOUSE_POSITION_CHROMIUM, value_state1));
  simple_client_->gpu_channel_manager()->OnMessageReceived(
      GpuMsg_UpdateValueState(
          kClientId2, GL_MOUSE_POSITION_CHROMIUM, value_state2));

  const gpu::ValueStateMap* pending_value_buffer_state1 =
      channel1->pending_valuebuffer_state();
  const gpu::ValueStateMap* pending_value_buffer_state2 =
      channel2->pending_valuebuffer_state();
  ASSERT_NE(pending_value_buffer_state1, pending_value_buffer_state2);

  const ValueState* state1 =
      pending_value_buffer_state1->GetState(GL_MOUSE_POSITION_CHROMIUM);
  const ValueState* state2 =
      pending_value_buffer_state2->GetState(GL_MOUSE_POSITION_CHROMIUM);
  ASSERT_NE(state1, state2);

  ASSERT_EQ(state1->int_value[0], value_state1.int_value[0]);
  ASSERT_EQ(state2->int_value[0], value_state2.int_value[0]);
  ASSERT_NE(state1->int_value[0], state2->int_value[0]);
}

}  // namespace content
