// 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 "content/renderer/bluetooth/web_bluetooth_impl.h"

#include <memory>
#include <utility>
#include <vector>

#include "base/memory/ptr_util.h"
#include "base/optional.h"
#include "content/child/mojo/type_converters.h"
#include "content/child/thread_safe_sender.h"
#include "content/common/bluetooth/web_bluetooth_device_id.h"
#include "content/renderer/bluetooth/bluetooth_type_converters.h"
#include "ipc/ipc_message.h"
#include "mojo/public/cpp/bindings/array.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/WebKit/public/platform/modules/bluetooth/WebBluetoothDevice.h"
#include "third_party/WebKit/public/platform/modules/bluetooth/WebBluetoothDeviceInit.h"
#include "third_party/WebKit/public/platform/modules/bluetooth/WebBluetoothRemoteGATTCharacteristic.h"
#include "third_party/WebKit/public/platform/modules/bluetooth/WebBluetoothRemoteGATTCharacteristicInit.h"
#include "third_party/WebKit/public/platform/modules/bluetooth/WebBluetoothRemoteGATTService.h"
#include "third_party/WebKit/public/platform/modules/bluetooth/WebRequestDeviceOptions.h"

namespace content {

namespace {

// Blink can't use non-blink mojo enums like blink::mojom::WebBluetoothResult,
// so we pass it as an int32 across the boundary.
int32_t ToInt32(blink::mojom::WebBluetoothResult result) {
  return static_cast<int32_t>(result);
}

}  // namespace

WebBluetoothImpl::WebBluetoothImpl(
    service_manager::InterfaceProvider* remote_interfaces)
    : remote_interfaces_(remote_interfaces), binding_(this) {}

WebBluetoothImpl::~WebBluetoothImpl() {
}

void WebBluetoothImpl::requestDevice(
    const blink::WebRequestDeviceOptions& options,
    blink::WebBluetoothRequestDeviceCallbacks* callbacks) {
  GetWebBluetoothService().RequestDevice(
      blink::mojom::WebBluetoothRequestDeviceOptions::From(options),
      base::Bind(&WebBluetoothImpl::OnRequestDeviceComplete,
                 base::Unretained(this),
                 base::Passed(base::WrapUnique(callbacks))));
}

void WebBluetoothImpl::connect(
    const blink::WebString& device_id,
    blink::WebBluetoothDevice* device,
    blink::WebBluetoothRemoteGATTServerConnectCallbacks* callbacks) {
  // TODO(crbug.com/495270): After the Bluetooth Tree is implemented, there will
  // only be one object per device. But for now we replace the previous object.
  WebBluetoothDeviceId device_id_obj = WebBluetoothDeviceId(device_id.utf8());
  connected_devices_[device_id_obj] = device;

  GetWebBluetoothService().RemoteServerConnect(
      std::move(device_id_obj),
      base::Bind(&WebBluetoothImpl::OnConnectComplete, base::Unretained(this),
                 base::Passed(base::WrapUnique(callbacks))));
}

void WebBluetoothImpl::disconnect(const blink::WebString& device_id) {
  WebBluetoothDeviceId device_id_obj = WebBluetoothDeviceId(device_id.utf8());
  connected_devices_.erase(device_id_obj);

  GetWebBluetoothService().RemoteServerDisconnect(std::move(device_id_obj));
}

void WebBluetoothImpl::getPrimaryServices(
    const blink::WebString& device_id,
    int32_t quantity,
    const blink::WebString& services_uuid,
    blink::WebBluetoothGetPrimaryServicesCallbacks* callbacks) {
  DCHECK(blink::mojom::IsKnownEnumValue(
      static_cast<blink::mojom::WebBluetoothGATTQueryQuantity>(quantity)));
  GetWebBluetoothService().RemoteServerGetPrimaryServices(
      WebBluetoothDeviceId(device_id.utf8()),
      static_cast<blink::mojom::WebBluetoothGATTQueryQuantity>(quantity),
      services_uuid.isEmpty()
          ? base::nullopt
          : base::make_optional(device::BluetoothUUID(services_uuid.utf8())),
      base::Bind(&WebBluetoothImpl::OnGetPrimaryServicesComplete,
                 base::Unretained(this), device_id,
                 base::Passed(base::WrapUnique(callbacks))));
}

void WebBluetoothImpl::getCharacteristics(
    const blink::WebString& service_instance_id,
    int32_t quantity,
    const blink::WebString& characteristics_uuid,
    blink::WebBluetoothGetCharacteristicsCallbacks* callbacks) {
  DCHECK(blink::mojom::IsKnownEnumValue(
      static_cast<blink::mojom::WebBluetoothGATTQueryQuantity>(quantity)));
  GetWebBluetoothService().RemoteServiceGetCharacteristics(
      mojo::String::From(service_instance_id),
      static_cast<blink::mojom::WebBluetoothGATTQueryQuantity>(quantity),
      characteristics_uuid.isEmpty()
          ? base::nullopt
          : base::make_optional(
                device::BluetoothUUID(characteristics_uuid.utf8())),
      base::Bind(&WebBluetoothImpl::OnGetCharacteristicsComplete,
                 base::Unretained(this), service_instance_id,
                 base::Passed(base::WrapUnique(callbacks))));
}

void WebBluetoothImpl::readValue(
    const blink::WebString& characteristic_instance_id,
    blink::WebBluetoothReadValueCallbacks* callbacks) {
  GetWebBluetoothService().RemoteCharacteristicReadValue(
      mojo::String::From(characteristic_instance_id),
      base::Bind(&WebBluetoothImpl::OnReadValueComplete, base::Unretained(this),
                 base::Passed(base::WrapUnique(callbacks))));
}

void WebBluetoothImpl::writeValue(
    const blink::WebString& characteristic_instance_id,
    const blink::WebVector<uint8_t>& value,
    blink::WebBluetoothWriteValueCallbacks* callbacks) {
  GetWebBluetoothService().RemoteCharacteristicWriteValue(
      mojo::String::From(characteristic_instance_id),
      mojo::Array<uint8_t>::From(value),
      base::Bind(&WebBluetoothImpl::OnWriteValueComplete,
                 base::Unretained(this), value,
                 base::Passed(base::WrapUnique(callbacks))));
}

void WebBluetoothImpl::startNotifications(
    const blink::WebString& characteristic_instance_id,
    blink::WebBluetoothNotificationsCallbacks* callbacks) {
  GetWebBluetoothService().RemoteCharacteristicStartNotifications(
      mojo::String::From(characteristic_instance_id),
      base::Bind(&WebBluetoothImpl::OnStartNotificationsComplete,
                 base::Unretained(this),
                 base::Passed(base::WrapUnique(callbacks))));
}

void WebBluetoothImpl::stopNotifications(
    const blink::WebString& characteristic_instance_id,
    blink::WebBluetoothNotificationsCallbacks* callbacks) {
  GetWebBluetoothService().RemoteCharacteristicStopNotifications(
      mojo::String::From(characteristic_instance_id),
      base::Bind(&WebBluetoothImpl::OnStopNotificationsComplete,
                 base::Unretained(this),
                 base::Passed(base::WrapUnique(callbacks))));
}

void WebBluetoothImpl::characteristicObjectRemoved(
    const blink::WebString& characteristic_instance_id,
    blink::WebBluetoothRemoteGATTCharacteristic* characteristic) {
  active_characteristics_.erase(characteristic_instance_id.utf8());
}

void WebBluetoothImpl::registerCharacteristicObject(
    const blink::WebString& characteristic_instance_id,
    blink::WebBluetoothRemoteGATTCharacteristic* characteristic) {
  // TODO(ortuno): After the Bluetooth Tree is implemented, there will
  // only be one object per characteristic. But for now we replace
  // the previous object.
  // https://crbug.com/495270
  active_characteristics_[characteristic_instance_id.utf8()] = characteristic;
}

void WebBluetoothImpl::RemoteCharacteristicValueChanged(
    const std::string& characteristic_instance_id,
    const std::vector<uint8_t>& value) {
  // We post a task so that the event is fired after any pending promises have
  // resolved.
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE,
      base::Bind(&WebBluetoothImpl::DispatchCharacteristicValueChanged,
                 base::Unretained(this), characteristic_instance_id, value));
}

void WebBluetoothImpl::OnRequestDeviceComplete(
    std::unique_ptr<blink::WebBluetoothRequestDeviceCallbacks> callbacks,
    const blink::mojom::WebBluetoothResult result,
    blink::mojom::WebBluetoothDevicePtr device) {
  if (result == blink::mojom::WebBluetoothResult::SUCCESS) {
    callbacks->onSuccess(base::MakeUnique<blink::WebBluetoothDeviceInit>(
        blink::WebString::fromUTF8(device->id.str()),
        device->name ? blink::WebString::fromUTF8(device->name.value())
                     : blink::WebString()));
  } else {
    callbacks->onError(ToInt32(result));
  }
}

void WebBluetoothImpl::GattServerDisconnected(
    const WebBluetoothDeviceId& device_id) {
  auto device_iter = connected_devices_.find(device_id);
  if (device_iter != connected_devices_.end()) {
    // Remove device from the map before calling dispatchGattServerDisconnected
    // to avoid removing a device the gattserverdisconnected event handler might
    // have re-connected.
    blink::WebBluetoothDevice* device = device_iter->second;
    connected_devices_.erase(device_iter);
    device->dispatchGattServerDisconnected();
  }
}

void WebBluetoothImpl::OnConnectComplete(
    std::unique_ptr<blink::WebBluetoothRemoteGATTServerConnectCallbacks>
        callbacks,
    blink::mojom::WebBluetoothResult result) {
  if (result == blink::mojom::WebBluetoothResult::SUCCESS) {
    callbacks->onSuccess();
  } else {
    callbacks->onError(ToInt32(result));
  }
}

void WebBluetoothImpl::OnGetPrimaryServicesComplete(
    const blink::WebString& device_id,
    std::unique_ptr<blink::WebBluetoothGetPrimaryServicesCallbacks> callbacks,
    blink::mojom::WebBluetoothResult result,
    base::Optional<std::vector<blink::mojom::WebBluetoothRemoteGATTServicePtr>>
        services) {
  if (result == blink::mojom::WebBluetoothResult::SUCCESS) {
    DCHECK(services);
    // TODO(dcheng): This WebVector should use smart pointers.
    blink::WebVector<blink::WebBluetoothRemoteGATTService*> promise_services(
        services->size());
    for (size_t i = 0; i < services->size(); i++) {
      promise_services[i] = new blink::WebBluetoothRemoteGATTService(
          blink::WebString::fromUTF8(services.value()[i]->instance_id),
          blink::WebString::fromUTF8(services.value()[i]->uuid),
          true /* isPrimary */, device_id);
    }
    callbacks->onSuccess(promise_services);
  } else {
    callbacks->onError(ToInt32(result));
  }
}

void WebBluetoothImpl::OnGetCharacteristicsComplete(
    const blink::WebString& service_instance_id,
    std::unique_ptr<blink::WebBluetoothGetCharacteristicsCallbacks> callbacks,
    blink::mojom::WebBluetoothResult result,
    base::Optional<
        std::vector<blink::mojom::WebBluetoothRemoteGATTCharacteristicPtr>>
        characteristics) {
  if (result == blink::mojom::WebBluetoothResult::SUCCESS) {
    DCHECK(characteristics);
    // TODO(dcheng): This WebVector should use smart pointers.
    blink::WebVector<blink::WebBluetoothRemoteGATTCharacteristicInit*>
        promise_characteristics(characteristics->size());
    for (size_t i = 0; i < characteristics->size(); i++) {
      promise_characteristics[i] =
          new blink::WebBluetoothRemoteGATTCharacteristicInit(
              service_instance_id, blink::WebString::fromUTF8(
                                       characteristics.value()[i]->instance_id),
              blink::WebString::fromUTF8(characteristics.value()[i]->uuid),
              characteristics.value()[i]->properties);
    }
    callbacks->onSuccess(promise_characteristics);
  } else {
    callbacks->onError(ToInt32(result));
  }
}

void WebBluetoothImpl::OnReadValueComplete(
    std::unique_ptr<blink::WebBluetoothReadValueCallbacks> callbacks,
    blink::mojom::WebBluetoothResult result,
    const base::Optional<std::vector<uint8_t>>& value) {
  if (result == blink::mojom::WebBluetoothResult::SUCCESS) {
    DCHECK(value);
    callbacks->onSuccess(value.value());
  } else {
    callbacks->onError(ToInt32(result));
  }
}

void WebBluetoothImpl::OnWriteValueComplete(
    const blink::WebVector<uint8_t>& value,
    std::unique_ptr<blink::WebBluetoothWriteValueCallbacks> callbacks,
    blink::mojom::WebBluetoothResult result) {
  if (result == blink::mojom::WebBluetoothResult::SUCCESS) {
    callbacks->onSuccess(value);
  } else {
    callbacks->onError(ToInt32(result));
  }
}

void WebBluetoothImpl::OnStartNotificationsComplete(
    std::unique_ptr<blink::WebBluetoothNotificationsCallbacks> callbacks,
    blink::mojom::WebBluetoothResult result) {
  if (result == blink::mojom::WebBluetoothResult::SUCCESS) {
    callbacks->onSuccess();
  } else {
    callbacks->onError(ToInt32(result));
  }
}

void WebBluetoothImpl::OnStopNotificationsComplete(
    std::unique_ptr<blink::WebBluetoothNotificationsCallbacks> callbacks) {
  callbacks->onSuccess();
}

void WebBluetoothImpl::DispatchCharacteristicValueChanged(
    const std::string& characteristic_instance_id,
    const std::vector<uint8_t>& value) {
  auto active_iter = active_characteristics_.find(characteristic_instance_id);
  if (active_iter != active_characteristics_.end()) {
    active_iter->second->dispatchCharacteristicValueChanged(value);
  }
}

blink::mojom::WebBluetoothService& WebBluetoothImpl::GetWebBluetoothService() {
  if (!web_bluetooth_service_) {
    remote_interfaces_->GetInterface(mojo::GetProxy(&web_bluetooth_service_));
    // Create an associated interface ptr and pass it to the WebBluetoothService
    // so that it can send us events without us prompting.
    blink::mojom::WebBluetoothServiceClientAssociatedPtrInfo ptr_info;
    binding_.Bind(&ptr_info, web_bluetooth_service_.associated_group());
    web_bluetooth_service_->SetClient(std::move(ptr_info));
  }
  return *web_bluetooth_service_;
}

}  // namespace content
