// Copyright 2015 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 "content/browser/navigator_connect/service_port_service_impl.h"

#include "content/browser/message_port_message_filter.h"
#include "content/browser/message_port_service.h"
#include "content/browser/navigator_connect/navigator_connect_context_impl.h"
#include "content/common/service_port_type_converters.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/message_port_types.h"
#include "mojo/common/common_type_converters.h"
#include "url/gurl.h"

namespace content {

// static
void ServicePortServiceImpl::Create(
    const scoped_refptr<NavigatorConnectContextImpl>& navigator_connect_context,
    const scoped_refptr<MessagePortMessageFilter>& message_port_message_filter,
    mojo::InterfaceRequest<ServicePortService> request) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      base::Bind(&ServicePortServiceImpl::CreateOnIOThread,
                 navigator_connect_context, message_port_message_filter,
                 base::Passed(&request)));
}

ServicePortServiceImpl::~ServicePortServiceImpl() {
  // Should always be destroyed on the IO thread, but can't check that with
  // DCHECK_CURRENTLY_ON because this class could be destroyed during thread
  // shutdown, at which point that check doesn't work.
  navigator_connect_context_->ServicePortServiceDestroyed(this);
}

void ServicePortServiceImpl::PostMessageToClient(
    int port_id,
    const MessagePortMessage& message,
    const std::vector<TransferredMessagePort>& sent_message_ports) {
  DCHECK(client_.get());
  // Hold messages on transferred message ports. Normally this wouldn't be
  // needed, but since MessagePort uses regular IPC while this class uses mojo,
  // without holding messages mojo IPC might overtake regular IPC resulting in a
  // non-functional port. When  WebMessagePortChannelImpl instances are
  // constructed in the renderer, they will send
  // MessagePortHostMsg_ReleaseMessages to release messages.
  for (const auto& port : sent_message_ports)
    MessagePortService::GetInstance()->HoldMessages(port.id);

  std::vector<int> new_routing_ids;
  message_port_message_filter_->UpdateMessagePortsWithNewRoutes(
      sent_message_ports, &new_routing_ids);
  client_->PostMessage(
      port_id, mojo::String::From(message.message_as_string),
      mojo::Array<MojoTransferredMessagePortPtr>::From(sent_message_ports),
      mojo::Array<int32_t>::From(new_routing_ids));
}

// static
void ServicePortServiceImpl::CreateOnIOThread(
    const scoped_refptr<NavigatorConnectContextImpl>& navigator_connect_context,
    const scoped_refptr<MessagePortMessageFilter>& message_port_message_filter,
    mojo::InterfaceRequest<ServicePortService> request) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  new ServicePortServiceImpl(navigator_connect_context,
                             message_port_message_filter, request.Pass());
}

ServicePortServiceImpl::ServicePortServiceImpl(
    const scoped_refptr<NavigatorConnectContextImpl>& navigator_connect_context,
    const scoped_refptr<MessagePortMessageFilter>& message_port_message_filter,
    mojo::InterfaceRequest<ServicePortService> request)
    : binding_(this, request.Pass()),
      navigator_connect_context_(navigator_connect_context),
      message_port_message_filter_(message_port_message_filter),
      weak_ptr_factory_(this) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
}

void ServicePortServiceImpl::SetClient(ServicePortServiceClientPtr client) {
  DCHECK(!client_.get());
  // TODO(mek): Set ErrorHandler to listen for errors.
  client_ = client.Pass();
}

void ServicePortServiceImpl::Connect(const mojo::String& target_url,
                                     const mojo::String& origin,
                                     const ConnectCallback& callback) {
  navigator_connect_context_->Connect(
      GURL(target_url), GURL(origin), this,
      base::Bind(&ServicePortServiceImpl::OnConnectResult,
                 weak_ptr_factory_.GetWeakPtr(), callback));
}

void ServicePortServiceImpl::PostMessage(
    int32_t port_id,
    const mojo::String& message,
    mojo::Array<MojoTransferredMessagePortPtr> ports) {
  // TODO(mek): Similar to http://crbug.com/490222 this code should make sure
  // port_id belongs to the process this IPC was received from.
  std::vector<TransferredMessagePort> transferred_ports =
      ports.To<std::vector<TransferredMessagePort>>();

  MessagePortService* mps = MessagePortService::GetInstance();
  // First, call QueueMessages for all transferred ports, since the ports
  // haven't sent their own IPC for that.
  for (const TransferredMessagePort& port : transferred_ports) {
    mps->QueueMessages(port.id);
  }

  // Second, pass of the actual to MessagePortService now ServicePort instances
  // are still backed by MessagePort.
  mps->PostMessage(port_id, MessagePortMessage(message.To<base::string16>()),
                   transferred_ports);
}

void ServicePortServiceImpl::ClosePort(int32_t port_id) {
  MessagePortService::GetInstance()->Destroy(port_id);
}

void ServicePortServiceImpl::OnConnectResult(const ConnectCallback& callback,
                                             int message_port_id,
                                             bool success) {
  callback.Run(success ? SERVICE_PORT_CONNECT_RESULT_ACCEPT
                       : SERVICE_PORT_CONNECT_RESULT_REJECT,
               message_port_id);
}

}  // namespace content
