Manager::Manager(const rclcpp::NodeOptions& options) : Node("lidar_manager", options)
{
  RCLCPP_INFO(this->get_logger(), "Manager http constructor");
  // get parameters from http server
  auto host_desc = rcl_interfaces::msg::ParameterDescriptor{};
  host_desc.description = "host ip";
  this->declare_parameter("host", "localhost", host_desc);
  auto const host = this->get_parameter("host").as_string();

  auto port_desc = rcl_interfaces::msg::ParameterDescriptor{};
  port_desc.description = "host port";
  this->declare_parameter("port", "3000", port_desc);
  auto const port = this->get_parameter("port").as_string();

  auto target_desc = rcl_interfaces::msg::ParameterDescriptor{};
  target_desc.description = "target url";
  this->declare_parameter("target", "/sensor-config/sensor/lidar", target_desc);
  auto const target = this->get_parameter("target").as_string();

  namespace beast = boost::beast;  // from <boost/beast.hpp>
  namespace http = beast::http;    // from <boost/beast/http.hpp>
  namespace net = boost::asio;     // from <boost/asio.hpp>
  using tcp = net::ip::tcp;        // from <boost/asio/ip/tcp.hpp>
  // The io_context is required for all I/O
  net::io_context ioc;

  // These objects perform our I/O
  tcp::resolver resolver(ioc);
  beast::tcp_stream stream(ioc);

  // Look up the domain name
  auto const results = resolver.resolve(host, port);

  // Make the connection on the IP address we get from a lookup
  stream.connect(results);

  // Set up an HTTP GET request message
  http::request<http::string_body> req{ http::verb::get, target, 11 };
  req.set(http::field::host, host);
  req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);

  // Send the HTTP request to the remote host
  http::write(stream, req);

  // This buffer is used for reading and must be persisted
  beast::flat_buffer buffer;

  // Declare a container to hold the response
  http::response<http::string_body> res;

  // Receive the HTTP response
  http::read(stream, buffer, res);

  // Write the message to standard out
  RCLCPP_INFO(this->get_logger(), "Received lidar config from http: %s", res.body().c_str());

  // Gracefully close the socket
  beast::error_code ec;
  stream.socket().shutdown(tcp::socket::shutdown_both, ec);

  // not_connected happens sometimes
  // so don't bother reporting it.
  //
  if (ec && ec != beast::errc::not_connected)
  {
    RCLCPP_ERROR(this->get_logger(), "Error: %s", ec.message().c_str());
    std::terminate();
  }

  // 解析 http 结果到 雷达配置列表
  std::vector<std::tuple<robosense::lidar::RSDriverParam, RosSenderParam>> lidar_config_list;

  try
  {
    nlohmann::json cfg_list = nlohmann::json::parse(res.body().c_str());
    for (const auto& cfg : cfg_list)
    {
      robosense::lidar::RSDriverParam driver_cfg;
      driver_cfg.input_type = robosense::lidar::InputType::ONLINE_LIDAR;
      driver_cfg.input_param.msop_port = cfg["msop_port"];
      driver_cfg.input_param.difop_port = cfg["difop_port"];
      driver_cfg.frame_id = cfg["sensor_id"];
      std::string lidar_type_str = cfg["device_type"];
      driver_cfg.lidar_type = robosense::lidar::strToLidarType(lidar_type_str);
      RosSenderParam ros_param{
        .topic_name = cfg["topic"],
        .frame_id = driver_cfg.frame_id,
      };

      lidar_config_list.emplace_back(driver_cfg, ros_param);
    }
  }
  catch (const std::exception& e)
  {
    RCLCPP_ERROR_STREAM(this->get_logger(), fmt::format("接收到的http配置解析出错 {}", e.what()));
    std::terminate();
  }

  RCLCPP_INFO_STREAM(this->get_logger(),
                     fmt::format("接收到的http配置解析成功 识别到{}个雷达配置", lidar_config_list.size()));

  for (const auto& [param, ros_param] : lidar_config_list)
  {
    std::string lidar_type_str = robosense::lidar::lidarTypeToStr(param.lidar_type);
    RCLCPP_INFO_STREAM(this->get_logger(), fmt::format("{} 雷达  id: {} topic: {}", lidar_type_str, ros_param.frame_id,
                                                       ros_param.topic_name));
    auto receiver = std::make_shared<LidarReceiver>(param, *this);
    auto sender = std::make_shared<RosSender>(ros_param, *this);
    sender->Connect(receiver.get());

    receivers_.push_back(receiver);
    senders_.push_back(sender);
  }

  start_receive();
}