// Copyright 2016 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/catalog/entry.h"

#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "base/values.h"
#include "services/catalog/public/cpp/manifest_parsing_util.h"
#include "services/catalog/store.h"
#include "services/service_manager/public/interfaces/interface_provider_spec.mojom.h"

namespace catalog {
namespace {

#if defined(OS_WIN)
const char kServiceExecutableExtension[] = ".service.exe";
#else
const char kServiceExecutableExtension[] = ".service";
#endif

bool ReadStringSet(const base::ListValue& list_value,
                   std::set<std::string>* string_set) {
  DCHECK(string_set);
  for (const auto& value_value : list_value) {
    std::string value;
    if (!value_value.GetAsString(&value)) {
      LOG(ERROR) << "Entry::Deserialize: list member must be a string";
      return false;
    }
    string_set->insert(std::move(value));
  }
  return true;
}

bool ReadStringSetFromValue(const base::Value& value,
                            std::set<std::string>* string_set) {
  const base::ListValue* list_value = nullptr;
  if (!value.GetAsList(&list_value)) {
    LOG(ERROR) << "Entry::Deserialize: Value must be a list.";
    return false;
  }
  return ReadStringSet(*list_value, string_set);
}

// If |key| refers to a dictionary value within |value|, |*out| is set to that
// DictionaryValue. Returns true if either |key| is not present or the
// corresponding value is a dictionary.
bool GetDictionaryValue(const base::DictionaryValue& value,
                        base::StringPiece key,
                        const base::DictionaryValue** out) {
  const base::Value* entry_value = nullptr;
  return !value.Get(key, &entry_value) || entry_value->GetAsDictionary(out);
}

bool BuildInterfaceProviderSpec(
    const base::DictionaryValue& value,
    service_manager::InterfaceProviderSpec* interface_provider_specs) {
  DCHECK(interface_provider_specs);
  const base::DictionaryValue* provides_value = nullptr;
  if (!GetDictionaryValue(value, Store::kInterfaceProviderSpecs_ProvidesKey,
                          &provides_value)) {
    LOG(ERROR) << "Entry::Deserialize: "
               << Store::kInterfaceProviderSpecs_ProvidesKey
               << " must be a dictionary.";
    return false;
  }
  if (provides_value) {
    base::DictionaryValue::Iterator it(*provides_value);
    for(; !it.IsAtEnd(); it.Advance()) {
      service_manager::InterfaceSet interfaces;
      if (!ReadStringSetFromValue(it.value(), &interfaces)) {
        LOG(ERROR) << "Entry::Deserialize: Invalid interface list in provided "
                   << " capabilities dictionary";
        return false;
      }
      interface_provider_specs->provides[it.key()] = std::move(interfaces);
    }
  }

  const base::DictionaryValue* requires_value = nullptr;
  if (!GetDictionaryValue(value, Store::kInterfaceProviderSpecs_RequiresKey,
                          &requires_value)) {
    LOG(ERROR) << "Entry::Deserialize: "
               << Store::kInterfaceProviderSpecs_RequiresKey
               << " must be a dictionary.";
    return false;
  }
  if (requires_value) {
    base::DictionaryValue::Iterator it(*requires_value);
    for (; !it.IsAtEnd(); it.Advance()) {
      service_manager::CapabilitySet capabilities;
      const base::ListValue* entry_value = nullptr;
      if (!it.value().GetAsList(&entry_value)) {
        LOG(ERROR) << "Entry::Deserialize: "
                   << Store::kInterfaceProviderSpecs_RequiresKey
                   << " entry must be a list.";
        return false;
      }
      if (!ReadStringSet(*entry_value, &capabilities)) {
        LOG(ERROR) << "Entry::Deserialize: Invalid capabilities list in "
                   << "requires dictionary.";
        return false;
      }

      interface_provider_specs->requires[it.key()] = std::move(capabilities);
    }
  }
  return true;
}

}  // namespace

Entry::Entry() {}
Entry::Entry(const std::string& name)
    : name_(name),
      display_name_(name) {}
Entry::~Entry() {}

// static
std::unique_ptr<Entry> Entry::Deserialize(const base::Value& manifest_root) {
  const base::DictionaryValue* dictionary_value = nullptr;
  if (!manifest_root.GetAsDictionary(&dictionary_value))
    return nullptr;
  const base::DictionaryValue& value = *dictionary_value;

  auto entry = base::MakeUnique<Entry>();

  // Name.
  std::string name;
  if (!value.GetString(Store::kNameKey, &name)) {
    LOG(ERROR) << "Entry::Deserialize: dictionary has no "
               << Store::kNameKey << " key";
    return nullptr;
  }
  if (name.empty()) {
    LOG(ERROR) << "Entry::Deserialize: empty service name.";
    return nullptr;
  }
  entry->set_name(std::move(name));

  // By default we assume a standalone service executable. The catalog may
  // override this layer based on configuration external to the service's own
  // manifest.
  base::FilePath module_path;
  base::PathService::Get(base::DIR_MODULE, &module_path);
  entry->set_path(
      module_path.AppendASCII(entry->name() + kServiceExecutableExtension));

  // Human-readable name.
  std::string display_name;
  if (!value.GetString(Store::kDisplayNameKey, &display_name)) {
    LOG(ERROR) << "Entry::Deserialize: dictionary has no "
               << Store::kDisplayNameKey << " key";
    return nullptr;
  }
  entry->set_display_name(std::move(display_name));

  // InterfaceProvider specs.
  const base::DictionaryValue* interface_provider_specs = nullptr;
  if (!value.GetDictionary(Store::kInterfaceProviderSpecsKey,
                           &interface_provider_specs)) {
    LOG(ERROR) << "Entry::Deserialize: dictionary has no "
               << Store::kInterfaceProviderSpecsKey << " key";
    return nullptr;
  }

  base::DictionaryValue::Iterator it(*interface_provider_specs);
  for (; !it.IsAtEnd(); it.Advance()) {
    const base::DictionaryValue* spec_value = nullptr;
    if (!interface_provider_specs->GetDictionary(it.key(), &spec_value)) {
      LOG(ERROR) << "Entry::Deserialize: value of InterfaceProvider map for "
                 << "key: " << it.key() << " not a dictionary.";
      return nullptr;
    }

    service_manager::InterfaceProviderSpec spec;
    if (!BuildInterfaceProviderSpec(*spec_value, &spec)) {
      LOG(ERROR) << "Entry::Deserialize: failed to build InterfaceProvider "
                 << "spec for key: " << it.key();
      return nullptr;
    }
    entry->AddInterfaceProviderSpec(it.key(), std::move(spec));
  }

  // Required files.
  base::Optional<RequiredFileMap> required_files =
      catalog::RetrieveRequiredFiles(value);
  DCHECK(required_files);
  for (auto& iter : *required_files) {
    entry->AddRequiredFilePath(iter.first, std::move(iter.second));
  }

  const base::ListValue* services = nullptr;
  if (value.GetList(Store::kServicesKey, &services)) {
    for (size_t i = 0; i < services->GetSize(); ++i) {
      const base::DictionaryValue* service = nullptr;
      services->GetDictionary(i, &service);
      std::unique_ptr<Entry> child = Entry::Deserialize(*service);
      if (child) {
        child->set_parent(entry.get());
        entry->children().emplace_back(std::move(child));
      }
    }
  }

  return entry;
}

bool Entry::ProvidesCapability(const std::string& capability) const {
  auto it = interface_provider_specs_.find(
      service_manager::mojom::kServiceManager_ConnectorSpec);
  if (it == interface_provider_specs_.end())
    return false;

  const auto& connection_spec = it->second;
  return connection_spec.provides.find(capability) !=
      connection_spec.provides.end();
}

bool Entry::operator==(const Entry& other) const {
  return other.name_ == name_ &&
         other.display_name_ == display_name_ &&
         other.interface_provider_specs_ == interface_provider_specs_;
}

void Entry::AddInterfaceProviderSpec(
    const std::string& name,
    service_manager::InterfaceProviderSpec spec) {
  interface_provider_specs_[name] = std::move(spec);
}

void Entry::AddRequiredFilePath(const std::string& name, base::FilePath path) {
  required_file_paths_[name] = std::move(path);
}

}  // catalog

namespace mojo {

// static
catalog::mojom::EntryPtr
    TypeConverter<catalog::mojom::EntryPtr, catalog::Entry>::Convert(
        const catalog::Entry& input) {
  catalog::mojom::EntryPtr result(catalog::mojom::Entry::New());
  result->name = input.name();
  result->display_name = input.display_name();
  return result;
}

}  // namespace mojo
