namespace content {
namespace {

const uint16_t kDummyPort = 4321;
const base::FilePath::CharType kDevToolsActivePortFileName[] =
    FILE_PATH_LITERAL("DevToolsActivePort");

class DummyServerSocket : public net::ServerSocket {
 public:
  DummyServerSocket() {}

  // net::ServerSocket "implementation"
  int Listen(const net::IPEndPoint& address, int backlog) override {
    return net::OK;
  }

  int GetLocalAddress(net::IPEndPoint* address) const override {
    *address = net::IPEndPoint(net::IPAddress::IPv4Localhost(), kDummyPort);
    return net::OK;
  }

  int Accept(std::unique_ptr<net::StreamSocket>* socket,
             const net::CompletionCallback& callback) override {
    return net::ERR_IO_PENDING;
  }
};

void QuitFromHandlerThread(const base::Closure& quit_closure) {
  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, quit_closure);
}

class DummyServerSocketFactory : public DevToolsSocketFactory {
 public:
  DummyServerSocketFactory(base::Closure quit_closure_1,
                           base::Closure quit_closure_2)
      : quit_closure_1_(quit_closure_1),
        quit_closure_2_(quit_closure_2) {}

  ~DummyServerSocketFactory() override {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE, quit_closure_2_);
  }

 protected:
  std::unique_ptr<net::ServerSocket> CreateForHttpServer() override {
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::Bind(&QuitFromHandlerThread, quit_closure_1_));
    return base::WrapUnique(new DummyServerSocket());
  }

  std::unique_ptr<net::ServerSocket> CreateForTethering(
      std::string* out_name) override {
    return nullptr;
  }

  base::Closure quit_closure_1_;
  base::Closure quit_closure_2_;
};

class FailingServerSocketFactory : public DummyServerSocketFactory {
 public:
  FailingServerSocketFactory(const base::Closure& quit_closure_1,
                             const base::Closure& quit_closure_2)
      : DummyServerSocketFactory(quit_closure_1, quit_closure_2) {
  }

 private:
  std::unique_ptr<net::ServerSocket> CreateForHttpServer() override {
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::Bind(&QuitFromHandlerThread, quit_closure_1_));
    return nullptr;
  }
};

class BrowserClient : public ContentBrowserClient {
 public:
  BrowserClient() {}
  ~BrowserClient() override {}
  DevToolsManagerDelegate* GetDevToolsManagerDelegate() override {
    return new DevToolsManagerDelegate();
  }
};

}

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

  void SetUp() override {
    content_client_.reset(new ContentClient());
    browser_content_client_.reset(new BrowserClient());
    SetBrowserClientForTesting(browser_content_client_.get());
  }

 private:
  std::unique_ptr<ContentClient> content_client_;
  std::unique_ptr<ContentBrowserClient> browser_content_client_;
  content::TestBrowserThreadBundle thread_bundle_;
};

TEST_F(DevToolsHttpHandlerTest, TestStartStop) {
  base::RunLoop run_loop, run_loop_2;
  std::unique_ptr<DevToolsSocketFactory> factory(
      new DummyServerSocketFactory(run_loop.QuitClosure(),
                                   run_loop_2.QuitClosure()));
  DevToolsAgentHost::StartRemoteDebuggingServer(
      std::move(factory), std::string(), base::FilePath(), base::FilePath(),
      std::string(), std::string());
  // Our dummy socket factory will post a quit message once the server will
  // become ready.
  run_loop.Run();
  DevToolsAgentHost::StopRemoteDebuggingServer();
  // Make sure the handler actually stops.
  run_loop_2.Run();
}

TEST_F(DevToolsHttpHandlerTest, TestServerSocketFailed) {
  base::RunLoop run_loop, run_loop_2;
  std::unique_ptr<DevToolsSocketFactory> factory(
      new FailingServerSocketFactory(run_loop.QuitClosure(),
                                     run_loop_2.QuitClosure()));
  DevToolsAgentHost::StartRemoteDebuggingServer(
      std::move(factory), std::string(), base::FilePath(), base::FilePath(),
      std::string(), std::string());
  // Our dummy socket factory will post a quit message once the server will
  // become ready.
  run_loop.Run();
  for (int i = 0; i < 5; i++) {
    RunAllPendingInMessageLoop(BrowserThread::UI);
    RunAllPendingInMessageLoop(BrowserThread::FILE);
  }
  DevToolsAgentHost::StopRemoteDebuggingServer();
  // Make sure the handler actually stops.
  run_loop_2.Run();
}


TEST_F(DevToolsHttpHandlerTest, TestDevToolsActivePort) {
  base::RunLoop run_loop, run_loop_2;
  base::ScopedTempDir temp_dir;
  EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
  std::unique_ptr<DevToolsSocketFactory> factory(
      new DummyServerSocketFactory(run_loop.QuitClosure(),
                                   run_loop_2.QuitClosure()));

  DevToolsAgentHost::StartRemoteDebuggingServer(
      std::move(factory), std::string(), temp_dir.GetPath(), base::FilePath(),
      std::string(), std::string());
  // Our dummy socket factory will post a quit message once the server will
  // become ready.
  run_loop.Run();
  DevToolsAgentHost::StopRemoteDebuggingServer();
  // Make sure the handler actually stops.
  run_loop_2.Run();

  // Now make sure the DevToolsActivePort was written into the
  // temporary directory and its contents are as expected.
  base::FilePath active_port_file =
      temp_dir.GetPath().Append(kDevToolsActivePortFileName);
  EXPECT_TRUE(base::PathExists(active_port_file));
  std::string file_contents;
  EXPECT_TRUE(base::ReadFileToString(active_port_file, &file_contents));
  int port = 0;
  EXPECT_TRUE(base::StringToInt(file_contents, &port));
  EXPECT_EQ(static_cast<int>(kDummyPort), port);
}

}  // namespace content
