// Copyright 2014 The Chromium OS 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 "chromeos-dbus-bindings/adaptor_generator.h"

#include <string>
#include <vector>

#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <gtest/gtest.h>

#include "chromeos-dbus-bindings/interface.h"
#include "chromeos-dbus-bindings/test_utils.h"

using std::string;
using std::vector;
using testing::Test;

namespace chromeos_dbus_bindings {

namespace {

const char kDBusTypeArryOfObjects[] = "ao";
const char kDBusTypeBool[] = "b";
const char kDBusTypeInt32[] = "i";
const char kDBusTypeInt64[] = "x";
const char kDBusTypeString[] = "s";

const char kPropertyAccessReadOnly[] = "read";
const char kPropertyAccessReadWrite[] = "readwrite";

const char kInterfaceName[] = "org.chromium.Test";
const char kInterfaceName2[] = "org.chromium.Test2";

const char kExpectedContent[] = R"literal_string(
#include <memory>
#include <string>
#include <tuple>
#include <vector>

#include <base/macros.h>
#include <dbus/object_path.h>
#include <brillo/any.h>
#include <brillo/dbus/dbus_object.h>
#include <brillo/dbus/exported_object_manager.h>
#include <brillo/variant_dictionary.h>

namespace org {
namespace chromium {

// Interface definition for org::chromium::Test.
class TestInterface {
 public:
  virtual ~TestInterface() = default;

  virtual bool Kaneda(
      brillo::ErrorPtr* error,
      dbus::Message* message,
      const std::string& in_iwata,
      const std::vector<dbus::ObjectPath>& in_clarke,
      std::string* out_3) = 0;
  virtual bool Tetsuo(
      brillo::ErrorPtr* error,
      int32_t in_1,
      int64_t* out_2) = 0;
  virtual bool Kei(
      brillo::ErrorPtr* error) = 0;
  virtual bool Kiyoko(
      brillo::ErrorPtr* error,
      int64_t* out_akira,
      std::string* out_2) = 0;
};

// Interface adaptor for org::chromium::Test.
class TestAdaptor {
 public:
  TestAdaptor(TestInterface* interface) : interface_(interface) {}

  void RegisterWithDBusObject(brillo::dbus_utils::DBusObject* object) {
    brillo::dbus_utils::DBusInterface* itf =
        object->AddOrGetInterface("org.chromium.Test");

    itf->AddSimpleMethodHandlerWithErrorAndMessage(
        "Kaneda",
        base::Unretained(interface_),
        &TestInterface::Kaneda);
    itf->AddSimpleMethodHandlerWithError(
        "Tetsuo",
        base::Unretained(interface_),
        &TestInterface::Tetsuo);
    itf->AddSimpleMethodHandlerWithError(
        "Kei",
        base::Unretained(interface_),
        &TestInterface::Kei);
    itf->AddSimpleMethodHandlerWithError(
        "Kiyoko",
        base::Unretained(interface_),
        &TestInterface::Kiyoko);

    signal_Update_ = itf->RegisterSignalOfType<SignalUpdateType>("Update");
    signal_Mapping_ = itf->RegisterSignalOfType<SignalMappingType>("Mapping");

    itf->AddProperty(CharacterNameName(), &character_name_);
    write_property_.SetAccessMode(
        brillo::dbus_utils::ExportedPropertyBase::Access::kReadWrite);
    write_property_.SetValidator(
        base::Bind(&TestAdaptor::ValidateWriteProperty,
                   base::Unretained(this)));
    itf->AddProperty(WritePropertyName(), &write_property_);
  }

  void SendUpdateSignal() {
    auto signal = signal_Update_.lock();
    if (signal)
      signal->Send();
  }
  void SendMappingSignal(
      const std::string& in_key,
      const std::vector<dbus::ObjectPath>& in_2) {
    auto signal = signal_Mapping_.lock();
    if (signal)
      signal->Send(in_key, in_2);
  }

  static const char* CharacterNameName() { return "CharacterName"; }
  std::string GetCharacterName() const {
    return character_name_.GetValue().Get<std::string>();
  }
  void SetCharacterName(const std::string& character_name) {
    character_name_.SetValue(character_name);
  }

  static const char* WritePropertyName() { return "WriteProperty"; }
  std::string GetWriteProperty() const {
    return write_property_.GetValue().Get<std::string>();
  }
  void SetWriteProperty(const std::string& write_property) {
    write_property_.SetValue(write_property);
  }
  virtual bool ValidateWriteProperty(
      brillo::ErrorPtr* /*error*/, const std::string& /*value*/) {
    return true;
  }

  static dbus::ObjectPath GetObjectPath() {
    return dbus::ObjectPath{"/org/chromium/Test"};
  }

 private:
  using SignalUpdateType = brillo::dbus_utils::DBusSignal<>;
  std::weak_ptr<SignalUpdateType> signal_Update_;

  using SignalMappingType = brillo::dbus_utils::DBusSignal<
      std::string /*key*/,
      std::vector<dbus::ObjectPath>>;
  std::weak_ptr<SignalMappingType> signal_Mapping_;

  brillo::dbus_utils::ExportedProperty<std::string> character_name_;
  brillo::dbus_utils::ExportedProperty<std::string> write_property_;

  TestInterface* interface_;  // Owned by container of this adapter.

  DISALLOW_COPY_AND_ASSIGN(TestAdaptor);
};

}  // namespace chromium
}  // namespace org

namespace org {
namespace chromium {

// Interface definition for org::chromium::Test2.
class Test2Interface {
 public:
  virtual ~Test2Interface() = default;

  virtual std::string Kaneda2(
      const std::string& in_iwata) const = 0;
  virtual void Tetsuo2(
      std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<int64_t>> response,
      int32_t in_1) = 0;
  virtual void Kei2(
      std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<bool>> response,
      dbus::Message* message) = 0;
};

// Interface adaptor for org::chromium::Test2.
class Test2Adaptor {
 public:
  Test2Adaptor(Test2Interface* interface) : interface_(interface) {}

  void RegisterWithDBusObject(brillo::dbus_utils::DBusObject* object) {
    brillo::dbus_utils::DBusInterface* itf =
        object->AddOrGetInterface("org.chromium.Test2");

    itf->AddSimpleMethodHandler(
        "Kaneda2",
        base::Unretained(interface_),
        &Test2Interface::Kaneda2);
    itf->AddMethodHandler(
        "Tetsuo2",
        base::Unretained(interface_),
        &Test2Interface::Tetsuo2);
    itf->AddMethodHandlerWithMessage(
        "Kei2",
        base::Unretained(interface_),
        &Test2Interface::Kei2);
  }

 private:
  Test2Interface* interface_;  // Owned by container of this adapter.

  DISALLOW_COPY_AND_ASSIGN(Test2Adaptor);
};

}  // namespace chromium
}  // namespace org
)literal_string";

}  // namespace
class AdaptorGeneratorTest : public Test {
 public:
  void SetUp() override {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
  }

 protected:
  base::FilePath CreateInputFile(const string& contents) {
    base::FilePath path;
    EXPECT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &path));
    int written = base::WriteFile(path, contents.c_str(), contents.size());
    EXPECT_EQ(contents.size(), static_cast<size_t>(written));
    return path;
  }

  base::ScopedTempDir temp_dir_;
};

TEST_F(AdaptorGeneratorTest, GenerateAdaptors) {
  Interface interface;
  interface.name = kInterfaceName;
  interface.path = "/org/chromium/Test";
  interface.methods.emplace_back(
      "Kaneda",
      vector<Interface::Argument>{
          {"iwata", kDBusTypeString},
          {"clarke", kDBusTypeArryOfObjects}},
      vector<Interface::Argument>{{"", kDBusTypeString}});
  interface.methods.back().include_dbus_message = true;
  interface.methods.emplace_back(
      "Tetsuo",
      vector<Interface::Argument>{{"", kDBusTypeInt32}},
      vector<Interface::Argument>{{"", kDBusTypeInt64}});
  interface.methods.emplace_back("Kei");
  // Interface methods with more than one return argument should be ignored.
  interface.methods.emplace_back(
      "Kiyoko",
      vector<Interface::Argument>{},
      vector<Interface::Argument>{
          {"akira", kDBusTypeInt64},
          {"", kDBusTypeString}});
  // Signals generate helper methods to send them.
  interface.signals.emplace_back(
      "Update",
      vector<Interface::Argument>{});
  interface.signals.emplace_back(
      "Mapping",
      vector<Interface::Argument>{
          {"key", kDBusTypeString},
          {"", kDBusTypeArryOfObjects}});
  interface.properties.emplace_back(
      "CharacterName",
      kDBusTypeString,
      kPropertyAccessReadOnly);
  interface.properties.emplace_back(
      "WriteProperty",
      kDBusTypeString,
      kPropertyAccessReadWrite);

  Interface interface2;
  interface2.name = kInterfaceName2;
  interface2.methods.emplace_back(
      "Kaneda2",
      vector<Interface::Argument>{{"iwata", kDBusTypeString}},
      vector<Interface::Argument>{{"", kDBusTypeString}});
  interface2.methods.back().is_const = true;
  interface2.methods.back().kind = Interface::Method::Kind::kSimple;
  interface2.methods.emplace_back(
      "Tetsuo2",
      vector<Interface::Argument>{{"", kDBusTypeInt32}},
      vector<Interface::Argument>{{"", kDBusTypeInt64}});
  interface2.methods.back().kind = Interface::Method::Kind::kAsync;
  interface2.methods.emplace_back(
      "Kei2",
      vector<Interface::Argument>{},
      vector<Interface::Argument>{{"", kDBusTypeBool}});
  interface2.methods.back().kind = Interface::Method::Kind::kAsync;
  interface2.methods.back().include_dbus_message = true;

  base::FilePath output_path = temp_dir_.path().Append("output.h");
  EXPECT_TRUE(AdaptorGenerator::GenerateAdaptors({interface, interface2},
                                                 output_path));
  string contents;
  EXPECT_TRUE(base::ReadFileToString(output_path, &contents));
  // The header guards contain the (temporary) filename, so we search for
  // the content we need within the string.
  test_utils::EXPECT_TEXT_CONTAINED(kExpectedContent, contents);
}

}  // namespace chromeos_dbus_bindings