// Copyright 2020 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * NodeParameters is a complicated interface with lots of code, but it is tested elsewhere
 * very thoroughly. This currently just includes unittests for the currently uncovered
 * functionality.
 */

#include <gtest/gtest.h>

#include <algorithm>
#include <filesystem>
#include <memory>
#include <string>
#include <vector>

#include "rclcpp/node.hpp"
#include "rclcpp/node_interfaces/node_parameters.hpp"

#include "test_msgs/msg/empty.hpp"

#include "../../mocking_utils/patch.hpp"
#include "../../utils/rclcpp_gtest_macros.hpp"

#include "rcpputils/filesystem_helper.hpp"

class TestNodeParameters : public ::testing::Test
{
public:
  void SetUp()
  {
    rclcpp::init(0, nullptr);
    rclcpp::NodeOptions options;
    options.allow_undeclared_parameters(true);
    node = std::make_shared<rclcpp::Node>("node", "ns", options);

    // This dynamic cast is not necessary for the unittest itself, but instead is used to ensure
    // the proper type is being tested and covered.
    node_parameters =
      dynamic_cast<rclcpp::node_interfaces::NodeParameters *>(
      node->get_node_parameters_interface().get());
    ASSERT_NE(nullptr, node_parameters);
    test_resources_path /= "test_node_parameters";
  }

  void TearDown()
  {
    rclcpp::shutdown();
  }

protected:
  std::shared_ptr<rclcpp::Node> node;
  rclcpp::node_interfaces::NodeParameters * node_parameters;

  std::filesystem::path test_resources_path{TEST_RESOURCES_DIRECTORY};
};

TEST_F(TestNodeParameters, construct_destruct_rcl_errors) {
  auto mock = mocking_utils::patch_and_return(
    "lib:rclcpp", rcl_arguments_get_param_overrides, RCL_RET_ERROR);
  EXPECT_THROW(
    std::make_shared<rclcpp::Node>("node2", "ns").reset(),
    rclcpp::exceptions::RCLError);
}

TEST_F(TestNodeParameters, list_parameters)
{
  std::vector<std::string> prefixes;
  const auto list_result = node_parameters->list_parameters(prefixes, 1u);

  // Currently the default parameters are 'use_sim_time' and 'start_type_description_service'
  size_t number_of_parameters = list_result.names.size();
  EXPECT_GE(2u, number_of_parameters);

  const std::string parameter_name = "new_parameter";
  const rclcpp::ParameterValue value(true);
  const rcl_interfaces::msg::ParameterDescriptor descriptor;
  const auto added_parameter_value =
    node_parameters->declare_parameter(parameter_name, value, descriptor, false);
  EXPECT_EQ(value.get<bool>(), added_parameter_value.get<bool>());

  auto list_result2 = node_parameters->list_parameters(prefixes, 1u);
  EXPECT_EQ(number_of_parameters + 1u, list_result2.names.size());

  EXPECT_NE(
    std::find(list_result2.names.begin(), list_result2.names.end(), parameter_name),
    list_result2.names.end());

  // Check prefixes and the depth relative to the given prefixes
  const std::string parameter_name2 = "prefix.new_parameter";
  const rclcpp::ParameterValue value2(true);
  const rcl_interfaces::msg::ParameterDescriptor descriptor2;
  const auto added_parameter_value2 =
    node_parameters->declare_parameter(parameter_name2, value2, descriptor2, false);
  EXPECT_EQ(value2.get<bool>(), added_parameter_value2.get<bool>());
  prefixes = {"prefix"};
  auto list_result3 = node_parameters->list_parameters(prefixes, 1u);
  EXPECT_EQ(1u, list_result3.names.size());
  EXPECT_NE(
    std::find(list_result3.names.begin(), list_result3.names.end(), parameter_name2),
    list_result3.names.end());

  // Check if prefix equals parameter name
  prefixes = {"new_parameter"};
  auto list_result4 = node_parameters->list_parameters(prefixes, 2u);
  EXPECT_EQ(1u, list_result4.names.size());
  EXPECT_NE(
    std::find(list_result4.names.begin(), list_result4.names.end(), parameter_name),
    list_result4.names.end());

  // Return all parameters when the depth = 0
  auto list_result5 = node_parameters->list_parameters(prefixes, 0u);
  EXPECT_EQ(1u, list_result5.names.size());
  EXPECT_NE(
    std::find(list_result5.names.begin(), list_result5.names.end(), parameter_name),
    list_result5.names.end());
}

TEST_F(TestNodeParameters, parameter_overrides_with_value)
{
  rclcpp::NodeOptions node_options;
  node_options.automatically_declare_parameters_from_overrides(true);
  node_options.append_parameter_override("param1", true);
  node_options.append_parameter_override("param2", 42);

  std::shared_ptr<rclcpp::Node> node2 = std::make_shared<rclcpp::Node>("node2", "ns", node_options);

  auto * node_parameters_interface =
    dynamic_cast<rclcpp::node_interfaces::NodeParameters *>(
    node2->get_node_parameters_interface().get());
  ASSERT_NE(nullptr, node_parameters_interface);

  const auto & parameter_overrides = node_parameters_interface->get_parameter_overrides();
  EXPECT_EQ(2u, parameter_overrides.size());
}

TEST_F(TestNodeParameters, parameter_overrides_with_parameter)
{
  rclcpp::NodeOptions node_options;
  node_options.automatically_declare_parameters_from_overrides(true);
  node_options.append_parameter_override(rclcpp::Parameter("param1", true));
  node_options.append_parameter_override(rclcpp::Parameter("param2", 42));

  std::shared_ptr<rclcpp::Node> node2 = std::make_shared<rclcpp::Node>("node2", "ns", node_options);

  auto * node_parameters_interface =
    dynamic_cast<rclcpp::node_interfaces::NodeParameters *>(
    node2->get_node_parameters_interface().get());
  ASSERT_NE(nullptr, node_parameters_interface);

  const auto & parameter_overrides = node_parameters_interface->get_parameter_overrides();
  EXPECT_EQ(2u, parameter_overrides.size());
}

TEST_F(TestNodeParameters, set_parameters) {
  rclcpp::NodeOptions node_options;
  node_options.allow_undeclared_parameters(true);

  rcl_interfaces::msg::ParameterDescriptor bool_descriptor;
  bool_descriptor.name = "bool_parameter";
  bool_descriptor.type = rcl_interfaces::msg::ParameterType::PARAMETER_BOOL;
  bool_descriptor.read_only = false;
  node_parameters->declare_parameter(
    "bool_parameter", rclcpp::ParameterValue(false), bool_descriptor, false);

  rcl_interfaces::msg::ParameterDescriptor read_only_descriptor;
  read_only_descriptor.name = "read_only_parameter";
  read_only_descriptor.type = rcl_interfaces::msg::ParameterType::PARAMETER_INTEGER;
  read_only_descriptor.read_only = true;
  node_parameters->declare_parameter(
    "read_only_parameter", rclcpp::ParameterValue(42), read_only_descriptor, false);

  const std::vector<rclcpp::Parameter> parameters = {
    rclcpp::Parameter("bool_parameter", true),
    rclcpp::Parameter("read_only_parameter", 42),
  };
  auto result = node_parameters->set_parameters(parameters);
  ASSERT_EQ(parameters.size(), result.size());
  EXPECT_TRUE(result[0].successful);
  EXPECT_FALSE(result[1].successful);
  EXPECT_STREQ(
    "parameter 'read_only_parameter' cannot be set because it is read-only",
    result[1].reason.c_str());

  RCLCPP_EXPECT_THROW_EQ(
    node_parameters->set_parameters({rclcpp::Parameter("", true)}),
    rclcpp::exceptions::InvalidParametersException("parameter name must not be empty"));

  result = node_parameters->set_parameters({rclcpp::Parameter("undeclared_parameter", 3.14159)});
  ASSERT_EQ(1u, result.size());
  EXPECT_TRUE(result[0].successful);
}

TEST_F(TestNodeParameters, add_remove_on_set_parameters_callback) {
  rcl_interfaces::msg::ParameterDescriptor bool_descriptor;
  bool_descriptor.name = "bool_parameter";
  bool_descriptor.type = rcl_interfaces::msg::ParameterType::PARAMETER_BOOL;
  bool_descriptor.read_only = false;
  node_parameters->declare_parameter(
    "bool_parameter", rclcpp::ParameterValue(false), bool_descriptor, false);
  const std::vector<rclcpp::Parameter> parameters = {rclcpp::Parameter("bool_parameter", true)};

  const std::string reason = "some totally not made up reason";
  auto callback = [reason](const std::vector<rclcpp::Parameter> &) {
      rcl_interfaces::msg::SetParametersResult result;
      result.successful = false;
      result.reason = reason;
      return result;
    };

  auto handle = node_parameters->add_on_set_parameters_callback(callback);
  auto result = node_parameters->set_parameters(parameters);
  ASSERT_EQ(1u, result.size());
  EXPECT_FALSE(result[0].successful);
  EXPECT_EQ(reason, result[0].reason);

  EXPECT_NO_THROW(node_parameters->remove_on_set_parameters_callback(handle.get()));

  RCLCPP_EXPECT_THROW_EQ(
    node_parameters->remove_on_set_parameters_callback(handle.get()),
    std::runtime_error("On set parameter callback doesn't exist"));
}

TEST_F(TestNodeParameters, add_remove_pre_set_parameters_callback) {
  //  `add_pre_set_parameters_callback` used to modify parameters list.
  auto modify_parameter_list_callback = [](std::vector<rclcpp::Parameter> & parameters) {
      for (const auto & param : parameters) {
        if (param.get_name() == "param1") {
          parameters.emplace_back("param2", 2.0);
        }
      }
    };

  // `add_pre_set_parameters_callback` used to make the parameters list empty.
  auto empty_parameter_list_callback = [](std::vector<rclcpp::Parameter> & parameters) {
      parameters = {};
    };

  auto handle1 =
    node_parameters->add_pre_set_parameters_callback(modify_parameter_list_callback);

  double default_value = 0.0;
  node_parameters->declare_parameter(
    "param1", rclcpp::ParameterValue(default_value));
  node_parameters->declare_parameter(
    "param2", rclcpp::ParameterValue(default_value));

  // verify that `declare_parameter` does not call any of the callbacks registered with
  // `add_pre_set_parameters_callback`
  EXPECT_TRUE(node_parameters->has_parameter("param1"));
  EXPECT_EQ(node_parameters->get_parameter("param1").get_value<double>(), default_value);
  EXPECT_TRUE(node_parameters->has_parameter("param2"));
  EXPECT_EQ(node_parameters->get_parameter("param2").get_value<double>(), default_value);

  // verify that the `param2` was set successfully conditioned on setting of
  // `param1`
  const std::vector<rclcpp::Parameter> parameters_to_be_set = {
    rclcpp::Parameter("param1", 1.0)};
  auto result = node_parameters->set_parameters(parameters_to_be_set);
  // we expect the result size to be same as the original "parameters_to_be_set"
  // since the pre set parameter callback will set the modified param list atomically.
  ASSERT_EQ(1u, result.size());
  EXPECT_TRUE(result[0].successful);
  EXPECT_TRUE(node_parameters->has_parameter("param1"));
  EXPECT_EQ(node_parameters->get_parameter("param1").get_value<double>(), 1.0);
  EXPECT_TRUE(node_parameters->has_parameter("param2"));
  EXPECT_EQ(node_parameters->get_parameter("param2").get_value<double>(), 2.0);
  EXPECT_NO_THROW(node_parameters->remove_pre_set_parameters_callback(handle1.get()));
  RCLCPP_EXPECT_THROW_EQ(
    node_parameters->remove_pre_set_parameters_callback(handle1.get()),
    std::runtime_error("Pre set parameter callback doesn't exist"));

  // verify that the result should be unsuccessful if the pre set callback makes
  // parameter list empty
  auto handle2 =
    node_parameters->add_pre_set_parameters_callback(empty_parameter_list_callback);
  auto results = node_parameters->set_parameters(parameters_to_be_set);

  std::string reason = "parameter list cannot be empty, this might be due to "
    "pre_set_parameters_callback modifying the original parameters list.";
  EXPECT_FALSE(results[0].successful);
  EXPECT_EQ(results[0].reason, reason);
  EXPECT_NO_THROW(node_parameters->remove_pre_set_parameters_callback(handle2.get()));
  RCLCPP_EXPECT_THROW_EQ(
    node_parameters->remove_pre_set_parameters_callback(handle2.get()),
    std::runtime_error("Pre set parameter callback doesn't exist"));
}

TEST_F(TestNodeParameters, add_remove_post_set_parameters_callback) {
  rcl_interfaces::msg::ParameterDescriptor param1_descriptor;
  param1_descriptor.name = "double_parameter1";
  param1_descriptor.type = rcl_interfaces::msg::ParameterType::PARAMETER_DOUBLE;
  param1_descriptor.read_only = false;

  rcl_interfaces::msg::ParameterDescriptor param2_descriptor;
  param2_descriptor.name = "double_parameter2";
  param2_descriptor.type = rcl_interfaces::msg::ParameterType::PARAMETER_DOUBLE;
  param2_descriptor.read_only = false;

  double variable_tracking_param1_internally = node_parameters->declare_parameter(
    "param1", rclcpp::ParameterValue(0.0), param1_descriptor, false).get<double>();
  double variable_tracking_param2_internally = node_parameters->declare_parameter(
    "param2", rclcpp::ParameterValue(0.0), param2_descriptor, false).get<double>();

  EXPECT_EQ(variable_tracking_param1_internally, 0.0);
  EXPECT_EQ(variable_tracking_param2_internally, 0.0);

  const std::vector<rclcpp::Parameter> parameters_to_be_set = {
    rclcpp::Parameter("param1", 1.0),
    rclcpp::Parameter("param2", 2.0)};

  // register a callback for successful set parameter and change the internally tracked variables.
  auto callback = [&](const std::vector<rclcpp::Parameter> & parameters) {
      for (const auto & param : parameters) {
        if (param.get_name() == "param1") {
          variable_tracking_param1_internally = param.get_value<double>();
        } else if (param.get_name() == "param2") {
          variable_tracking_param2_internally = param.get_value<double>();
        }
      }
    };

  auto handle = node_parameters->add_post_set_parameters_callback(callback);
  auto result = node_parameters->set_parameters(parameters_to_be_set);
  ASSERT_EQ(2u, result.size());
  EXPECT_TRUE(result[0].successful);
  EXPECT_TRUE(result[1].successful);
  EXPECT_TRUE(node_parameters->has_parameter("param1"));
  EXPECT_TRUE(node_parameters->has_parameter("param2"));
  EXPECT_EQ(variable_tracking_param1_internally, 1.0);
  EXPECT_EQ(variable_tracking_param2_internally, 2.0);

  EXPECT_NO_THROW(node_parameters->remove_post_set_parameters_callback(handle.get()));

  RCLCPP_EXPECT_THROW_EQ(
    node_parameters->remove_post_set_parameters_callback(handle.get()),
    std::runtime_error("Post set parameter callback doesn't exist"));
}

TEST_F(TestNodeParameters, set_param_recursive_in_post_set_parameters_callback) {
  rclcpp::Subscription<test_msgs::msg::Empty>::SharedPtr subscription_;
  rclcpp::Publisher<test_msgs::msg::Empty>::SharedPtr publisher_;

  rcl_interfaces::msg::ParameterDescriptor param_descriptor;
  param_descriptor.name = "create_entities";
  param_descriptor.type = rcl_interfaces::msg::ParameterType::PARAMETER_BOOL;
  param_descriptor.read_only = false;

  bool result = node_parameters->declare_parameter(
    "create_entities", rclcpp::ParameterValue(false), param_descriptor, false).get<bool>();
  EXPECT_EQ(result, false);

  // Register a callback to create/delete publisher and subscription with
  // QoS override parameter options. This will call declare_parameter recursively
  // during this callback.
  auto sub_callback = [](test_msgs::msg::Empty::ConstSharedPtr) {};
  auto callback = [&](const std::vector<rclcpp::Parameter> & parameters) {
      for (const auto & parameter : parameters) {
        if (parameter.get_name() == "create_entities" &&
          parameter.get_type() == rclcpp::ParameterType::PARAMETER_BOOL)
        {
          if (parameter.as_bool()) {
            ASSERT_EQ(subscription_, nullptr);
            rclcpp::SubscriptionOptions sub_options;
            // This will declare the QoS override parameters in this callback.
            sub_options.qos_overriding_options =
              rclcpp::QosOverridingOptions::with_default_policies();
            subscription_ = node->create_subscription<test_msgs::msg::Empty>(
              "empty",
              rclcpp::QoS(10),
              sub_callback,
              sub_options);
            ASSERT_NE(subscription_, nullptr);
            ASSERT_EQ(publisher_, nullptr);
            rclcpp::PublisherOptions pub_options;
            // This will declare the QoS override parameters in this callback.
            pub_options.qos_overriding_options =
              rclcpp::QosOverridingOptions::with_default_policies();
            publisher_ = node->create_publisher<test_msgs::msg::Empty>(
              "empty",
              rclcpp::QoS(10),
              pub_options);
            ASSERT_NE(publisher_, nullptr);
          } else {
            ASSERT_NE(subscription_, nullptr);
            subscription_.reset();
            ASSERT_EQ(subscription_, nullptr);
            ASSERT_NE(publisher_, nullptr);
            publisher_.reset();
            ASSERT_EQ(publisher_, nullptr);
          }
        }
      }
    };

  auto handle = node_parameters->add_post_set_parameters_callback(callback);
  ASSERT_NE(handle, nullptr);
  EXPECT_TRUE(node_parameters->has_parameter("create_entities"));
  EXPECT_EQ(node_parameters->get_parameter("create_entities").get_value<bool>(), false);

  // This will call the registered callback, that will create endpoints with
  // declaring the QoS override parameters recursively.
  auto results = node_parameters->set_parameters({rclcpp::Parameter("create_entities", true)});
  EXPECT_TRUE(!results.empty() && results[0].successful);

  EXPECT_TRUE(node_parameters->has_parameter("create_entities"));
  EXPECT_EQ(node_parameters->get_parameter("create_entities").get_value<bool>(), true);

  // Destroy publisher and subscription endpoints.
  results = node_parameters->set_parameters({rclcpp::Parameter("create_entities", false)});
  EXPECT_TRUE(!results.empty() && results[0].successful);

  EXPECT_TRUE(node_parameters->has_parameter("create_entities"));
  EXPECT_EQ(node_parameters->get_parameter("create_entities").get_value<bool>(), false);

  // Make sure recreation can also work without any exception.
  results = node_parameters->set_parameters({rclcpp::Parameter("create_entities", true)});
  EXPECT_TRUE(!results.empty() && results[0].successful);

  EXPECT_NO_THROW(node_parameters->remove_post_set_parameters_callback(handle.get()));
}

TEST_F(TestNodeParameters, wildcard_with_namespace)
{
  rclcpp::NodeOptions opts;
  opts.arguments(
  {
    "--ros-args",
    "--params-file", (test_resources_path / "wildcards.yaml").string()
  });

  std::shared_ptr<rclcpp::Node> node = std::make_shared<rclcpp::Node>("node2", "ns", opts);

  auto * node_parameters =
    dynamic_cast<rclcpp::node_interfaces::NodeParameters *>(
    node->get_node_parameters_interface().get());
  ASSERT_NE(nullptr, node_parameters);

  const auto & parameter_overrides = node_parameters->get_parameter_overrides();
  EXPECT_EQ(7u, parameter_overrides.size());
  EXPECT_EQ(parameter_overrides.at("full_wild").get<std::string>(), "full_wild");
  EXPECT_EQ(parameter_overrides.at("namespace_wild").get<std::string>(), "namespace_wild");
  EXPECT_EQ(
    parameter_overrides.at("namespace_wild_another").get<std::string>(),
    "namespace_wild_another");
  EXPECT_EQ(
    parameter_overrides.at("namespace_wild_one_star").get<std::string>(),
    "namespace_wild_one_star");
  EXPECT_EQ(parameter_overrides.at("node_wild_in_ns").get<std::string>(), "node_wild_in_ns");
  EXPECT_EQ(
    parameter_overrides.at("node_wild_in_ns_another").get<std::string>(),
    "node_wild_in_ns_another");
  EXPECT_EQ(parameter_overrides.at("explicit_in_ns").get<std::string>(), "explicit_in_ns");
  EXPECT_EQ(parameter_overrides.count("should_not_appear"), 0u);
}

TEST_F(TestNodeParameters, wildcard_no_namespace)
{
  rclcpp::NodeOptions opts;
  opts.arguments(
  {
    "--ros-args",
    "--params-file", (test_resources_path / "wildcards.yaml").string()
  });

  std::shared_ptr<rclcpp::Node> node = std::make_shared<rclcpp::Node>("node2", opts);

  auto * node_parameters =
    dynamic_cast<rclcpp::node_interfaces::NodeParameters *>(
    node->get_node_parameters_interface().get());
  ASSERT_NE(nullptr, node_parameters);

  const auto & parameter_overrides = node_parameters->get_parameter_overrides();
  EXPECT_EQ(5u, parameter_overrides.size());
  EXPECT_EQ(parameter_overrides.at("full_wild").get<std::string>(), "full_wild");
  EXPECT_EQ(parameter_overrides.at("namespace_wild").get<std::string>(), "namespace_wild");
  EXPECT_EQ(
    parameter_overrides.at("namespace_wild_another").get<std::string>(),
    "namespace_wild_another");
  EXPECT_EQ(parameter_overrides.at("node_wild_no_ns").get<std::string>(), "node_wild_no_ns");
  EXPECT_EQ(parameter_overrides.at("explicit_no_ns").get<std::string>(), "explicit_no_ns");
  EXPECT_EQ(parameter_overrides.count("should_not_appear"), 0u);
  // "/*" match exactly one token, not expect to get `namespace_wild_one_star`
  EXPECT_EQ(parameter_overrides.count("namespace_wild_one_star"), 0u);
}

TEST_F(TestNodeParameters, params_by_order)
{
  rclcpp::NodeOptions opts;
  opts.arguments(
  {
    "--ros-args",
    "--params-file", (test_resources_path / "params_by_order.yaml").string()
  });

  std::shared_ptr<rclcpp::Node> node = std::make_shared<rclcpp::Node>("node2", "ns", opts);

  auto * node_parameters =
    dynamic_cast<rclcpp::node_interfaces::NodeParameters *>(
    node->get_node_parameters_interface().get());
  ASSERT_NE(nullptr, node_parameters);

  const auto & parameter_overrides = node_parameters->get_parameter_overrides();
  EXPECT_EQ(3u, parameter_overrides.size());
  EXPECT_EQ(parameter_overrides.at("a_value").get<std::string>(), "last_one_win");
  EXPECT_EQ(parameter_overrides.at("foo").get<std::string>(), "foo");
  EXPECT_EQ(parameter_overrides.at("bar").get<std::string>(), "bar");
}

TEST_F(TestNodeParameters, complicated_wildcards)
{
  rclcpp::NodeOptions opts;
  opts.arguments(
  {
    "--ros-args",
    "--params-file", (test_resources_path / "complicated_wildcards.yaml").string()
  });

  {
    // regex matched: /**/foo/*/bar
    std::shared_ptr<rclcpp::Node> node =
      std::make_shared<rclcpp::Node>("node2", "/a/b/c/foo/d/bar", opts);

    auto * node_parameters =
      dynamic_cast<rclcpp::node_interfaces::NodeParameters *>(
      node->get_node_parameters_interface().get());
    ASSERT_NE(nullptr, node_parameters);

    const auto & parameter_overrides = node_parameters->get_parameter_overrides();
    EXPECT_EQ(2u, parameter_overrides.size());
    EXPECT_EQ(parameter_overrides.at("foo").get<std::string>(), "foo");
    EXPECT_EQ(parameter_overrides.at("bar").get<std::string>(), "bar");
  }

  {
    // regex not matched: /**/foo/*/bar
    std::shared_ptr<rclcpp::Node> node =
      std::make_shared<rclcpp::Node>("node2", "/a/b/c/foo/bar", opts);

    auto * node_parameters =
      dynamic_cast<rclcpp::node_interfaces::NodeParameters *>(
      node->get_node_parameters_interface().get());
    ASSERT_NE(nullptr, node_parameters);

    const auto & parameter_overrides = node_parameters->get_parameter_overrides();
    EXPECT_EQ(0u, parameter_overrides.size());
  }
}
