// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/command_line.h"
#include "base/logging.h"
#include "base/message_loop/message_pump.h"
#include "base/notreached.h"
#include "base/process/launch.h"
#include "base/run_loop.h"
#include "base/task/sequence_manager/sequence_manager.h"
#include "base/threading/thread.h"
#include "codelabs/mojo_examples/mojom/interface.mojom.h"
#include "codelabs/mojo_examples/process_bootstrapper.h"
#include "ipc/ipc_channel_proxy.h"
#include "mojo/core/embedder/embedder.h"
#include "mojo/core/embedder/scoped_ipc_support.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/system/invitation.h"
#include "mojo/public/cpp/system/message_pipe.h"

mojo::ScopedMessagePipeHandle LaunchAndConnect() {
  // Under the hood, this is essentially always an OS pipe (domain socket pair,
  // Windows named pipe, Fuchsia channel, etc).
  mojo::PlatformChannel channel;

  mojo::OutgoingInvitation invitation;

  // Attach a message pipe to be extracted by the receiver. The other end of the
  // pipe is returned for us to use locally.
  mojo::ScopedMessagePipeHandle ipc_bootstrap_pipe =
      invitation.AttachMessagePipe("ipc_bootstrap_pipe");

  base::LaunchOptions options;
  // This is the relative path to the mock "renderer process" binary. We pass it
  // into `base::LaunchProcess` to run the binary in a new process.
  static const base::CommandLine::CharType* argv[] = {
      FILE_PATH_LITERAL("./03-mojo-renderer")};
  base::CommandLine command_line(1, argv);
  channel.PrepareToPassRemoteEndpoint(&options, &command_line);
  LOG(INFO) << "Browser: " << command_line.GetCommandLineString();
  base::Process child_process = base::LaunchProcess(command_line, options);
  channel.RemoteProcessLaunchAttempted();

  mojo::OutgoingInvitation::Send(std::move(invitation), child_process.Handle(),
                                 channel.TakeLocalEndpoint());
  return ipc_bootstrap_pipe;
}

class BrowserIPCListener : public IPC::Listener {
 public:
  BrowserIPCListener(mojo::ScopedMessagePipeHandle ipc_bootstrap_pipe,
                     scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
      : IPC::Listener() {
    // This program differs from `02-associated-interface-freezing`, because
    // we're using channel-associated interfaces as opposed to non-channel
    // associated interfaces. This means we need to set up an
    // `IPC::ChannelProxy` in addition to the regular mojo stuff. The sequence
    // of events will look like so:
    //   1.) Bootstrap the IPC channel. This is actually pretty easy. We just a
    //       pull a message pipe handle off of the mojo invitation we sent to
    //       the renderer process earlier, and feed that pipe handle into the
    //       IPC::ChannelProxy. From there, we can start requesting remote
    //       associated interfaces directly from the IPC channel.
    //   2.) Requesting a remote channel-associated interface from the
    //       IPC::Channel mojo connection, for interface
    //       `codelabs::mojom::ObjectA`
    //   3.) Do the same thing as (2) but for `codelabs::mojom::ObjectB`. Both
    //       of our `ObjectA` and `ObjectB` connections are channel-associated,
    //       however the remote "renderer" process will bind the backing
    //       mojo::AssociatedReceiver for each of these to different TaskQueues.
    //
    //       We first send a message to `ObjectA`, whose backing
    //       mojo::AssociatedReceiver is bound to a TaskQueue that is initially
    //       frozen
    //
    //       We then send a message to `ObjectB`, whose backing
    //       mojo::AssociatedReceiver is bound to a normal unfrozen TaskQueue.
    //
    //       From this we see two results:
    //         - The message for `ObjectA` is not delayed, despite its
    //           AssociatedReceiver being bound to a frozen TaskQueue. This is
    //           because we cannot delay channel-associated message from being
    //           delivered due to legacy IPC deadlock reasons
    //         - If you then comment out the part of the renderer code that
    //           binds the `ObjectA` interface (so that you prevent it from ever
    //           being bound to an implementation), you then observe that the
    //           `ObjectB` message is not blocked at all on the `ObjectA`
    //           message, for the same reasons above.

    // 1.) Bootstrap the IPC Channel.
    channel_proxy_ = IPC::ChannelProxy::Create(
        std::move(ipc_bootstrap_pipe), IPC::Channel::MODE_SERVER, this,
        /*ipc_task_runner=*/io_task_runner,
        /*listener_task_runner=*/
        base::SingleThreadTaskRunner::GetCurrentDefault());

    // 2.) Bind and send an IPC to ObjectA.
    mojo::AssociatedRemote<codelabs::mojom::ObjectA> remote_a;
    channel_proxy_->GetRemoteAssociatedInterface(&remote_a);
    remote_a->DoA();

    // 3.) Bind and send an IPC to ObjectB.
    mojo::AssociatedRemote<codelabs::mojom::ObjectB> remote_b;
    channel_proxy_->GetRemoteAssociatedInterface(&remote_b);
    remote_b->DoB();
  }

  // IPC::Listener implementation.
  void OnAssociatedInterfaceRequest(
      const std::string& interface_name,
      mojo::ScopedInterfaceEndpointHandle handle) override {
    NOTREACHED()
        << "The browser should not receive associated interface requests";
  }

 private:
  std::unique_ptr<IPC::ChannelProxy> channel_proxy_;
};

int main(int argc, char** argv) {
  LOG(INFO) << "Browser process starting up";
  base::CommandLine::Init(argc, argv);

  ProcessBootstrapper bootstrapper;
  // The IO thread that the `BrowserIPCListener` ChannelProxy listens for
  // messages on *must* be different than the main thread, so in this example
  // (and in the corresponding "renderer.cc") we initialize the main thread with
  // a "DEFAULT" (i.e., non-IO-capable) main thread. This will automatically
  // give us a separate dedicated IO thread for Mojo and the IPC infrastructure
  // to use.
  bootstrapper.InitMainThread(base::MessagePumpType::DEFAULT);
  bootstrapper.InitMojo(/*as_browser_process=*/true);

  mojo::ScopedMessagePipeHandle handle = LaunchAndConnect();

  // Create a new `BrowserIPCListener` to sponsor communication coming from the
  // "browser" process. The rest of the program will execute there.
  std::unique_ptr<BrowserIPCListener> browser_ipc_listener =
      std::make_unique<BrowserIPCListener>(std::move(handle),
                                           bootstrapper.io_task_runner);

  base::RunLoop run_loop;
  // Delay shutdown of the browser process for visual effects, as well as to
  // ensure the browser process doesn't die while the IPC message is still being
  // sent to the target process asynchronously, which would prevent its
  // delivery. This delay is an arbitrary 5 seconds, which just needs to be
  // longer than the renderer's 3 seconds, which is used to show visually via
  // logging, how the ordering of IPCs can be effected by a frozen task queue
  // that gets unfrozen 3 seconds later.
  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(
          [](base::OnceClosure quit_closure) {
            LOG(INFO) << "'Browser process' shutting down";
            std::move(quit_closure).Run();
          },
          run_loop.QuitClosure()),
      base::Seconds(5));
  run_loop.Run();
  return 0;
}
