// Copyright 2018 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/webauth/scoped_virtual_authenticator_environment.h"

#include <utility>

#include "base/stl_util.h"
#include "content/browser/webauth/authenticator_type_converters.h"
#include "content/browser/webauth/virtual_authenticator.h"
#include "content/browser/webauth/virtual_discovery.h"
#include "device/fido/virtual_ctap2_device.h"

namespace content {

namespace {

blink::test::mojom::VirtualAuthenticatorPtr GetMojoPtrToVirtualAuthenticator(
    VirtualAuthenticator* authenticator) {
  blink::test::mojom::VirtualAuthenticatorPtr mojo_authenticator_ptr;
  authenticator->AddBinding(mojo::MakeRequest(&mojo_authenticator_ptr));
  return mojo_authenticator_ptr;
}

}  // namespace

// static
ScopedVirtualAuthenticatorEnvironment*
ScopedVirtualAuthenticatorEnvironment::GetInstance() {
  static base::NoDestructor<ScopedVirtualAuthenticatorEnvironment> environment;
  return environment.get();
}

ScopedVirtualAuthenticatorEnvironment::ScopedVirtualAuthenticatorEnvironment()
    : virtual_device_state_(new device::VirtualFidoDevice::State) {}

ScopedVirtualAuthenticatorEnvironment::
    ~ScopedVirtualAuthenticatorEnvironment() = default;

void ScopedVirtualAuthenticatorEnvironment::AddBinding(
    blink::test::mojom::VirtualAuthenticatorManagerRequest request) {
  bindings_.AddBinding(this, std::move(request));
}

void ScopedVirtualAuthenticatorEnvironment::CreateAuthenticator(
    blink::test::mojom::VirtualAuthenticatorOptionsPtr options,
    CreateAuthenticatorCallback callback) {
  auto authenticator = std::make_unique<VirtualAuthenticator>(
      mojo::ConvertTo<::device::FidoTransportProtocol>(options->transport));
  auto* authenticator_ptr = authenticator.get();
  authenticators_.emplace(authenticator_ptr->unique_id(),
                          std::move(authenticator));

  for (auto* discovery : discoveries_) {
    if (discovery->transport() != authenticator_ptr->transport())
      continue;
    discovery->AddVirtualDevice(authenticator_ptr->ConstructDevice());
  }

  std::move(callback).Run(GetMojoPtrToVirtualAuthenticator(authenticator_ptr));
}

void ScopedVirtualAuthenticatorEnvironment::GetAuthenticators(
    GetAuthenticatorsCallback callback) {
  std::vector<blink::test::mojom::VirtualAuthenticatorPtrInfo>
      mojo_authenticators;
  for (auto& authenticator : authenticators_) {
    mojo_authenticators.push_back(
        GetMojoPtrToVirtualAuthenticator(authenticator.second.get())
            .PassInterface());
  }

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

void ScopedVirtualAuthenticatorEnvironment::RemoveAuthenticator(
    const std::string& id,
    RemoveAuthenticatorCallback callback) {
  const bool removed = authenticators_.erase(id);
  if (removed) {
    for (auto* discovery : discoveries_)
      discovery->RemoveVirtualDevice(id);
  }

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

void ScopedVirtualAuthenticatorEnvironment::ClearAuthenticators(
    ClearAuthenticatorsCallback callback) {
  for (auto& authenticator : authenticators_) {
    for (auto* discovery : discoveries_) {
      discovery->RemoveVirtualDevice(authenticator.second->unique_id());
    }
  }
  authenticators_.clear();

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

std::unique_ptr<::device::FidoDiscoveryBase>
ScopedVirtualAuthenticatorEnvironment::CreateFidoDiscovery(
    device::FidoTransportProtocol transport,
    ::service_manager::Connector* connector) {
  auto discovery = std::make_unique<VirtualFidoDiscovery>(this, transport);

  if (bindings_.empty()) {
    // If no bindings are active then create a virtual device. This is useful
    // for web-platform tests which assume that they can make webauthn calls,
    // but which don't implement the Chromium-specific mock Mojo interfaces.
    auto device =
        std::make_unique<device::VirtualCtap2Device>(virtual_device_state_);
    discovery->AddVirtualDevice(std::move(device));
  } else {
    for (auto& authenticator : authenticators_) {
      if (discovery->transport() != authenticator.second->transport())
        continue;
      discovery->AddVirtualDevice(authenticator.second->ConstructDevice());
    }
  }

  discoveries_.insert(discovery.get());
  return discovery;
}

void ScopedVirtualAuthenticatorEnvironment::OnDiscoveryDestroyed(
    VirtualFidoDiscovery* discovery) {
  DCHECK(base::ContainsKey(discoveries_, discovery));
  discoveries_.erase(discovery);
}

}  // namespace content
