// 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 "mojo/services/catalog/entry.h"

#include "base/values.h"
#include "mojo/services/catalog/store.h"
#include "mojo/shell/public/cpp/names.h"
#include "mojo/util/filename_util.h"
#include "url/gurl.h"

namespace catalog {
namespace {

mojo::CapabilitySpec BuildCapabilitiesV0(
    const base::DictionaryValue& value) {
  mojo::CapabilitySpec capabilities;
  base::DictionaryValue::Iterator it(value);
  for (; !it.IsAtEnd(); it.Advance()) {
    const base::ListValue* values = nullptr;
    CHECK(it.value().GetAsList(&values));
    mojo::CapabilityRequest spec;
    for (auto i = values->begin(); i != values->end(); ++i) {
      mojo::Interface interface_name;
      const base::Value* v = *i;
      CHECK(v->GetAsString(&interface_name));
      spec.interfaces.insert(interface_name);
    }
    capabilities.required[it.key()] = spec;
  }
  return capabilities;
}

void ReadStringSet(const base::ListValue& list_value,
                   std::set<std::string>* string_set) {
  DCHECK(string_set);
  for (auto i = list_value.begin(); i != list_value.end(); ++i) {
    std::string value;
    const base::Value* value_value = *i;
    CHECK(value_value->GetAsString(&value));
    string_set->insert(value);
  }
}

void ReadStringSetFromValue(const base::Value& value,
                            std::set<std::string>* string_set) {
  const base::ListValue* list_value = nullptr;
  CHECK(value.GetAsList(&list_value));
  ReadStringSet(*list_value, string_set);
}

void ReadStringSetFromDictionary(const base::DictionaryValue& dictionary,
                                 const std::string& key,
                                 std::set<std::string>* string_set) {
  const base::ListValue* list_value = nullptr;
  if (dictionary.HasKey(key))
    CHECK(dictionary.GetList(key, &list_value));
  if (list_value)
    ReadStringSet(*list_value, string_set);
}

mojo::CapabilitySpec BuildCapabilitiesV1(
    const base::DictionaryValue& value) {
  mojo::CapabilitySpec capabilities;

  const base::DictionaryValue* provided_value = nullptr;
  if (value.HasKey(Store::kCapabilities_ProvidedKey)) {
    CHECK(value.GetDictionary(Store::kCapabilities_ProvidedKey,
                              &provided_value));
  }
  if (provided_value) {
    mojo::CapabilityRequest provided;
    base::DictionaryValue::Iterator it(*provided_value);
    for(; !it.IsAtEnd(); it.Advance()) {
      mojo::Interfaces interfaces;
      ReadStringSetFromValue(it.value(), &interfaces);
      capabilities.provided[it.key()] = interfaces;
    }
  }

  const base::DictionaryValue* required_value = nullptr;
  if (value.HasKey(Store::kCapabilities_RequiredKey)) {
    CHECK(value.GetDictionary(Store::kCapabilities_RequiredKey,
                              &required_value));
  }
  if (required_value) {
    base::DictionaryValue::Iterator it(*required_value);
    for (; !it.IsAtEnd(); it.Advance()) {
      mojo::CapabilityRequest spec;
      const base::DictionaryValue* entry_value = nullptr;
      CHECK(it.value().GetAsDictionary(&entry_value));
      ReadStringSetFromDictionary(
          *entry_value, Store::kCapabilities_ClassesKey, &spec.classes);
      ReadStringSetFromDictionary(
          *entry_value, Store::kCapabilities_InterfacesKey, &spec.interfaces);
      capabilities.required[it.key()] = spec;
    }
  }
  return capabilities;
}

}  // namespace

Entry::Entry() {}
Entry::Entry(const std::string& name)
    : name_(name),
      qualifier_(mojo::GetNamePath(name)),
      display_name_(name) {}
Entry::Entry(const Entry& other) = default;
Entry::~Entry() {}

scoped_ptr<base::DictionaryValue> Entry::Serialize() const {
  scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
  value->SetInteger(Store::kManifestVersionKey, 1);
  value->SetString(Store::kNameKey, name_);
  value->SetString(Store::kDisplayNameKey, display_name_);
  value->SetString(Store::kQualifierKey, qualifier_);
  scoped_ptr<base::DictionaryValue> spec(new base::DictionaryValue);

  scoped_ptr<base::DictionaryValue> provided(new base::DictionaryValue);
  for (const auto& i : capabilities_.provided) {
    scoped_ptr<base::ListValue> interfaces(new base::ListValue);
    for (const auto& interface_name : i.second)
      interfaces->AppendString(interface_name);
    provided->Set(i.first, std::move(interfaces));
  }
  spec->Set(Store::kCapabilities_ProvidedKey, std::move(provided));

  scoped_ptr<base::DictionaryValue> required(new base::DictionaryValue);
  for (const auto& i : capabilities_.required) {
    scoped_ptr<base::DictionaryValue> request(new base::DictionaryValue);
    scoped_ptr<base::ListValue> classes(new base::ListValue);
    for (const auto& class_name : i.second.classes)
      classes->AppendString(class_name);
    request->Set(Store::kCapabilities_ClassesKey, std::move(classes));
    scoped_ptr<base::ListValue> interfaces(new base::ListValue);
    for (const auto& interface_name : i.second.interfaces)
      interfaces->AppendString(interface_name);
    request->Set(Store::kCapabilities_InterfacesKey, std::move(interfaces));
    required->Set(i.first, std::move(request));
  }
  spec->Set(Store::kCapabilities_RequiredKey, std::move(required));

  value->Set(Store::kCapabilitiesKey, std::move(spec));
  return value;
}

// static
scoped_ptr<Entry> Entry::Deserialize(const base::DictionaryValue& value) {
  scoped_ptr<Entry> entry(new Entry);
  int manifest_version = 0;
  if (value.HasKey(Store::kManifestVersionKey))
    CHECK(value.GetInteger(Store::kManifestVersionKey, &manifest_version));
  std::string name_string;
  if (!value.GetString(Store::kNameKey, &name_string)) {
    LOG(ERROR) << "Entry::Deserialize: dictionary has no name key";
    return nullptr;
  }
  if (!mojo::IsValidName(name_string)) {
    LOG(WARNING) << "Entry::Deserialize: " << name_string << " is not a valid "
                 << "Mojo name";
    return nullptr;
  }
  entry->set_name(name_string);
  if (value.HasKey(Store::kQualifierKey)) {
    std::string qualifier;
    CHECK(value.GetString(Store::kQualifierKey, &qualifier));
    entry->set_qualifier(qualifier);
  } else {
    entry->set_qualifier(mojo::GetNamePath(name_string));
  }
  std::string display_name;
  if (!value.GetString(Store::kDisplayNameKey, &display_name)) {
    LOG(WARNING) << "Entry::Deserialize: dictionary has no display_name key";
    return nullptr;
  }
  entry->set_display_name(display_name);
  const base::DictionaryValue* capabilities = nullptr;
  if (!value.GetDictionary(Store::kCapabilitiesKey, &capabilities)) {
    LOG(WARNING) << "Entry::Description: dictionary has no capabilities key";
    return nullptr;
  }
  if (manifest_version == 0)
    entry->set_capabilities(BuildCapabilitiesV0(*capabilities));
  else
    entry->set_capabilities(BuildCapabilitiesV1(*capabilities));

  if (value.HasKey(Store::kApplicationsKey)) {
    const base::ListValue* applications = nullptr;
    value.GetList(Store::kApplicationsKey, &applications);
    for (size_t i = 0; i < applications->GetSize(); ++i) {
      const base::DictionaryValue* application = nullptr;
      applications->GetDictionary(i, &application);
      scoped_ptr<Entry> child = Entry::Deserialize(*application);
      if (child) {
        child->set_package(entry.get());
        // Caller must assume ownership of these items.
        entry->applications_.insert(child.release());
      }
    }
  }

  return entry;
}

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

bool Entry::operator<(const Entry& other) const {
  return std::tie(name_, qualifier_, display_name_, capabilities_) <
         std::tie(other.name_, other.qualifier_, other.display_name_,
                  other.capabilities_);
}

}  // catalog

namespace mojo {

// static
shell::mojom::ResolveResultPtr
    TypeConverter<shell::mojom::ResolveResultPtr, catalog::Entry>::Convert(
        const catalog::Entry& input) {
  shell::mojom::ResolveResultPtr result(shell::mojom::ResolveResult::New());
  result->name = input.name();
  const catalog::Entry& package = input.package() ? *input.package() : input;
  result->resolved_name = package.name();
  result->qualifier = input.qualifier();
  result->capabilities =
      shell::mojom::CapabilitySpec::From(input.capabilities());
  result->package_url = mojo::util::FilePathToFileURL(package.path()).spec();
  return result;
}

}  // namespace mojo