// Copyright 2013 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 "extensions/common/extension_builder.h"

#include <utility>

#include "base/optional.h"
#include "components/crx_file/id_util.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_constants.h"

namespace extensions {

struct ExtensionBuilder::ManifestData {
  Type type;
  std::string name;
  std::vector<std::string> permissions;
  base::Optional<ActionType> action;
  base::Optional<BackgroundPage> background_page;
  base::Optional<base::Value> extra;

  std::unique_ptr<base::DictionaryValue> GetValue() const {
    DictionaryBuilder manifest;
    manifest.Set("name", name)
        .Set("manifest_version", 2)
        .Set("version", "0.1")
        .Set("description", "some description");

    switch (type) {
      case Type::EXTENSION:
        break;  // Sufficient already.
      case Type::PLATFORM_APP: {
        DictionaryBuilder background;
        background.Set("scripts", ListBuilder().Append("test.js").Build());
        manifest.Set(
            "app",
            DictionaryBuilder().Set("background", background.Build()).Build());
        break;
      }
    }

    if (!permissions.empty()) {
      ListBuilder permissions_builder;
      for (const std::string& permission : permissions)
        permissions_builder.Append(permission);
      manifest.Set("permissions", permissions_builder.Build());
    }

    if (action) {
      const char* action_key = nullptr;
      switch (*action) {
        case ActionType::PAGE_ACTION:
          action_key = manifest_keys::kPageAction;
          break;
        case ActionType::BROWSER_ACTION:
          action_key = manifest_keys::kBrowserAction;
          break;
      }
      manifest.Set(action_key, std::make_unique<base::DictionaryValue>());
    }

    if (background_page) {
      DictionaryBuilder background;
      background.Set("page", "background_page.html");
      bool persistent = false;
      switch (*background_page) {
        case BackgroundPage::PERSISTENT:
          persistent = true;
          break;
        case BackgroundPage::EVENT:
          persistent = false;
          break;
      }
      background.Set("persistent", persistent);
      manifest.Set("background", background.Build());
    }

    std::unique_ptr<base::DictionaryValue> result = manifest.Build();
    if (extra) {
      const base::DictionaryValue* extra_dict = nullptr;
      extra->GetAsDictionary(&extra_dict);
      result->MergeDictionary(extra_dict);
    }

    return result;
  }

  base::Value* get_extra() {
    if (!extra)
      extra.emplace(base::Value::Type::DICTIONARY);
    return &extra.value();
  }
};

ExtensionBuilder::ExtensionBuilder()
    : location_(Manifest::UNPACKED), flags_(Extension::NO_FLAGS) {}

ExtensionBuilder::ExtensionBuilder(const std::string& name, Type type)
    : ExtensionBuilder() {
  manifest_data_ = std::make_unique<ManifestData>();
  manifest_data_->name = name;
  manifest_data_->type = type;
}

ExtensionBuilder::~ExtensionBuilder() {}

ExtensionBuilder::ExtensionBuilder(ExtensionBuilder&& other) = default;
ExtensionBuilder& ExtensionBuilder::operator=(ExtensionBuilder&& other) =
    default;

scoped_refptr<Extension> ExtensionBuilder::Build() {
  CHECK(manifest_data_ || manifest_value_);

  if (id_.empty() && manifest_data_)
    id_ = crx_file::id_util::GenerateId(manifest_data_->name);

  std::string error;
  scoped_refptr<Extension> extension = Extension::Create(
      path_, location_,
      manifest_data_ ? *manifest_data_->GetValue() : *manifest_value_, flags_,
      id_, &error);

  CHECK(error.empty()) << error;
  CHECK(extension);

  return extension;
}

ExtensionBuilder& ExtensionBuilder::AddPermission(
    const std::string& permission) {
  CHECK(manifest_data_);
  manifest_data_->permissions.push_back(permission);
  return *this;
}

ExtensionBuilder& ExtensionBuilder::AddPermissions(
    const std::vector<std::string>& permissions) {
  CHECK(manifest_data_);
  manifest_data_->permissions.insert(manifest_data_->permissions.end(),
                                     permissions.begin(), permissions.end());
  return *this;
}

ExtensionBuilder& ExtensionBuilder::SetAction(ActionType action) {
  CHECK(manifest_data_);
  manifest_data_->action = action;
  return *this;
}

ExtensionBuilder& ExtensionBuilder::SetBackgroundPage(
    BackgroundPage background_page) {
  CHECK(manifest_data_);
  manifest_data_->background_page = background_page;
  return *this;
}

ExtensionBuilder& ExtensionBuilder::SetPath(const base::FilePath& path) {
  path_ = path;
  return *this;
}

ExtensionBuilder& ExtensionBuilder::SetLocation(Manifest::Location location) {
  location_ = location;
  return *this;
}

ExtensionBuilder& ExtensionBuilder::SetManifest(
    std::unique_ptr<base::DictionaryValue> manifest) {
  CHECK(!manifest_data_);
  manifest_value_ = std::move(manifest);
  return *this;
}

ExtensionBuilder& ExtensionBuilder::MergeManifest(
    std::unique_ptr<base::DictionaryValue> manifest) {
  if (manifest_data_) {
    base::DictionaryValue* extra_dict = nullptr;
    manifest_data_->get_extra()->GetAsDictionary(&extra_dict);
    extra_dict->MergeDictionary(manifest.get());
  } else {
    manifest_value_->MergeDictionary(manifest.get());
  }
  return *this;
}

ExtensionBuilder& ExtensionBuilder::AddFlags(int init_from_value_flags) {
  flags_ |= init_from_value_flags;
  return *this;
}

ExtensionBuilder& ExtensionBuilder::SetID(const std::string& id) {
  id_ = id;
  return *this;
}

void ExtensionBuilder::SetManifestKeyImpl(base::StringPiece key,
                                          base::Value value) {
  CHECK(manifest_data_);
  manifest_data_->get_extra()->SetKey(key, std::move(value));
}

void ExtensionBuilder::SetManifestPathImpl(
    std::initializer_list<base::StringPiece> path,
    base::Value value) {
  CHECK(manifest_data_);
  manifest_data_->get_extra()->SetPath(path, std::move(value));
}

}  // namespace extensions
