// Copyright 2017 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 "services/device/serial/serial_port_manager_impl.h"

#include <string>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/sequenced_task_runner.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "services/device/public/cpp/serial/serial_switches.h"
#include "services/device/serial/bluetooth_serial_device_enumerator.h"
#include "services/device/serial/bluetooth_serial_port_impl.h"
#include "services/device/serial/serial_device_enumerator.h"
#include "services/device/serial/serial_port_impl.h"

namespace device {

SerialPortManagerImpl::SerialPortManagerImpl(
    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
    scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner)
    : io_task_runner_(std::move(io_task_runner)),
      ui_task_runner_(std::move(ui_task_runner)) {}

SerialPortManagerImpl::~SerialPortManagerImpl() = default;

void SerialPortManagerImpl::Bind(
    mojo::PendingReceiver<mojom::SerialPortManager> receiver) {
  receivers_.Add(this, std::move(receiver));
}

void SerialPortManagerImpl::SetSerialEnumeratorForTesting(
    std::unique_ptr<SerialDeviceEnumerator> fake_enumerator) {
  DCHECK(fake_enumerator);
  enumerator_ = std::move(fake_enumerator);
  observed_enumerator_.Add(enumerator_.get());
}

void SerialPortManagerImpl::SetBluetoothSerialEnumeratorForTesting(
    std::unique_ptr<BluetoothSerialDeviceEnumerator>
        fake_bluetooth_enumerator) {
  DCHECK(fake_bluetooth_enumerator);
  bluetooth_enumerator_ = std::move(fake_bluetooth_enumerator);
  observed_enumerator_.Add(bluetooth_enumerator_.get());
}

void SerialPortManagerImpl::SetClient(
    mojo::PendingRemote<mojom::SerialPortManagerClient> client) {
  clients_.Add(std::move(client));
}

void SerialPortManagerImpl::GetDevices(GetDevicesCallback callback) {
  if (!enumerator_) {
    enumerator_ = SerialDeviceEnumerator::Create(ui_task_runner_);
    observed_enumerator_.Add(enumerator_.get());
  }
  auto devices = enumerator_->GetDevices();
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kEnableBluetoothSerialPortProfileInSerialApi)) {
    if (!bluetooth_enumerator_) {
      bluetooth_enumerator_ =
          std::make_unique<BluetoothSerialDeviceEnumerator>();
      observed_enumerator_.Add(bluetooth_enumerator_.get());
    }
    auto bluetooth_devices = bluetooth_enumerator_->GetDevices();
    devices.insert(devices.end(),
                   std::make_move_iterator(bluetooth_devices.begin()),
                   std::make_move_iterator(bluetooth_devices.end()));
  }

  std::move(callback).Run(std::move(devices));
}

void SerialPortManagerImpl::GetPort(
    const base::UnguessableToken& token,
    bool use_alternate_path,
    mojo::PendingReceiver<mojom::SerialPort> receiver,
    mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher) {
  if (!enumerator_) {
    enumerator_ = SerialDeviceEnumerator::Create(ui_task_runner_);
    observed_enumerator_.Add(enumerator_.get());
  }
  base::Optional<base::FilePath> path =
      enumerator_->GetPathFromToken(token, use_alternate_path);
  if (path) {
    io_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&SerialPortImpl::Create, *path, std::move(receiver),
                       std::move(watcher), ui_task_runner_));
    return;
  }

  DCHECK(bluetooth_enumerator_);
  base::Optional<std::string> address =
      bluetooth_enumerator_->GetAddressFromToken(token);
  if (address) {
    ui_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&BluetoothSerialPortImpl::Create,
                                  bluetooth_enumerator_->GetAdapter(), *address,
                                  std::move(receiver), std::move(watcher)));
  }
}

void SerialPortManagerImpl::OnPortAdded(const mojom::SerialPortInfo& port) {
  for (auto& client : clients_)
    client->OnPortAdded(port.Clone());
}

void SerialPortManagerImpl::OnPortRemoved(const mojom::SerialPortInfo& port) {
  for (auto& client : clients_)
    client->OnPortRemoved(port.Clone());
}

}  // namespace device
