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

#include "extensions/common/manifest_handlers/automation.h"

#include <memory>
#include <utility>

#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "extensions/common/api/extensions_manifest_types.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/permissions/api_permission_set.h"
#include "extensions/common/permissions/manifest_permission.h"
#include "extensions/common/permissions/permission_message_util.h"
#include "extensions/common/permissions/permissions_data.h"

using extensions::mojom::APIPermissionID;

namespace extensions {

namespace errors = manifest_errors;
namespace keys = extensions::manifest_keys;
using api::extensions_manifest_types::Automation;

class AutomationManifestPermission : public ManifestPermission {
 public:
  explicit AutomationManifestPermission(
      std::unique_ptr<const AutomationInfo> automation_info)
      : automation_info_(std::move(automation_info)) {}

  // extensions::ManifestPermission overrides.
  std::string name() const override;

  std::string id() const override;

  PermissionIDSet GetPermissions() const override;

  bool FromValue(const base::Value* value) override;

  std::unique_ptr<base::Value> ToValue() const override;

  std::unique_ptr<ManifestPermission> Diff(
      const ManifestPermission* rhs) const override;

  std::unique_ptr<ManifestPermission> Union(
      const ManifestPermission* rhs) const override;

  std::unique_ptr<ManifestPermission> Intersect(
      const ManifestPermission* rhs) const override;

  bool RequiresManagementUIWarning() const override;

 private:
  std::unique_ptr<const AutomationInfo> automation_info_;
};

std::string AutomationManifestPermission::name() const {
  return keys::kAutomation;
}

std::string AutomationManifestPermission::id() const {
  return keys::kAutomation;
}

PermissionIDSet AutomationManifestPermission::GetPermissions() const {
  // Meant to mimic the behavior of GetMessages().
  PermissionIDSet permissions;
  if (automation_info_->desktop) {
    permissions.insert(APIPermissionID::kFullAccess);
  }
  return permissions;
}

bool AutomationManifestPermission::FromValue(const base::Value* value) {
  std::u16string error;
  automation_info_.reset(
      AutomationInfo::FromValue(*value, nullptr /* install_warnings */, &error)
          .release());
  return error.empty();
}

std::unique_ptr<base::Value> AutomationManifestPermission::ToValue() const {
  return AutomationInfo::ToValue(*automation_info_);
}

std::unique_ptr<ManifestPermission> AutomationManifestPermission::Diff(
    const ManifestPermission* rhs) const {
  const AutomationManifestPermission* other =
      static_cast<const AutomationManifestPermission*>(rhs);

  bool desktop = automation_info_->desktop && !other->automation_info_->desktop;
  return std::make_unique<AutomationManifestPermission>(
      base::WrapUnique(new const AutomationInfo(desktop)));
}

std::unique_ptr<ManifestPermission> AutomationManifestPermission::Union(
    const ManifestPermission* rhs) const {
  const AutomationManifestPermission* other =
      static_cast<const AutomationManifestPermission*>(rhs);

  bool desktop = automation_info_->desktop || other->automation_info_->desktop;
  return std::make_unique<AutomationManifestPermission>(
      base::WrapUnique(new const AutomationInfo(desktop)));
}

std::unique_ptr<ManifestPermission> AutomationManifestPermission::Intersect(
    const ManifestPermission* rhs) const {
  const AutomationManifestPermission* other =
      static_cast<const AutomationManifestPermission*>(rhs);

  bool desktop = automation_info_->desktop && other->automation_info_->desktop;
  return std::make_unique<AutomationManifestPermission>(
      base::WrapUnique(new const AutomationInfo(desktop)));
}

bool AutomationManifestPermission::RequiresManagementUIWarning() const {
  return automation_info_->desktop;
}

AutomationHandler::AutomationHandler() = default;

AutomationHandler::~AutomationHandler() = default;

bool AutomationHandler::Parse(Extension* extension, std::u16string* error) {
  const base::Value* automation =
      extension->manifest()->FindPath(keys::kAutomation);
  CHECK(automation != nullptr);
  std::vector<InstallWarning> install_warnings;
  std::unique_ptr<AutomationInfo> info =
      AutomationInfo::FromValue(*automation, &install_warnings, error);
  if (!error->empty()) {
    return false;
  }

  extension->AddInstallWarnings(std::move(install_warnings));

  if (!info) {
    return true;
  }

  extension->SetManifestData(std::move(info));
  return true;
}

base::span<const char* const> AutomationHandler::Keys() const {
  static constexpr const char* kKeys[] = {keys::kAutomation};
  return kKeys;
}

ManifestPermission* AutomationHandler::CreatePermission() {
  return new AutomationManifestPermission(
      base::WrapUnique(new const AutomationInfo));
}

ManifestPermission* AutomationHandler::CreateInitialRequiredPermission(
    const Extension* extension) {
  const AutomationInfo* info = AutomationInfo::Get(extension);
  if (info) {
    return new AutomationManifestPermission(
        base::WrapUnique(new const AutomationInfo(info->desktop)));
  }
  return nullptr;
}

// static
const char* AutomationInfo::kManifestDataKey = keys::kAutomation;

// static
const AutomationInfo* AutomationInfo::Get(const Extension* extension) {
  return extension->GetManifestData<AutomationInfo>();
}

// static
std::unique_ptr<AutomationInfo> AutomationInfo::FromValue(
    const base::Value& value,
    std::vector<InstallWarning>* install_warnings,
    std::u16string* error) {
  auto automation = Automation::FromValue(value);
  if (!automation.has_value()) {
    *error = std::move(automation).error();
    return nullptr;
  }

  if (automation->as_boolean) {
    if (*automation->as_boolean) {
      return base::WrapUnique(new AutomationInfo());
    }
    return nullptr;
  }
  const Automation::Object& automation_object = *automation->as_object;

  bool desktop = false;
  if (automation_object.desktop && *automation_object.desktop) {
    desktop = true;
  }

  return base::WrapUnique(new AutomationInfo(desktop));
}

// static
std::unique_ptr<base::Value> AutomationInfo::ToValue(
    const AutomationInfo& info) {
  return base::Value::ToUniquePtrValue(AsManifestType(info)->ToValue());
}

// static
std::unique_ptr<Automation> AutomationInfo::AsManifestType(
    const AutomationInfo& info) {
  std::unique_ptr<Automation> automation(new Automation);
  if (!info.desktop) {
    automation->as_boolean = true;
    return automation;
  }

  automation->as_object.emplace();
  automation->as_object->desktop = info.desktop;

  return automation;
}

AutomationInfo::AutomationInfo() : desktop(false) {}

AutomationInfo::AutomationInfo(bool desktop) : desktop(desktop) {}

AutomationInfo::~AutomationInfo() = default;

}  // namespace extensions
