// Copyright 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 "extensions/renderer/api_test_base.h"

#include <utility>
#include <vector>

#include "base/location.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "extensions/common/extension_urls.h"
#include "extensions/renderer/dispatcher.h"
#include "extensions/renderer/process_info_native_handler.h"
#include "gin/converter.h"
#include "gin/dictionary.h"
#include "mojo/edk/js/core.h"
#include "mojo/edk/js/handle.h"
#include "mojo/edk/js/support.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "mojo/public/cpp/system/core.h"

namespace extensions {
namespace {

// Natives for the implementation of the unit test version of chrome.test. Calls
// the provided |quit_closure| when either notifyPass or notifyFail is called.
class TestNatives : public gin::Wrappable<TestNatives> {
 public:
  static gin::Handle<TestNatives> Create(v8::Isolate* isolate,
                                         const base::Closure& quit_closure) {
    return gin::CreateHandle(isolate, new TestNatives(quit_closure));
  }

  gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
      v8::Isolate* isolate) override {
    return Wrappable<TestNatives>::GetObjectTemplateBuilder(isolate)
        .SetMethod("Log", &TestNatives::Log)
        .SetMethod("NotifyPass", &TestNatives::NotifyPass)
        .SetMethod("NotifyFail", &TestNatives::NotifyFail);
  }

  void Log(const std::string& value) { logs_ += value + "\n"; }
  void NotifyPass() { FinishTesting(); }

  void NotifyFail(const std::string& message) {
    FinishTesting();
    FAIL() << logs_ << message;
  }

  void FinishTesting() {
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, quit_closure_);
  }

  static gin::WrapperInfo kWrapperInfo;

 private:
  explicit TestNatives(const base::Closure& quit_closure)
      : quit_closure_(quit_closure) {}

  const base::Closure quit_closure_;
  std::string logs_;
};

gin::WrapperInfo TestNatives::kWrapperInfo = {gin::kEmbedderNativeGin};

}  // namespace

gin::WrapperInfo TestInterfaceProvider::kWrapperInfo =
    {gin::kEmbedderNativeGin};

gin::Handle<TestInterfaceProvider> TestInterfaceProvider::Create(
    v8::Isolate* isolate) {
  return gin::CreateHandle(isolate, new TestInterfaceProvider());
}

TestInterfaceProvider::~TestInterfaceProvider() {
}

gin::ObjectTemplateBuilder TestInterfaceProvider::GetObjectTemplateBuilder(
    v8::Isolate* isolate) {
  return Wrappable<TestInterfaceProvider>::GetObjectTemplateBuilder(isolate)
      .SetMethod("getInterface", &TestInterfaceProvider::GetInterface);
}

mojo::Handle TestInterfaceProvider::GetInterface(
    const std::string& interface_name) {
  EXPECT_EQ(1u, factories_.count(interface_name))
      << "Unregistered interface " << interface_name << " requested.";
  mojo::MessagePipe pipe;
  std::map<std::string,
           base::Callback<void(mojo::ScopedMessagePipeHandle)> >::iterator it =
      factories_.find(interface_name);
  if (it != factories_.end())
    it->second.Run(std::move(pipe.handle0));
  return pipe.handle1.release();
}

TestInterfaceProvider::TestInterfaceProvider() {
}

// static
void TestInterfaceProvider::IgnoreHandle(mojo::ScopedMessagePipeHandle handle) {
}

ApiTestEnvironment::ApiTestEnvironment(
    ModuleSystemTestEnvironment* environment) {
  env_ = environment;
  InitializeEnvironment();
  RegisterModules();
}

ApiTestEnvironment::~ApiTestEnvironment() {
}

void ApiTestEnvironment::RegisterModules() {
  v8_schema_registry_.reset(new V8SchemaRegistry);
  const std::vector<std::pair<std::string, int> > resources =
      Dispatcher::GetJsResources();
  for (std::vector<std::pair<std::string, int> >::const_iterator resource =
           resources.begin();
       resource != resources.end();
       ++resource) {
    if (resource->first != "test_environment_specific_bindings")
      env()->RegisterModule(resource->first, resource->second);
  }
  Dispatcher::RegisterNativeHandlers(env()->module_system(),
                                     env()->context(),
                                     NULL,
                                     NULL,
                                     v8_schema_registry_.get());
  env()->module_system()->RegisterNativeHandler(
      "process", std::unique_ptr<NativeHandler>(new ProcessInfoNativeHandler(
                     env()->context(), env()->context()->GetExtensionID(),
                     env()->context()->GetContextTypeDescription(), false,
                     false, 2, false)));
  env()->RegisterTestFile("test_environment_specific_bindings",
                          "unit_test_environment_specific_bindings.js");

  env()->OverrideNativeHandler("activityLogger",
                               "exports.$set('LogAPICall', function() {});");
  env()->OverrideNativeHandler(
      "apiDefinitions",
      "exports.$set('GetExtensionAPIDefinitionsForTest',"
                    "function() { return [] });");
  env()->OverrideNativeHandler(
      "event_natives",
      "exports.$set('AttachEvent', function() {});"
      "exports.$set('DetachEvent', function() {});"
      "exports.$set('AttachFilteredEvent', function() {});"
      "exports.$set('AttachFilteredEvent', function() {});"
      "exports.$set('MatchAgainstEventFilter', function() { return [] });");

  gin::ModuleRegistry::From(env()->context()->v8_context())
      ->AddBuiltinModule(env()->isolate(), mojo::edk::js::Core::kModuleName,
                         mojo::edk::js::Core::GetModule(env()->isolate()));
  gin::ModuleRegistry::From(env()->context()->v8_context())
      ->AddBuiltinModule(env()->isolate(), mojo::edk::js::Support::kModuleName,
                         mojo::edk::js::Support::GetModule(env()->isolate()));
  gin::Handle<TestInterfaceProvider> interface_provider =
    TestInterfaceProvider::Create(env()->isolate());
  interface_provider_ = interface_provider.get();
  gin::ModuleRegistry::From(env()->context()->v8_context())
      ->AddBuiltinModule(env()->isolate(),
                         "content/public/renderer/frame_interfaces",
                         interface_provider.ToV8());
}

void ApiTestEnvironment::InitializeEnvironment() {
  gin::Dictionary global(env()->isolate(),
                         env()->context()->v8_context()->Global());
  gin::Dictionary navigator(gin::Dictionary::CreateEmpty(env()->isolate()));
  navigator.Set("appVersion", base::StringPiece(""));
  global.Set("navigator", navigator);
  gin::Dictionary chrome(gin::Dictionary::CreateEmpty(env()->isolate()));
  global.Set("chrome", chrome);
  gin::Dictionary extension(gin::Dictionary::CreateEmpty(env()->isolate()));
  chrome.Set("extension", extension);
  gin::Dictionary runtime(gin::Dictionary::CreateEmpty(env()->isolate()));
  chrome.Set("runtime", runtime);
}

void ApiTestEnvironment::RunTest(const std::string& file_name,
                                 const std::string& test_name) {
  env()->RegisterTestFile("testBody", file_name);
  base::RunLoop run_loop;
  gin::ModuleRegistry::From(env()->context()->v8_context())->AddBuiltinModule(
      env()->isolate(),
      "testNatives",
      TestNatives::Create(env()->isolate(), run_loop.QuitClosure()).ToV8());
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE,
      base::Bind(&ApiTestEnvironment::RunTestInner, base::Unretained(this),
                 test_name, run_loop.QuitClosure()));
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE, base::Bind(&ApiTestEnvironment::RunPromisesAgain,
                            base::Unretained(this)));
  run_loop.Run();
}

void ApiTestEnvironment::RunTestInner(const std::string& test_name,
                                      const base::Closure& quit_closure) {
  v8::HandleScope scope(env()->isolate());
  ModuleSystem::NativesEnabledScope natives_enabled(env()->module_system());
  v8::Local<v8::Value> result =
      env()->module_system()->CallModuleMethod("testBody", test_name);
  if (!result->IsTrue()) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, quit_closure);
    FAIL() << "Failed to run test \"" << test_name << "\"";
  }
}

void ApiTestEnvironment::RunPromisesAgain() {
  v8::MicrotasksScope::PerformCheckpoint(env()->isolate());
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE, base::Bind(&ApiTestEnvironment::RunPromisesAgain,
                            base::Unretained(this)));
}

ApiTestBase::ApiTestBase() {
}

ApiTestBase::~ApiTestBase() {
}

void ApiTestBase::SetUp() {
  ModuleSystemTest::SetUp();
  test_env_.reset(new ApiTestEnvironment(env()));
}

void ApiTestBase::RunTest(const std::string& file_name,
                          const std::string& test_name) {
  ExpectNoAssertionsMade();
  test_env_->RunTest(file_name, test_name);
}

}  // namespace extensions
