// third party
#include <nav_msgs/Path.h>
#include <pcl/console/parse.h>
#include <pcl/conversions.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl_conversions/pcl_conversions.h>
#include <ros/ros.h>
#include <sensor_msgs/PointCloud.h>
#include <sensor_msgs/PointCloud2.h>
#include <std_msgs/String.h>
#include <visualization_msgs/Marker.h>
// stl
#include <unistd.h>
#include <sstream>
// local
#include "builder/map_builder.h"
#include "common/file_utils.h"
#include "common/performance/simple_prof.h"
#include "common/simple_time.h"
#include "ros_node/kitti_reader.h"
#include "ros_node/msg_conversion.h"
#include "ros_node/playable_bag.h"
#include "ros_node/tf_bridge.h"
#include "tf/transform_broadcaster.h"

using static_map::MapBuilder;
static MapBuilder::Ptr map_builder;

using static_map::data::ImuMsg;
using static_map::data::NavSatFixMsg;
using static_map::data::OdomMsg;

namespace {

constexpr char kMapFrameId[] = "/map";

void pointcloud_callback(const sensor_msgs::PointCloud2::ConstPtr& msg) {
  pcl::PCLPointCloud2 pcl_pc2;
  pcl_conversions::toPCL(*msg, pcl_pc2);
  MapBuilder::PointCloudPtr incoming_cloud(new MapBuilder::PointCloudType);
  pcl::fromPCLPointCloud2(pcl_pc2, *incoming_cloud);

  std::vector<int> inliers;  // no use, just for the function
  pcl::removeNaNFromPointCloud(*incoming_cloud, *incoming_cloud, inliers);
  map_builder->InsertPointcloudMsg(incoming_cloud);
}

void imu_callback(const sensor_msgs::Imu& imu_msg) {
  ImuMsg::Ptr incomming_imu(new ImuMsg);
  *incomming_imu = static_map_ros::ToLocalImu(imu_msg);
  map_builder->InsertImuMsg(incomming_imu);
}

void odom_callback(const nav_msgs::Odometry& odom_msg) {
  OdomMsg::Ptr local_odom(new OdomMsg);
  *loca

  map_builder->InsertOdomMsg(local_odom);
}

void gps_callback(const sensor_msgs::NavSatFix& gps_msg) {
  NavSatFixMsg::Ptr local_gps(new NavSatFixMsg);
  *local_gps = static_map_ros::ToLocalNavSatMsg(gps_msg);
  map_builder->InsertGpsMsg(local_gps);
}

}  // namespace

int main(int argc, char** argv) {
  ros::init(argc, argv, "static_mapping_node");
  ros::NodeHandle n;

  SIMPLE_PROF_USE_MS;
  SIMPLE_PROF_MERGE_OUTPUT;

  google::InitGoogleLogging(argv[0]);
  FLAGS_alsologtostderr = true;
  FLAGS_colorlogtostderr = true;
  google::InstallFailureSignalHandler();

  // parse auguements
  // point cloud
  std::string point_cloud_topic = "";
  pcl::console::parse_argument(argc, argv, "-pc", point_cloud_topic);
  if (point_cloud_topic.empty()) {
    PRINT_ERROR("point cloud topic is empty!");
    return -1;
  } else {
    PRINT_INFO_FMT("Get point cloud from ROS topic: %s",
                   point_cloud_topic.c_str());
  }
  ros::Subscriber sub_pointcloud =
      n.subscribe(point_cloud_topic, 10, pointcloud_callback);
  std::string cloud_frame_id = "base_link";
  pcl::console::parse_argument(argc, argv, "-pc_frame_id", cloud_frame_id);

  // imu
  std::string imu_topic = "";
  bool use_imu = false;
  ros::Subscriber sub_imu;
  std::string imu_frame_id = "/novatel_imu";
  pcl::console::parse_argument(argc, argv, "-imu", imu_topic);
  if (imu_topic.empty()) {
    PRINT_WARNING("No imu topic, expect no imu messages.");
  } else {
    PRINT_INFO_FMT("Get imu data from ROS topic: %s", imu_topic.c_str());
    sub_imu = n.subscribe(imu_topic, 100, imu_callback);
    pcl::console::parse_argument(argc, argv, "-imu_frame_id", imu_frame_id);
    use_imu = true;
  }

  // odom
  bool use_odom = false;
  std::string odom_topic = "";
  std::string odom_frame_id = "";
  pcl::console::parse_argument(argc, argv, "-odom", odom_topic);
  pcl::console::parse_argument(argc, argv, "-odom_frame_id", odom_frame_id);
  ros::Subscriber sub_odom;
  if (!odom_topic.empty() && !odom_frame_id.empty()) {
    sub_odom = n.subscribe(odom_topic, 100, odom_callback);
    use_odom = true;
    PRINT_INFO_FMT("Get odom data from ROS topic: %s", odom_topic.c_str());
  } else {
    PRINT_WARNING("No odom data accessed!");
  }

  // gps
  bool use_gps = false;
  std::string gps_topic = "";
  std::string gps_frame_id = "";
  pcl::console::parse_argument(argc, argv, "-gps", gps_topic);
  pcl::console::parse_argument(argc, argv, "-gps_frame_id", gps_frame_id);
  ros::Subscriber gps_subscriber;
  if (!gps_topic.empty() && !gps_frame_id.empty()) {
    gps_subscriber = n.subscribe(gps_topic, 100, gps_callback);
    use_gps = true;
    PRINT_INFO_FMT("Get gps data from ROS topic: %s", gps_topic.c_str());
  } else {
    PRINT_WARNING("No GPS data accessed!");
  }

  // config file
  std::string config_file = "";
  pcl::console::parse_argument(argc, argv, "-cfg", config_file);

  // urdf file
  std::string urdf_file = "";
  pcl::console::parse_argument(argc, argv, "-urdf", urdf_file);

  // tracking frame
  std::string tracking_frame = "base_link";
  pcl::console::parse_argument(argc, argv, "-track", tracking_frame);
  CHECK(!tracking_frame.empty())
      << "tracking frame id should be set." << std::endl;

  // ros bag
  std::string bag_file = "";
  pcl::console::parse_argument(argc, argv, "-bag", bag_file);
  if (!bag_file.empty() && !static_map::common::FileExist(bag_file)) {
    PRINT_WARNING_FMT("%s does not exist.", bag_file.c_str());
    bag_file = "";
  }

  // kitti data set
  std::string kitti_path = "";
  pcl::console::parse_argument(argc, argv, "-kitti", kitti_path);
  if (!kitti_path.empty() && !static_map::common::FileExist(kitti_path)) {
    PRINT_WARNING_FMT("%s does not exist.", kitti_path.c_str());
    kitti_path = "";
  }

  if (!kitti_path.empty() && !bag_file.empty()) {
    PRINT_ERROR("Should not read bag and kitti data set at the same time.");
    return -1;
  }

  std::string pub_topics = "";
  pcl::console::parse_argument(argc, argv, "-pubs", pub_topics);

  ros::Publisher map_publisher =
      n.advertise<sensor_msgs::PointCloud2>("/optimized_map", 1);
  ros::Publisher submap_publisher =
      n.advertise<sensor_msgs::PointCloud2>("/submap", 1);
  ros::Publisher pose_marker_pub =
      n.advertise<visualization_msgs::Marker>("/submap_pose", 1);
  ros::Publisher path_pub =
      n.advertise<nav_msgs::Path>("/static_mapping_path", 1);
  ros::Publisher edge_markers_pub =
      n.advertise<visualization_msgs::Marker>("/loop_edges", 1);
  tf::TransformBroadcaster tf_broadcaster;

  auto show_function =
      [&](const static_map::MapBuilder::PointCloudPtr& cloud) -> void {
    sensor_msgs::PointCloud2 map_pointcloud;
    pcl::toROSMsg(*cloud, map_pointcloud);
    map_pointcloud.header.frame_id = kMapFrameId;
    map_pointcloud.header.stamp = ros::Time::now();
    map_publisher.publish(map_pointcloud);
  };

  auto show_submap_function =
      [&](const static_map::MapBuilder::PointCloudPtr& cloud,
          const Eigen::Matrix4d& pose) -> void {
    tf::Transform transform;
    transform.setOrigin(tf::Vector3(pose(0, 3), pose(1, 3), pose(2, 3)));
    Eigen::Quaterniond rotation(Eigen::Matrix3d(pose.block(0, 0, 3, 3)));
    tf::Quaternion q;
    q.setW(rotation.w());
    q.setX(rotation.x());
    q.setY(rotation.y());
    q.setZ(rotation.z());
    transform.setRotation(q);
    tf_broadcaster.sendTransform(tf::StampedTransform(
        transform, ros::Time::now(), kMapFrameId, tracking_frame));

    sensor_msgs::PointCloud2 submap_pointcloud;
    pcl::toROSMsg(*cloud, submap_pointcloud);
    submap_pointcloud.header.frame_id = tracking_frame;
    submap_pointcloud.header.stamp = ros::Time::now();
    submap_publisher.publish(submap_pointcloud);
  };

  const auto publish_path = [&](const std::vector<Eigen::Matrix4d>& poses) {
    nav_msgs::Path path;
    path.header.frame_id = kMapFrameId;

    path.poses.reserve(poses.size());
    for (const Eigen::Matrix4d& pose : poses) {
      geometry_msgs::PoseStamped pose_ros;
      pose_ros.header.frame_id = kMapFrameId;
      Eigen::Quaterniond q(Eigen::Matrix3d(pose.block(0, 0, 3, 3)));
      pose_ros.pose.orientation.w = q.w();
      pose_ros.pose.orientation.x = q.x();
      pose_ros.pose.orientation.y = q.y();
      pose_ros.pose.orientation.z = q.z();
      pose_ros.pose.position.x = pose(0, 3);
      pose_ros.pose.position.y = pose(1, 3);
      pose_ros.pose.position.z = pose(2, 3);

      path.poses.push_back(pose_ros);
    }
    path_pub.publish(path);
  };

  const auto publish_edges =
      [&](const std::map<int64_t, static_map::back_end::ViewGraph::GraphItem>&
              graph) {
        visualization_msgs::Marker line_list;
        line_list.header.frame_id = kMapFrameId;
        line_list.id = 0;
        line_list.type = visualization_msgs::Marker::LINE_LIST;
        // LINE_STRIP/LINE_LIST markers use only the x component of scale, for
        // the line width
        line_list.scale.x = 0.1;
        // Line list is red
        line_list.color.b = 1.0;
        line_list.color.a = 1.0;
        // Create the vertices for the points and lines
        for (const auto& index_to_connected_item : graph) {
          if (index_to_connected_item.second.connections.size() <= 1) {
            continue;
          }
          const Eigen::Vector3d self_translation =
              index_to_connected_item.second.self_pose.block(0, 3, 3, 1);

          for (const auto& connection :
               index_to_connected_item.second.connections) {
            if (connection.first - index_to_connected_item.first == 1) {
              continue;
            }
            geometry_msgs::Point p_start;
            p_start.x = self_translation[0];
            p_start.y = self_translation[1];
            p_start.z = self_translation[2];

            line_list.points.push_back(p_start);

            geometry_msgs::Point p_end;
            const Eigen::Vector3d connected_translation =
                graph.at(connection.first).self_pose.block(0, 3, 3, 1);
            p_end.x = connected_translation[0];
            p_end.y = connected_translation[1];
            p_end.z = connected_translation[2];

            line_list.points.push_back(p_end);
          }
        }

        edge_markers_pub.publish(line_list);
      };

  map_builder = std::make_shared<MapBuilder>();

  // @todo(edward) merge these 2 situation into 1
  if (urdf_file.empty()) {
    tf::TransformListener listener;
    map_builder->SetTrackingToLidar(static_map_ros::LoopUpTransfrom(
        tracking_frame, cloud_frame_id, listener));
    if (use_imu) {
      map_builder->SetTrackingToImu(static_map_ros::LoopUpTransfrom(
          tracking_frame, imu_frame_id, listener));
    }
    if (use_odom) {
      map_builder->SetTransformOdomToLidar(static_map_ros::LoopUpTransfrom(
          odom_frame_id, cloud_frame_id, listener));
      map_builder->SetTrackingToOdom(static_map_ros::LoopUpTransfrom(
          tracking_frame, odom_frame_id, listener));
    }
    if (use_gps) {
      map_builder->SetTrackingToGps(static_map_ros::LoopUpTransfrom(
          tracking_frame, gps_frame_id, listener));
    }
  } else {
    tf2_ros::Buffer tf_buffer;
    static_map_ros::ReadStaticTransformsFromUrdf(urdf_file, &tf_buffer);
    map_builder->SetTrackingToLidar(static_map_ros::LoopUpTransfrom(
        tracking_frame, cloud_frame_id, tf_buffer));
    if (use_imu) {
      map_builder->SetTrackingToImu(static_map_ros::LoopUpTransfrom(
          tracking_frame, imu_frame_id, tf_buffer));
    }
    if (use_odom) {
      map_builder->SetTransformOdomToLidar(static_map_ros::LoopUpTransfrom(
          odom_frame_id, cloud_frame_id, tf_buffer));
      map_builder->SetTrackingToOdom(static_map_ros::LoopUpTransfrom(
          tracking_frame, odom_frame_id, tf_buffer));
    }
    if (use_gps) {
      map_builder->SetTrackingToGps(static_map_ros::LoopUpTransfrom(
          tracking_frame, gps_frame_id, tf_buffer));
    }
  }

  if (!config_file.empty()) {
    const auto options = map_builder->Initialise(config_file.c_str());
    if (options.front_end_options.imu_options.enabled && !use_imu) {
      PRINT_ERROR("You should set a imu topic if you enable using imu.");
      map_builder->FinishAllComputations();
      return 0;
    }
  } else {
    map_builder->Initialise(NULL);
  }
  map_builder->EnableUsingOdom(use_odom);
  map_builder->EnableUsingGps(use_gps);
  if (!pub_topics.empty()) {
    if (std::strstr(pub_topics.c_str(), "[map]")) {
      map_builder->SetShowMapFunction(show_function);
    }
    if (std::strstr(pub_topics.c_str(), "[submap]")) {
      map_builder->SetShowSubmapFunction(show_submap_function);
    }
    if (std::strstr(pub_topics.c_str(), "[path]")) {
      map_builder->SetShowPathFunction(publish_path);
    }
    if (std::strstr(pub_topics.c_str(), "[edge]")) {
      map_builder->SetShowEdgeFunction(publish_edges);
    }
  }

  REGISTER_FUNC;

  if (!bag_file.empty()) {
    static_map_ros::PlayableBag bag(bag_file, ros::TIME_MIN, ros::TIME_MAX,
                                    ::ros::Duration(1.0));

    const double duration_sec = bag.DurationInSeconds();
    const auto begin_time = std::get<0>(bag.GetBeginEndTime());

    int message_index = 0;
    ros::Time last_message_time = begin_time;
    while (bag.IsMessageAvailable() && ros::ok()) {
      auto message = bag.GetNextMessage();
      if (message.isType<sensor_msgs::PointCloud2>() &&
          message.getTopic() == point_cloud_topic) {
        pointcloud_callback(message.instantiate<sensor_msgs::PointCloud2>());
      } else if (message.isType<sensor_msgs::Imu>() &&
                 message.getTopic() == imu_topic) {
        if (use_imu) {
          imu_callback(*message.instantiate<sensor_msgs::Imu>());
        }
      } else if (message.isType<nav_msgs::Odometry>() &&
                 message.getTopic() == odom_topic) {
        if (use_odom) {
          odom_callback(*message.instantiate<nav_msgs::Odometry>());
        }
      } else if (message.isType<sensor_msgs::NavSatFix>() &&
                 message.getTopic() == gps_topic) {
        if (use_gps) {
          gps_callback(*message.instantiate<sensor_msgs::NavSatFix>());
        }
      }

      // read bag in twice time of the bag speed
      if (message.getTime() > last_message_time) {
        usleep((message.getTime() - last_message_time).toSec() * 0.1 * 1000000);
      }
      last_message_time = message.getTime();

      ++message_index;
      if (message_index % 10000 == 0) {
        PRINT_COLOR_FMT(BOLD, "processed %lf of %lf seconds of the bag... ",
                        (message.getTime() - begin_time).toSec(), duration_sec);
      }
    }
  } else if (!kitti_path.empty()) {
    PRINT_INFO("No bag, read kitti data.");
    static_map::KittiReader kitti_reader;
    kitti_reader.SetPointCloudDataPath(kitti_path);
    int index = 0;
    while (true && ros::ok()) {
      MapBuilder::PointCloudPtr point_cloud = kitti_reader.ReadFromBin(index);
      if (point_cloud && !point_cloud->empty()) {
        point_cloud->header.frame_id = cloud_frame_id;
        point_cloud->header.stamp =
            static_map::SimpleTime::GetCurrentTime().ToNanoSec() / 1000ull;
        map_builder->InsertPointcloudMsg(point_cloud);

        usleep(100000);
      } else {
        break;
      }
      index++;
    }
  } else {
    ::ros::spin();
  }

  map_builder->FinishAllComputations();
  return 0;
}


while (true && ros::ok()) {
      MapBuilder::PointCloudPtr point_cloud = kitti_reader.ReadFromBin(index);
      if (point_cloud && !point_cloud->empty()) {
        point_cloud->header.frame_id = cloud_frame_id;
        point_cloud->header.stamp =
            static_map::SimpleTime::GetCurrentTime().ToNanoSec() / 1000ull;
        map_builder->InsertPointcloudMsg(point_cloud);

        usleep(100000);
      } else {
        break;
      }
      index++;
    }

// third party
#include <nav_msgs/Path.h>
#include <pcl/console/parse.h>
#include <pcl/conversions.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl_conversions/pcl_conversions.h>
#include <ros/ros.h>
#include <sensor_msgs/PointCloud.h>
#include <sensor_msgs/PointCloud2.h>
#include <std_msgs/String.h>
#include <visualization_msgs/Marker.h>
// stl
#include <unistd.h>
#include <sstream>
// local
#include "builder/map_builder.h"
#include "common/file_utils.h"
#include "common/performance/simple_prof.h"
#include "common/simple_time.h"
#include "ros_node/kitti_reader.h"
#include "ros_node/msg_conversion.h"
#include "ros_node/playable_bag.h"
#include "ros_node/tf_bridge.h"
#include "tf/transform_broadcaster.h"

using static_map::MapBuilder;
static MapBuilder::Ptr map_builder;

using static_map::data::ImuMsg;
using static_map::data::NavSatFixMsg;
using static_map::data::OdomMsg;

namespace {

constexpr char kMapFrameId[] = "/map";

void pointcloud_callback(const sensor_msgs::PointCloud2::ConstPtr& msg) {
  pcl::PCLPointCloud2 pcl_pc2;
  pcl_conversions::toPCL(*msg, pcl_pc2);
  MapBuilder::PointCloudPtr incoming_cloud(new MapBuilder::PointCloudType);
  pcl::fromPCLPointCloud2(pcl_pc2, *incoming_cloud);

  std::vector<int> inliers;  // no use, just for the function
  pcl::removeNaNFromPointCloud(*incoming_cloud, *incoming_cloud, inliers);
  map_builder->InsertPointcloudMsg(incoming_cloud);
}

void imu_callback(const sensor_msgs::Imu& imu_msg) {
  ImuMsg::Ptr incomming_imu(new ImuMsg);
  *incomming_imu = static_map_ros::ToLocalImu(imu_msg);
  map_builder->InsertImuMsg(incomming_imu);
}

void odom_callback(const nav_msgs::Odometry& odom_msg) {
  OdomMsg::Ptr local_odom(new OdomMsg);
  *local_odom = static_map_ros::ToLocalOdom(odom_msg);
  map_builder->InsertOdomMsg(local_odom);
}

void gps_callback(const sensor_msgs::NavSatFix& gps_msg) {
  NavSatFixMsg::Ptr local_gps(new NavSatFixMsg);
  *local_gps = static_map_ros::ToLocalNavSatMsg(gps_msg);
  map_builder->InsertGpsMsg(local_gps);
}

}  // namespace

int main(int argc, char** argv) {
  ros::init(argc, argv, "static_mapping_node");
  ros::NodeHandle n;

  SIMPLE_PROF_USE_MS;
  SIMPLE_PROF_MERGE_OUTPUT;

  google::InitGoogleLogging(argv[0]);
  FLAGS_alsologtostderr = true;
  FLAGS_colorlogtostderr = true;
  google::InstallFailureSignalHandler();

  // parse auguements
  // point cloud
  std::string point_cloud_topic = "";
  pcl::console::parse_argument(argc, argv, "-pc", point_cloud_topic);
  if (point_cloud_topic.empty()) {
    PRINT_ERROR("point cloud topic is empty!");
    return -1;
  } else {
    PRINT_INFO_FMT("Get point cloud from ROS topic: %s",
                   point_cloud_topic.c_str());
  }
  ros::Subscriber sub_pointcloud =
      n.subscribe(point_cloud_topic, 10, pointcloud_callback);
  std::string cloud_frame_id = "base_link";
  pcl::console::parse_argument(argc, argv, "-pc_frame_id", cloud_frame_id);

  // imu
  std::string imu_topic = "";
  bool use_imu = false;
  ros::Subscriber sub_imu;
  std::string imu_frame_id = "/novatel_imu";
  pcl::console::parse_argument(argc, argv, "-imu", imu_topic);
  if (imu_topic.empty()) {
    PRINT_WARNING("No imu topic, expect no imu messages.");
  } else {
    PRINT_INFO_FMT("Get imu data from ROS topic: %s", imu_topic.c_str());
    sub_imu = n.subscribe(imu_topic, 100, imu_callback);
    pcl::console::parse_argument(argc, argv, "-imu_frame_id", imu_frame_id);
    use_imu = true;
  }

  // odom
  bool use_odom = false;
  std::string odom_topic = "";
  std::string odom_frame_id = "";
  pcl::console::parse_argument(argc, argv, "-odom", odom_topic);
  pcl::console::parse_argument(argc, argv, "-odom_frame_id", odom_frame_id);
  ros::Subscriber sub_odom;
  if (!odom_topic.empty() && !odom_frame_id.empty()) {
    sub_odom = n.subscribe(odom_topic, 100, odom_callback);
    use_odom = true;
    PRINT_INFO_FMT("Get odom data from ROS topic: %s", odom_topic.c_str());
  } else {
    PRINT_WARNING("No odom data accessed!");
  }

  // gps
  bool use_gps = false;
  std::string gps_topic = "";
  std::string gps_frame_id = "";
  pcl::console::parse_argument(argc, argv, "-gps", gps_topic);
  pcl::console::parse_argument(argc, argv, "-gps_frame_id", gps_frame_id);
  ros::Subscriber gps_subscriber;
  if (!gps_topic.empty() && !gps_frame_id.empty()) {
    gps_subscriber = n.subscribe(gps_topic, 100, gps_callback);
    use_gps = true;
    PRINT_INFO_FMT("Get gps data from ROS topic: %s", gps_topic.c_str());
  } else {
    PRINT_WARNING("No GPS data accessed!");
  }

  // config file
  std::string config_file = "";
  pcl::console::parse_argument(argc, argv, "-cfg", config_file);

  // urdf file
  std::string urdf_file = "";
  pcl::console::parse_argument(argc, argv, "-urdf", urdf_file);

  // tracking frame
  std::string tracking_frame = "base_link";
  pcl::console::parse_argument(argc, argv, "-track", tracking_frame);
  CHECK(!tracking_frame.empty())
      << "tracking frame id should be set." << std::endl;

  // ros bag
  std::string bag_file = "";
  pcl::console::parse_argument(argc, argv, "-bag", bag_file);
  if (!bag_file.empty() && !static_map::common::FileExist(bag_file)) {
    PRINT_WARNING_FMT("%s does not exist.", bag_file.c_str());
    bag_file = "";
  }

  // kitti data set
  std::string kitti_path = "";
  pcl::console::parse_argument(argc, argv, "-kitti", kitti_path);
  if (!kitti_path.empty() && !static_map::common::FileExist(kitti_path)) {
    PRINT_WARNING_FMT("%s does not exist.", kitti_path.c_str());
    kitti_path = "";
  }

  if (!kitti_path.empty() && !bag_file.empty()) {
    PRINT_ERROR("Should not read bag and kitti data set at the same time.");
    return -1;
  }

  std::string pub_topics = "";
  pcl::console::parse_argument(argc, argv, "-pubs", pub_topics);

  ros::Publisher map_publisher =
      n.advertise<sensor_msgs::PointCloud2>("/optimized_map", 1);
  ros::Publisher submap_publisher =
      n.advertise<sensor_msgs::PointCloud2>("/submap", 1);
  ros::Publisher pose_marker_pub =
      n.advertise<visualization_msgs::Marker>("/submap_pose", 1);
  ros::Publisher path_pub =
      n.advertise<nav_msgs::Path>("/static_mapping_path", 1);
  ros::Publisher edge_markers_pub =
      n.advertise<visualization_msgs::Marker>("/loop_edges", 1);
  tf::TransformBroadcaster tf_broadcaster;

  auto show_function =
      [&](const static_map::MapBuilder::PointCloudPtr& cloud) -> void {
    sensor_msgs::PointCloud2 map_pointcloud;
    pcl::toROSMsg(*cloud, map_pointcloud);
    map_pointcloud.header.frame_id = kMapFrameId;
    map_pointcloud.header.stamp = ros::Time::now();
    map_publisher.publish(map_pointcloud);
  };

  auto show_submap_function =
      [&](const static_map::MapBuilder::PointCloudPtr& cloud,
          const Eigen::Matrix4d& pose) -> void {
    tf::Transform transform;
    transform.setOrigin(tf::Vector3(pose(0, 3), pose(1, 3), pose(2, 3)));
    Eigen::Quaterniond rotation(Eigen::Matrix3d(pose.block(0, 0, 3, 3)));
    tf::Quaternion q;
    q.setW(rotation.w());
    q.setX(rotation.x());
    q.setY(rotation.y());
    q.setZ(rotation.z());
    transform.setRotation(q);
    tf_broadcaster.sendTransform(tf::StampedTransform(
        transform, ros::Time::now(), kMapFrameId, tracking_frame));

    sensor_msgs::PointCloud2 submap_pointcloud;
    pcl::toROSMsg(*cloud, submap_pointcloud);
    submap_pointcloud.header.frame_id = tracking_frame;
    submap_pointcloud.header.stamp = ros::Time::now();
    submap_publisher.publish(submap_pointcloud);
  };

  const auto publish_path = [&](const std::vector<Eigen::Matrix4d>& poses) {
    nav_msgs::Path path;
    path.header.frame_id = kMapFrameId;

    path.poses.reserve(poses.size());
    for (const Eigen::Matrix4d& pose : poses) {
      geometry_msgs::PoseStamped pose_ros;
      pose_ros.header.frame_id = kMapFrameId;
      Eigen::Quaterniond q(Eigen::Matrix3d(pose.block(0, 0, 3, 3)));
      pose_ros.pose.orientation.w = q.w();
      pose_ros.pose.orientation.x = q.x();
      pose_ros.pose.orientation.y = q.y();
      pose_ros.pose.orientation.z = q.z();
      pose_ros.pose.position.x = pose(0, 3);
      pose_ros.pose.position.y = pose(1, 3);
      pose_ros.pose.position.z = pose(2, 3);

      path.poses.push_back(pose_ros);
    }
    path_pub.publish(path);
  };

  const auto publish_edges =
      [&](const std::map<int64_t, static_map::back_end::ViewGraph::GraphItem>&
              graph) {
        visualization_msgs::Marker line_list;
        line_list.header.frame_id = kMapFrameId;
        line_list.id = 0;
        line_list.type = visualization_msgs::Marker::LINE_LIST;
        // LINE_STRIP/LINE_LIST markers use only the x component of scale, for
        // the line width
        line_list.scale.x = 0.1;
        // Line list is red
        line_list.color.b = 1.0;
        line_list.color.a = 1.0;
        // Create the vertices for the points and lines
        for (const auto& index_to_connected_item : graph) {
          if (index_to_connected_item.second.connections.size() <= 1) {
            continue;
          }
          const Eigen::Vector3d self_translation =
              index_to_connected_item.second.self_pose.block(0, 3, 3, 1);

          for (const auto& connection :
               index_to_connected_item.second.connections) {
            if (connection.first - index_to_connected_item.first == 1) {
              continue;
            }
            geometry_msgs::Point p_start;
            p_start.x = self_translation[0];
            p_start.y = self_translation[1];
            p_start.z = self_translation[2];

            line_list.points.push_back(p_start);

            geometry_msgs::Point p_end;
            const Eigen::Vector3d connected_translation =
                graph.at(connection.first).self_pose.block(0, 3, 3, 1);
            p_end.x = connected_translation[0];
            p_end.y = connected_translation[1];
            p_end.z = connected_translation[2];

            line_list.points.push_back(p_end);
          }
        }

        edge_markers_pub.publish(line_list);
      };

  map_builder = std::make_shared<MapBuilder>();

  // @todo(edward) merge these 2 situation into 1
  if (urdf_file.empty()) {
    tf::TransformListener listener;
    map_builder->SetTrackingToLidar(static_map_ros::LoopUpTransfrom(
        tracking_frame, cloud_frame_id, listener));
    if (use_imu) {
      map_builder->SetTrackingToImu(static_map_ros::LoopUpTransfrom(
          tracking_frame, imu_frame_id, listener));
    }
    if (use_odom) {
      map_builder->SetTransformOdomToLidar(static_map_ros::LoopUpTransfrom(
          odom_frame_id, cloud_frame_id, listener));
      map_builder->SetTrackingToOdom(static_map_ros::LoopUpTransfrom(
          tracking_frame, odom_frame_id, listener));
    }
    if (use_gps) {
      map_builder->SetTrackingToGps(static_map_ros::LoopUpTransfrom(
          tracking_frame, gps_frame_id, listener));
    }
  } else {
    tf2_ros::Buffer tf_buffer;
    static_map_ros::ReadStaticTransformsFromUrdf(urdf_file, &tf_buffer);
    map_builder->SetTrackingToLidar(static_map_ros::LoopUpTransfrom(
        tracking_frame, cloud_frame_id, tf_buffer));
    if (use_imu) {
      map_builder->SetTrackingToImu(static_map_ros::LoopUpTransfrom(
          tracking_frame, imu_frame_id, tf_buffer));
    }
    if (use_odom) {
      map_builder->SetTransformOdomToLidar(static_map_ros::LoopUpTransfrom(
          odom_frame_id, cloud_frame_id, tf_buffer));
      map_builder->SetTrackingToOdom(static_map_ros::LoopUpTransfrom(
          tracking_frame, odom_frame_id, tf_buffer));
    }
    if (use_gps) {
      map_builder->SetTrackingToGps(static_map_ros::LoopUpTransfrom(
          tracking_frame, gps_frame_id, tf_buffer));
    }
  }

  if (!config_file.empty()) {
    const auto options = map_builder->Initialise(config_file.c_str());
    if (options.front_end_options.imu_options.enabled && !use_imu) {
      PRINT_ERROR("You should set a imu topic if you enable using imu.");
      map_builder->FinishAllComputations();
      return 0;
    }
  } else {
    map_builder->Initialise(NULL);
  }
  map_builder->EnableUsingOdom(use_odom);
  map_builder->EnableUsingGps(use_gps);
  if (!pub_topics.empty()) {
    if (std::strstr(pub_topics.c_str(), "[map]")) {
      map_builder->SetShowMapFunction(show_function);
    }
    if (std::strstr(pub_topics.c_str(), "[submap]")) {
      map_builder->SetShowSubmapFunction(show_submap_function);
    }
    if (std::strstr(pub_topics.c_str(), "[path]")) {
      map_builder->SetShowPathFunction(publish_path);
    }
    if (std::strstr(pub_topics.c_str(), "[edge]")) {
      map_builder->SetShowEdgeFunction(publish_edges);
    }
  }

  REGISTER_FUNC;

  if (!bag_file.empty()) {
    static_map_ros::PlayableBag bag(bag_file, ros::TIME_MIN, ros::TIME_MAX,
                                    ::ros::Duration(1.0));

    const double duration_sec = bag.DurationInSeconds();
    const auto begin_time = std::get<0>(bag.GetBeginEndTime());

    int message_index = 0;
    ros::Time last_message_time = begin_time;
    while (bag.IsMessageAvailable() && ros::ok()) {
      auto message = bag.GetNextMessage();
      if (message.isType<sensor_msgs::PointCloud2>() &&
          message.getTopic() == point_cloud_topic) {
        pointcloud_callback(message.instantiate<sensor_msgs::PointCloud2>());
      } else if (message.isType<sensor_msgs::Imu>() &&
                 message.getTopic() == imu_topic) {
        if (use_imu) {
          imu_callback(*message.instantiate<sensor_msgs::Imu>());
        }
      } else if (message.isType<nav_msgs::Odometry>() &&
                 message.getTopic() == odom_topic) {
        if (use_odom) {
          odom_callback(*message.instantiate<nav_msgs::Odometry>());
        }
      } else if (message.isType<sensor_msgs::NavSatFix>() &&
                 message.getTopic() == gps_topic) {
        if (use_gps) {
          gps_callback(*message.instantiate<sensor_msgs::NavSatFix>());
        }
      }

      // read bag in twice time of the bag speed
      if (message.getTime() > last_message_time) {
        usleep((message.getTime() - last_message_time).toSec() * 0.1 * 1000000);
      }
      last_message_time = message.getTime();

      ++message_index;
      if (message_index % 10000 == 0) {
        PRINT_COLOR_FMT(BOLD, "processed %lf of %lf seconds of the bag... ",
                        (message.getTime() - begin_time).toSec(), duration_sec);
      }
    }
  } else if (!kitti_path.empty()) {
    PRINT_INFO("No bag, read kitti data.");
    static_map::KittiReader kitti_reader;
    kitti_reader.SetPointCloudDataPath(kitti_path);
    int index = 0;

  } else {
    ::ros::spin();
  }

  map_builder->FinishAllComputations();
  return 0;
}


inline const std::error_code &make_error_code(const failure_info &fi)

/* Example of how to marshall Outcomes at namespace boundaries

*/

#include "../../../include/outcome.hpp"
#if __has_include("quickcpplib/string_view.hpp")
#include "quickcpplib/string_view.hpp"
#else
#include "../../../include/outcome/quickcpplib/include/quickcpplib/string_view.hpp"
#endif
#include <cstring>  // for memcpy

#if __has_include(<filesystem>) && (__cplusplus >= 201700 || _HAS_CXX17)
#include <filesystem>
namespace filesystem = std::filesystem;
#else
#include <experimental/filesystem>
namespace filesystem = std::experimental::filesystem;
#endif

// This is some standalone library implementing high level HTTP
namespace httplib
{
  // These are the error code that this HTTP library can return
  enum class status_code
  {
    success = 0,  // not the HTTP success code of 200

    // A subset of all HTTP status codes for brevity
    bad_request = 400,
    access_denied = 401,
    logon_failed = 402,
    forbidden = 403,
    not_found = 404,
    internal_error = 500
  };
  // This is the error type that this HTTP library can return
  struct failure
  {
    status_code status{status_code::success};
    std::string url{};  // The failing URL
  };
  // Localise a result implementation to this library, holding
  // the logic error of incorrect observation to mean program
  template <class T>
  using result =  //
  OUTCOME_V2_NAMESPACE::result<T, failure, OUTCOME_V2_NAMESPACE::policy::terminate>;

  /* Performs a HTTP GET on the url, returning the body if successful,
  a failure with status_code if unsuccessful at the HTTP level, or a
  C++ exception throw if something catastrophic happened e.g. bad_alloc
  */
  result<std::string> get(std::string url);
}  // namespace httplib

namespace httplib
{
  result<std::string> get(std::string url)
  {
    (void) url;
#if 1
    return "hello world";
#else
    return failure{status_code::not_found, url};
#endif
  }
}  // namespace httplib

namespace filelib
{
  using QUICKCPPLIB_NAMESPACE::string_view::string_view;
  using filesystem::filesystem_error;
  using filesystem::path;
}  // namespace filelib

namespace app
{
  using QUICKCPPLIB_NAMESPACE::string_view::string_view;
}

// You may remember this from the tutorial section on Custom Payloads
namespace filelib
{
  // Error code + paths related to a failure. Also causes ADL discovery
  struct failure_info
  {
    std::error_code ec;
    path path1{}, path2{};
  };

  // Tell Outcome that failure_info is to be treated as a std::error_code

{ return fi.ec; }

  // Tell Outcome that no-value observation should throw a custom exception
  inline void outcome_throw_as_system_error_with_payload(failure_info fi)
  {
    // If the error code is not filesystem related e.g. ENOMEM, throw that
    // as a standard STL exception.
    OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(fi.ec);
    // Throw the exact same filesystem_error exception which the throwing
    throw filesystem_error(fi.ec.message(), std::move(fi.path1), std::move(fi.path2), fi.ec);
  }

  // Localise a result implementation specific to this namespace.
  template <class T> using result = OUTCOME_V2_NAMESPACE::result<T, failure_info>;

  // Writes a chunk of data to some file. Returns bytes written, or
  // failure_info. Never throws exceptions.
  result<size_t> write_file(string_view chunk) noexcept;
}  // namespace filelib

namespace filelib
{
  result<size_t> write_file(string_view chunk) noexcept
  {
    (void) chunk;
    return failure_info{make_error_code(std::errc::no_space_on_device), "somepath"};
  }
}  // namespace filelib

// There actually is a library for tidying HTML into XHTML called HTMLTidy
// HTMLTidy is actually a great tool for dealing with 1990s-era tag soup
// HTML, I highly recommend it.

// This isn't the API for Tidy, but let's assume it's a C library returning
// errno domained error codes. out must be freed with free() after use.
extern "C" int tidy_html(char **out, size_t *outlen, const char *in, size_t inlen);

extern "C" int tidy_html(char **out, size_t *outlen, const char *in, size_t inlen)
{
#if 1
  *out = (char *) malloc(inlen + 1);
  memcpy(*out, in, inlen + 1);
  *outlen = inlen;
  return 0;
#else
  return EROFS;
#endif
}

// This is the namespace of the application which is connecting together the httplib,
// filelib and tidylib libraries into a solution.
namespace app
{
  // Create an ADL bridge so copy/move hooks will be searched for in this namespace
  struct error_code : public std::error_code
  {
    // passthrough
    using std::error_code::error_code;
    error_code() = default;
    error_code(std::error_code ec)
        : std::error_code(ec)
    {
    }
  };
  // Localise an outcome implementation for this namespace
  template <class T>
  using outcome =  //
  OUTCOME_V2_NAMESPACE::outcome<T, error_code /*, std::exception_ptr */>;
  using OUTCOME_V2_NAMESPACE::success;
}  // namespace app

namespace app
{
  // Specialise an exception type for httplib errors
  struct httplib_error : std::runtime_error
  {
    // passthrough
    using std::runtime_error::runtime_error;
    httplib_error(httplib::failure _failure, std::string msg)
        : std::runtime_error(std::move(msg))
        , failure(std::move(_failure))
    {
    }

    // the original failure
    httplib::failure failure;
  };

  // Type erase httplib::result<U> into a httplib_error exception ptr
  template <class U>  //
  inline std::exception_ptr make_httplib_exception(const httplib::result<U> &src)
  {
    std::string str("httplib failed with error ");
    switch(src.error().status)
    {
    case httplib::status_code::success:
      str.append("success");
      break;
    case httplib::status_code::bad_request:
      str.append("bad request");
      break;
    case httplib::status_code::access_denied:
      str.append("access denied");
      break;
    case httplib::status_code::logon_failed:
      str.append("logon failed");
      break;
    case httplib::status_code::forbidden:
      str.append("forbidden");
      break;
    case httplib::status_code::not_found:
      str.append("not found");
      break;
    case httplib::status_code::internal_error:
      str.append("internal error");
      break;
    }
    str.append(" [url was ");
    str.append(src.error().url);
    str.append("]");
    return std::make_exception_ptr(httplib_error(src.error(), std::move(str)));
  }
}  // namespace app

// Inject custom ValueOrError conversion
OUTCOME_V2_NAMESPACE_BEGIN
namespace convert
{
  // Provide custom ValueOrError conversion from
  // httplib::result<U> into any app::outcome<T>
  template <class T, class U>  //
  struct value_or_error<app::outcome<T>, httplib::result<U>>
  {
    // False to indicate that this converter wants `result`/`outcome`
    // to NOT reject all other `result`
    static constexpr bool enable_result_inputs = true;
    // False to indicate that this converter wants `outcome` to NOT
    // reject all other `outcome`
    static constexpr bool enable_outcome_inputs = true;

    template <class X,                                                                              //
              typename = std::enable_if_t<std::is_same<httplib::result<U>, std::decay_t<X>>::value  //
                                          && std::is_constructible<T, U>::value>>                   //
    constexpr app::outcome<T> operator()(X &&src)
    {
      // Forward any successful value, else synthesise an exception ptr
      return src.has_value() ?                              //
             app::outcome<T>{std::forward<X>(src).value()}  //
             :
             app::outcome<T>{app::make_httplib_exception(std::forward<X>(src))};
    }
  };
}  // namespace convert
OUTCOME_V2_NAMESPACE_END

namespace app
{
  static outcome<int> test_value_or_error2 = OUTCOME_V2_NAMESPACE::convert::value_or_error<outcome<int>, httplib::result<int>>{}(httplib::result<int>{5});
  static outcome<int> test_value_or_error3(httplib::result<int>{5});
}  // namespace app

// Inject custom ValueOrError conversion
OUTCOME_V2_NAMESPACE_BEGIN
namespace convert
{
  // Provide custom ValueOrError conversion from filelib::result<U>
  // into any app::outcome<T>
  template <class T, class U>  //
  struct value_or_error<app::outcome<T>, filelib::result<U>>
  {
    // True to indicate that this converter wants `result`/`outcome`
    // to NOT reject all other `result`
    static constexpr bool enable_result_inputs = true;
    // False to indicate that this converter wants `outcome` to NOT
    // reject all other `outcome`
    static constexpr bool enable_outcome_inputs = true;

    template <class X,                                                                              //
              typename = std::enable_if_t<std::is_same<filelib::result<U>, std::decay_t<X>>::value  //
                                          && std::is_constructible<T, U>::value>>                   //
    constexpr app::outcome<T> operator()(X &&src)
    {
      // Forward any successful value
      if(src.has_value())
      {
        return {std::forward<X>(src).value()};
      }

      // Synthesise a filesystem_error, exactly as if someone had
      auto &fi = src.error();
      OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(fi.ec);  // might throw
      return {std::make_exception_ptr(                                  //
      filelib::filesystem_error(fi.ec.message(), std::move(fi.path1), std::move(fi.path2), fi.ec))};
    }
  };
}  // namespace convert
OUTCOME_V2_NAMESPACE_END

namespace app
{
  // Specialise an exception type for tidylib errors
  struct tidylib_error : std::system_error
  {
    // passthrough
    using std::system_error::system_error;
    tidylib_error() = default;
    explicit tidylib_error(int c)
        : std::system_error(c, std::generic_category())
    {
    }
  };

  // Create a C++ invoking wrapper for the tidylib C API, modifying data with the returned data,
  // returing a unique_ptr to release storage on scope exit.
  struct call_free
  {
    template <class T> void operator()(T *p) { ::free(p); }
  };
  inline outcome<std::unique_ptr<char, call_free>> tidy_html(string_view &data)
  {
    char *out = nullptr;
    size_t outlen = 0;
    int errcode = ::tidy_html(&out, &outlen, data.data(), data.size());
    if(errcode != 0)
    {
      // If the error code matches a standard STL exception, throw as that.
      OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(std::error_code(errcode, std::generic_category()));
      // Otherwise wrap the error code into a tidylib_error exception throw
      return std::make_exception_ptr(tidylib_error(errcode));
    }
    // Reset input view to tidied html
    data = string_view(out, outlen);
    // Return a unique ptr to release storage on scope exit
    return std::unique_ptr<char, call_free>(out);
  }
}  // namespace app

namespace app
{
  // A markup function to indicate when we are ValueOrError converting
  template <class T> inline outcome<typename T::value_type> ext(T &&v)
  {  //
    return outcome<typename T::value_type>(std::move(v));
  }

  outcome<void> go()  // NOT noexcept, this can throw STL exceptions e.g. bad_alloc
  {
    // Note that explicit construction is required when converting between differing types
    // of outcome and result. This makes it explicit what you intend to do as conversion
    // may be a lot more expensive than moves.

    // Try to GET this URL. If an unsuccessful HTTP status is returned, serialise a string
    // containing a description of the HTTP status code and the URL which failed, storing
    // that into a httplib_error exception type which is stored as an exception ptr. The
    // TRY operation below will return that exception ptr to be rethrown in the caller.
    // Otherwise the fetched data is returned in a std::string data.
    OUTCOME_TRY(auto data, ext(httplib::get("http://www.nedproductions.biz/")));
    string_view data_view(data);

    // HTML tidy the fetched data. If the C library fails due to an error corresponding to
    // a standard library exception type, throw that. Otherwise, synthesise an exception
    // ptr of type tidylib_error which stores the error code returned in an error code with
    // TRY operation below will return that exception ptr to be rethrown in the caller.
    // Otherwise the tidied data is returned into holdmem, with the string view updated to
    // point at the tidied data.
    OUTCOME_TRY(auto holdmem, ext(tidy_html(data_view)));

    // Write the tidied data to some file. If the write fails, synthesise a filesystem_error
    // exception ptr exactly as if one called filelib::write_file(data_view).value().
    OUTCOME_TRY(auto written, ext(filelib::write_file(data_view)));
    return success();
  }
}  // namespace app

int main()
{
  try
  {
    app::go().value();
  }
  catch(const filelib::filesystem_error &e)
  {
    std::cerr << "Exception thrown, " << e.what()                               //
              << " (path1 = " << e.path1() << ", path2 = " << e.path2() << ")"  //
              << std::endl;
    return 1;
  }
  catch(const std::exception &e)
  {
    std::cerr << "Exception thrown, " << e.what() << std::endl;
    return 1;
  }
  return 0;
}


{
#if 1
  *out = (char *) malloc(inlen + 1);
  memcpy(*out, in, inlen + 1);
  *outlen = inlen;
  return 0;
#else
  return EROFS;
#endif
}

/* Example of how to marshall Outcomes at namespace boundaries

*/

#include "../../../include/outcome.hpp"
#if __has_include("quickcpplib/string_view.hpp")
#include "quickcpplib/string_view.hpp"
#else
#include "../../../include/outcome/quickcpplib/include/quickcpplib/string_view.hpp"
#endif
#include <cstring>  // for memcpy

#if __has_include(<filesystem>) && (__cplusplus >= 201700 || _HAS_CXX17)
#include <filesystem>
namespace filesystem = std::filesystem;
#else
#include <experimental/filesystem>
namespace filesystem = std::experimental::filesystem;
#endif

// This is some standalone library implementing high level HTTP
namespace httplib
{
  // These are the error code that this HTTP library can return
  enum class status_code
  {
    success = 0,  // not the HTTP success code of 200

    // A subset of all HTTP status codes for brevity
    bad_request = 400,
    access_denied = 401,
    logon_failed = 402,
    forbidden = 403,
    not_found = 404,
    internal_error = 500
  };
  // This is the error type that this HTTP library can return
  struct failure
  {
    status_code status{status_code::success};
    std::string url{};  // The failing URL
  };
  // Localise a result implementation to this library, holding
  // the logic error of incorrect observation to mean program
  template <class T>
  using result =  //
  OUTCOME_V2_NAMESPACE::result<T, failure, OUTCOME_V2_NAMESPACE::policy::terminate>;

  /* Performs a HTTP GET on the url, returning the body if successful,
  a failure with status_code if unsuccessful at the HTTP level, or a
  C++ exception throw if something catastrophic happened e.g. bad_alloc
  */
  result<std::string> get(std::string url);
}  // namespace httplib

namespace httplib
{
  result<std::string> get(std::string url)
  {
    (void) url;
#if 1
    return "hello world";
#else
    return failure{status_code::not_found, url};
#endif
  }
}  // namespace httplib

namespace filelib
{
  using QUICKCPPLIB_NAMESPACE::string_view::string_view;
  using filesystem::filesystem_error;
  using filesystem::path;
}  // namespace filelib

namespace app
{
  using QUICKCPPLIB_NAMESPACE::string_view::string_view;
}

// You may remember this from the tutorial section on Custom Payloads
namespace filelib
{
  // Error code + paths related to a failure. Also causes ADL discovery
  struct failure_info
  {
    std::error_code ec;
    path path1{}, path2{};
  };

  // Tell Outcome that failure_info is to be treated as a std::error_code
  inline const std::error_code &make_error_code(const failure_info &fi) { return fi.ec; }

  // Tell Outcome that no-value observation should throw a custom exception
  inline void outcome_throw_as_system_error_with_payload(failure_info fi)
  {
    // If the error code is not filesystem related e.g. ENOMEM, throw that
    // as a standard STL exception.
    OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(fi.ec);
    // Throw the exact same filesystem_error exception which the throwing
    throw filesystem_error(fi.ec.message(), std::move(fi.path1), std::move(fi.path2), fi.ec);
  }

  // Localise a result implementation specific to this namespace.
  template <class T> using result = OUTCOME_V2_NAMESPACE::result<T, failure_info>;

  // Writes a chunk of data to some file. Returns bytes written, or
  // failure_info. Never throws exceptions.
  result<size_t> write_file(string_view chunk) noexcept;
}  // namespace filelib

namespace filelib
{
  result<size_t> write_file(string_view chunk) noexcept
  {
    (void) chunk;
    return failure_info{make_error_code(std::errc::no_space_on_device), "somepath"};
  }
}  // namespace filelib

// There actually is a library for tidying HTML into XHTML called HTMLTidy
// HTMLTidy is actually a great tool for dealing with 1990s-era tag soup
// HTML, I highly recommend it.

// This isn't the API for Tidy, but let's assume it's a C library returning
// errno domained error codes. out must be freed with free() after use.
extern "C" int tidy_html(char **out, size_t *outlen, const char *in, size_t inlen);

extern "C" int tidy_html(char **out, size_t *outlen, const char *in, size_t inlen)

// This is the namespace of the application which is connecting together the httplib,
// filelib and tidylib libraries into a solution.
namespace app
{
  // Create an ADL bridge so copy/move hooks will be searched for in this namespace
  struct error_code : public std::error_code
  {
    // passthrough
    using std::error_code::error_code;
    error_code() = default;
    error_code(std::error_code ec)
        : std::error_code(ec)
    {
    }
  };
  // Localise an outcome implementation for this namespace
  template <class T>
  using outcome =  //
  OUTCOME_V2_NAMESPACE::outcome<T, error_code /*, std::exception_ptr */>;
  using OUTCOME_V2_NAMESPACE::success;
}  // namespace app

namespace app
{
  // Specialise an exception type for httplib errors
  struct httplib_error : std::runtime_error
  {
    // passthrough
    using std::runtime_error::runtime_error;
    httplib_error(httplib::failure _failure, std::string msg)
        : std::runtime_error(std::move(msg))
        , failure(std::move(_failure))
    {
    }

    // the original failure
    httplib::failure failure;
  };

  // Type erase httplib::result<U> into a httplib_error exception ptr
  template <class U>  //
  inline std::exception_ptr make_httplib_exception(const httplib::result<U> &src)
  {
    std::string str("httplib failed with error ");
    switch(src.error().status)
    {
    case httplib::status_code::success:
      str.append("success");
      break;
    case httplib::status_code::bad_request:
      str.append("bad request");
      break;
    case httplib::status_code::access_denied:
      str.append("access denied");
      break;
    case httplib::status_code::logon_failed:
      str.append("logon failed");
      break;
    case httplib::status_code::forbidden:
      str.append("forbidden");
      break;
    case httplib::status_code::not_found:
      str.append("not found");
      break;
    case httplib::status_code::internal_error:
      str.append("internal error");
      break;
    }
    str.append(" [url was ");
    str.append(src.error().url);
    str.append("]");
    return std::make_exception_ptr(httplib_error(src.error(), std::move(str)));
  }
}  // namespace app

// Inject custom ValueOrError conversion
OUTCOME_V2_NAMESPACE_BEGIN
namespace convert
{
  // Provide custom ValueOrError conversion from
  // httplib::result<U> into any app::outcome<T>
  template <class T, class U>  //
  struct value_or_error<app::outcome<T>, httplib::result<U>>
  {
    // False to indicate that this converter wants `result`/`outcome`
    // to NOT reject all other `result`
    static constexpr bool enable_result_inputs = true;
    // False to indicate that this converter wants `outcome` to NOT
    // reject all other `outcome`
    static constexpr bool enable_outcome_inputs = true;

    template <class X,                                                                              //
              typename = std::enable_if_t<std::is_same<httplib::result<U>, std::decay_t<X>>::value  //
                                          && std::is_constructible<T, U>::value>>                   //
    constexpr app::outcome<T> operator()(X &&src)
    {
      // Forward any successful value, else synthesise an exception ptr
      return src.has_value() ?                              //
             app::outcome<T>{std::forward<X>(src).value()}  //
             :
             app::outcome<T>{app::make_httplib_exception(std::forward<X>(src))};
    }
  };
}  // namespace convert
OUTCOME_V2_NAMESPACE_END

namespace app
{
  static outcome<int> test_value_or_error2 = OUTCOME_V2_NAMESPACE::convert::value_or_error<outcome<int>, httplib::result<int>>{}(httplib::result<int>{5});
  static outcome<int> test_value_or_error3(httplib::result<int>{5});
}  // namespace app

// Inject custom ValueOrError conversion
OUTCOME_V2_NAMESPACE_BEGIN
namespace convert
{
  // Provide custom ValueOrError conversion from filelib::result<U>
  // into any app::outcome<T>
  template <class T, class U>  //
  struct value_or_error<app::outcome<T>, filelib::result<U>>
  {
    // True to indicate that this converter wants `result`/`outcome`
    // to NOT reject all other `result`
    static constexpr bool enable_result_inputs = true;
    // False to indicate that this converter wants `outcome` to NOT
    // reject all other `outcome`
    static constexpr bool enable_outcome_inputs = true;

    template <class X,                                                                              //
              typename = std::enable_if_t<std::is_same<filelib::result<U>, std::decay_t<X>>::value  //
                                          && std::is_constructible<T, U>::value>>                   //
    constexpr app::outcome<T> operator()(X &&src)
    {
      // Forward any successful value
      if(src.has_value())
      {
        return {std::forward<X>(src).value()};
      }

      // Synthesise a filesystem_error, exactly as if someone had
      auto &fi = src.error();
      OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(fi.ec);  // might throw
      return {std::make_exception_ptr(                                  //
      filelib::filesystem_error(fi.ec.message(), std::move(fi.path1), std::move(fi.path2), fi.ec))};
    }
  };
}  // namespace convert
OUTCOME_V2_NAMESPACE_END

namespace app
{
  // Specialise an exception type for tidylib errors
  struct tidylib_error : std::system_error
  {
    // passthrough
    using std::system_error::system_error;
    tidylib_error() = default;
    explicit tidylib_error(int c)
        : std::system_error(c, std::generic_category())
    {
    }
  };

  // Create a C++ invoking wrapper for the tidylib C API, modifying data with the returned data,
  // returing a unique_ptr to release storage on scope exit.
  struct call_free
  {
    template <class T> void operator()(T *p) { ::free(p); }
  };
  inline outcome<std::unique_ptr<char, call_free>> tidy_html(string_view &data)
  {
    char *out = nullptr;
    size_t outlen = 0;
    int errcode = ::tidy_html(&out, &outlen, data.data(), data.size());
    if(errcode != 0)
    {
      // If the error code matches a standard STL exception, throw as that.
      OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(std::error_code(errcode, std::generic_category()));
      // Otherwise wrap the error code into a tidylib_error exception throw
      return std::make_exception_ptr(tidylib_error(errcode));
    }
    // Reset input view to tidied html
    data = string_view(out, outlen);
    // Return a unique ptr to release storage on scope exit
    return std::unique_ptr<char, call_free>(out);
  }
}  // namespace app

namespace app
{
  // A markup function to indicate when we are ValueOrError converting
  template <class T> inline outcome<typename T::value_type> ext(T &&v)
  {  //
    return outcome<typename T::value_type>(std::move(v));
  }

  outcome<void> go()  // NOT noexcept, this can throw STL exceptions e.g. bad_alloc
  {
    // Note that explicit construction is required when converting between differing types
    // of outcome and result. This makes it explicit what you intend to do as conversion
    // may be a lot more expensive than moves.

    // Try to GET this URL. If an unsuccessful HTTP status is returned, serialise a string
    // containing a description of the HTTP status code and the URL which failed, storing
    // that into a httplib_error exception type which is stored as an exception ptr. The
    // TRY operation below will return that exception ptr to be rethrown in the caller.
    // Otherwise the fetched data is returned in a std::string data.
    OUTCOME_TRY(auto data, ext(httplib::get("http://www.nedproductions.biz/")));
    string_view data_view(data);

    // HTML tidy the fetched data. If the C library fails due to an error corresponding to
    // a standard library exception type, throw that. Otherwise, synthesise an exception
    // ptr of type tidylib_error which stores the error code returned in an error code with
    // TRY operation below will return that exception ptr to be rethrown in the caller.
    // Otherwise the tidied data is returned into holdmem, with the string view updated to
    // point at the tidied data.
    OUTCOME_TRY(auto holdmem, ext(tidy_html(data_view)));

    // Write the tidied data to some file. If the write fails, synthesise a filesystem_error
    // exception ptr exactly as if one called filelib::write_file(data_view).value().
    OUTCOME_TRY(auto written, ext(filelib::write_file(data_view)));
    return success();
  }
}  // namespace app

int main()
{
  try
  {
    app::go().value();
  }
  catch(const filelib::filesystem_error &e)
  {
    std::cerr << "Exception thrown, " << e.what()                               //
              << " (path1 = " << e.path1() << ", path2 = " << e.path2() << ")"  //
              << std::endl;
    return 1;
  }
  catch(const std::exception &e)
  {
    std::cerr << "Exception thrown, " << e.what() << std::endl;
    return 1;
  }
  return 0;
}


/* Example of how to marshall Outcomes at namespace boundaries

*/

#include "../../../include/outcome.hpp"
#if __has_include("quickcpplib/string_view.hpp")
#include "quickcpplib/string_view.hpp"
#else
#include "../../../include/outcome/quickcpplib/include/quickcpplib/string_view.hpp"
#endif
#include <cstring>  // for memcpy

#if __has_include(<filesystem>) && (__cplusplus >= 201700 || _HAS_CXX17)
#include <filesystem>
namespace filesystem = std::filesystem;
#else
#include <experimental/filesystem>
namespace filesystem = std::experimental::filesystem;
#endif

// This is some standalone library implementing high level HTTP
namespace httplib
{
  // These are the error code that this HTTP library can return
  enum class status_code
  {
    success = 0,  // not the HTTP success code of 200

    // A subset of all HTTP status codes for brevity
    bad_request = 400,
    access_denied = 401,
    logon_failed = 402,
    forbidden = 403,
    not_found = 404,
    internal_error = 500
  };
  // This is the error type that this HTTP library can return
  struct failure
  {
    status_code status{status_code::success};
    std::string url{};  // The failing URL
  };
  // Localise a result implementation to this library, holding
  // the logic error of incorrect observation to mean program
  template <class T>
  using result =  //
  OUTCOME_V2_NAMESPACE::result<T, failure, OUTCOME_V2_NAMESPACE::policy::terminate>;

  /* Performs a HTTP GET on the url, returning the body if successful,
  a failure with status_code if unsuccessful at the HTTP level, or a
  C++ exception throw if something catastrophic happened e.g. bad_alloc
  */
  result<std::string> get(std::string url);
}  // namespace httplib

namespace httplib
{
  result<std::string> get(std::string url)
  {
    (void) url;
#if 1
    return "hello world";
#else
    return failure{status_code::not_found, url};
#endif
  }
}  // namespace httplib

namespace filelib
{
  using QUICKCPPLIB_NAMESPACE::string_view::string_view;
  using filesystem::filesystem_error;
  using filesystem::path;
}  // namespace filelib

namespace app
{
  using QUICKCPPLIB_NAMESPACE::string_view::string_view;
}

// You may remember this from the tutorial section on Custom Payloads
namespace filelib
{
  // Error code + paths related to a failure. Also causes ADL discovery
  struct failure_info
  {
    std::error_code ec;
    path path1{}, path2{};
  };

  // Tell Outcome that failure_info is to be treated as a std::error_code
  inline const std::error_code &make_error_code(const failure_info &fi) { return fi.ec; }

  // Tell Outcome that no-value observation should throw a custom exception
  inline void outcome_throw_as_system_error_with_payload(failure_info fi)
  {
    // If the error code is not filesystem related e.g. ENOMEM, throw that
    // as a standard STL exception.
    OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(fi.ec);
    // Throw the exact same filesystem_error exception which the throwing
    throw filesystem_error(fi.ec.message(), std::move(fi.path1), std::move(fi.path2), fi.ec);
  }

  // Localise a result implementation specific to this namespace.
  template <class T> using result = OUTCOME_V2_NAMESPACE::result<T, failure_info>;

  // Writes a chunk of data to some file. Returns bytes written, or
  // failure_info. Never throws exceptions.
  result<size_t> write_file(string_view chunk) noexcept;
}  // namespace filelib

namespace filelib
{
  result<size_t> write_file(string_view chunk) noexcept
  {
    (void) chunk;
    return failure_info{make_error_code(std::errc::no_space_on_device), "somepath"};
  }
}  // namespace filelib

// There actually is a library for tidying HTML into XHTML called HTMLTidy
// HTMLTidy is actually a great tool for dealing with 1990s-era tag soup
// HTML, I highly recommend it.

// This isn't the API for Tidy, but let's assume it's a C library returning
// errno domained error codes. out must be freed with free() after use.
extern "C" int tidy_html(char **out, size_t *outlen, const char *in, size_t inlen);

extern "C" int tidy_html(char **out, size_t *outlen, const char *in, size_t inlen)
{
#if 1
  *out = (char *) malloc(inlen + 1);
  memcpy(*out, in, inlen + 1);
  *outlen = inlen;
  return 0;
#else
  return EROFS;
#endif
}

// This is the namespace of the application which is connecting together the httplib,
// filelib and tidylib libraries into a solution.
namespace app
{
  // Create an ADL bridge so copy/move hooks will be searched for in this namespace
  struct error_code : public std::error_code
  {
    // passthrough
    using std::error_code::error_code;
    error_code() = default;
    error_code(std::error_code ec)
        : std::error_code(ec)
    {
    }
  };
  // Localise an outcome implementation for this namespace
  template <class T>
  using outcome =  //
  OUTCOME_V2_NAMESPACE::outcome<T, error_code /*, std::exception_ptr */>;
  using OUTCOME_V2_NAMESPACE::success;
}  // namespace app

namespace app
{
  // Specialise an exception type for httplib errors
  struct httplib_error : std::runtime_error
  {
    // passthrough
    using std::runtime_error::runtime_error;
    httplib_error(httplib::failure _failure, std::string msg)
        : std::runtime_error(std::move(msg))
        , failure(std::move(_failure))
    {
    }

    // the original failure
    httplib::failure failure;
  };

  // Type erase httplib::result<U> into a httplib_error exception ptr
  template <class U>  //
  inline std::exception_ptr make_httplib_exception(const httplib::result<U> &src)
  {
    std::string str("httplib failed with error ");
    switch(src.error().status)
    {
    case httplib::status_code::success:
      str.append("success");
      break;
    case httplib::status_code::bad_request:
      str.append("bad request");
      break;
    case httplib::status_code::access_denied:
      str.append("access denied");
      break;
    case httplib::status_code::logon_failed:
      str.append("logon failed");
      break;
    case httplib::status_code::forbidden:
      str.append("forbidden");
      break;
    case httplib::status_code::not_found:
      str.append("not found");
      break;
    case httplib::status_code::internal_error:
      str.append("internal error");
      break;
    }
    str.append(" [url was ");
    str.append(src.error().url);
    str.append("]");
    return std::make_exception_ptr(httplib_error(src.error(), std::move(str)));
  }
}  // namespace app

// Inject custom ValueOrError conversion
OUTCOME_V2_NAMESPACE_BEGIN
namespace convert
{
  // Provide custom ValueOrError conversion from
  // httplib::result<U> into any app::outcome<T>
  template <class T, class U>  //
  struct value_or_error<app::outcome<T>, httplib::result<U>>
  {
    // False to indicate that this converter wants `result`/`outcome`
    // to NOT reject all other `result`
    static constexpr bool enable_result_inputs = true;
    // False to indicate that this converter wants `outcome` to NOT
    // reject all other `outcome`
    static constexpr bool enable_outcome_inputs = true;

    template <class X,                                                                              //
              typename = std::enable_if_t<std::is_same<httplib::result<U>, std::decay_t<X>>::value  //
                                          && std::is_constructible<T, U>::value>>                   //
    constexpr app::outcome<T> operator()(X &&src)
    {
      // Forward any successful value, else synthesise an exception ptr
      return src.has_value() ?                              //
             app::outcome<T>{std::forward<X>(src).value()}  //
             :
             app::outcome<T>{app::make_httplib_exception(std::forward<X>(src))};
    }
  };
}  // namespace convert
OUTCOME_V2_NAMESPACE_END

namespace app
{
  static outcome<int> test_value_or_error2 = OUTCOME_V2_NAMESPACE::convert::value_or_error<outcome<int>, httplib::result<int>>{}(httplib::result<int>{5});
  static outcome<int> test_value_or_error3(httplib::result<int>{5});
}  // namespace app

// Inject custom ValueOrError conversion
OUTCOME_V2_NAMESPACE_BEGIN
namespace convert
{
  // Provide custom ValueOrError conversion from filelib::result<U>
  // into any app::outcome<T>
  template <class T, class U>  //
  struct value_or_error<app::outcome<T>, filelib::result<U>>
  {
    // True to indicate that this converter wants `result`/`outcome`
    // to NOT reject all other `result`
    static constexpr bool enable_result_inputs = true;
    // False to indicate that this converter wants `outcome` to NOT
    // reject all other `outcome`
    static constexpr bool enable_outcome_inputs = true;

    template <class X,                                                                              //
              typename = std::enable_if_t<std::is_same<filelib::result<U>, std::decay_t<X>>::value  //
                                          && std::is_constructible<T, U>::value>>                   //
    constexpr app::outcome<T> operator()(X &&src)
    {
      // Forward any successful value
      if(src.has_value())
      {
        return {std::forward<X>(src).value()};
      }

      // Synthesise a filesystem_error, exactly as if someone had
      auto &fi = src.e

      OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(fi.ec);  // might throw
      return {std::make_exception_ptr(                                  //
      filelib::filesystem_error(fi.ec.message(), std::move(fi.path1), std::move(fi.path2), fi.ec))};
    }
  };
}  // namespace convert
OUTCOME_V2_NAMESPACE_END

namespace app
{
  // Specialise an exception type for tidylib errors
  struct tidylib_error : std::system_error
  {
    // passthrough
    using std::system_error::system_error;
    tidylib_error() = default;
    explicit tidylib_error(int c)
        : std::system_error(c, std::generic_category())
    {
    }
  };

  // Create a C++ invoking wrapper for the tidylib C API, modifying data with the returned data,
  // returing a unique_ptr to release storage on scope exit.
  struct call_free
  {
    template <class T> void operator()(T *p) { ::free(p); }
  };
  inline outcome<std::unique_ptr<char, call_free>> tidy_html(string_view &data)
  {
    char *out = nullptr;
    size_t outlen = 0;
    int errcode = ::tidy_html(&out, &outlen, data.data(), data.size());
    if(errcode != 0)
    {
      // If the error code matches a standard STL exception, throw as that.
      OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(std::error_code(errcode, std::generic_category()));
      // Otherwise wrap the error code into a tidylib_error exception throw
      return std::make_exception_ptr(tidylib_error(errcode));
    }
    // Reset input view to tidied html
    data = string_view(out, outlen);
    // Return a unique ptr to release storage on scope exit
    return std::unique_ptr<char, call_free>(out);
  }
}  // namespace app

namespace app
{
  // A markup function to indicate when we are ValueOrError converting
  template <class T> inline outcome<typename T::value_type> ext(T &&v)
  {  //
    return outcome<typename T::value_type>(std::move(v));
  }

  outcome<void> go()  // NOT noexcept, this can throw STL exceptions e.g. bad_alloc
  {
    // Note that explicit construction is required when converting between differing types
    // of outcome and result. This makes it explicit what you intend to do as conversion
    // may be a lot more expensive than moves.

    // Try to GET this URL. If an unsuccessful HTTP status is returned, serialise a string
    // containing a description of the HTTP status code and the URL which failed, storing
    // that into a httplib_error exception type which is stored as an exception ptr. The
    // TRY operation below will return that exception ptr to be rethrown in the caller.
    // Otherwise the fetched data is returned in a std::string data.
    OUTCOME_TRY(auto data, ext(httplib::get("http://www.nedproductions.biz/")));
    string_view data_view(data);

    // HTML tidy the fetched data. If the C library fails due to an error corresponding to
    // a standard library exception type, throw that. Otherwise, synthesise an exception
    // ptr of type tidylib_error which stores the error code returned in an error code with
    // TRY operation below will return that exception ptr to be rethrown in the caller.
    // Otherwise the tidied data is returned into holdmem, with the string view updated to
    // point at the tidied data.
    OUTCOME_TRY(auto holdmem, ext(tidy_html(data_view)));

    // Write the tidied data to some file. If the write fails, synthesise a filesystem_error
    // exception ptr exactly as if one called filelib::write_file(data_view).value().
    OUTCOME_TRY(auto written, ext(filelib::write_file(data_view)));
    return success();
  }
}  // namespace app

int main()
{
  try
  {
    app::go().value();
  }
  catch(const filelib::filesystem_error &e)
  {
    std::cerr << "Exception thrown, " << e.what()                               //
              << " (path1 = " << e.path1() << ", path2 = " << e.path2() << ")"  //
              << std::endl;
    return 1;
  }
  catch(const std::exception &e)
  {
    std::cerr << "Exception thrown, " << e.what() << std::endl;
    return 1;
  }
  return 0;
}


OUTCOME_V2_NAMESPACE_END

namespace app
{
  static outcome<int> test_value_or_error2 = OUTCOME_V2_NAMESPACE::convert::value_or_error<outcome<int>, httplib::result<int>>{}(httplib::result<int>{5});
  static outcome<int> test_value_or_error3(httplib::result<int>{5});
}

/* Example of how to marshall Outcomes at namespace boundaries

*/

#include "../../../include/outcome.hpp"
#if __has_include("quickcpplib/string_view.hpp")
#include "quickcpplib/string_view.hpp"
#else
#include "../../../include/outcome/quickcpplib/include/quickcpplib/string_view.hpp"
#endif
#include <cstring>  // for memcpy

#if __has_include(<filesystem>) && (__cplusplus >= 201700 || _HAS_CXX17)
#include <filesystem>
namespace filesystem = std::filesystem;
#else
#include <experimental/filesystem>
namespace filesystem = std::experimental::filesystem;
#endif

// This is some standalone library implementing high level HTTP
namespace httplib
{
  // These are the error code that this HTTP library can return
  enum class status_code
  {
    success = 0,  // not the HTTP success code of 200

    // A subset of all HTTP status codes for brevity
    bad_request = 400,
    access_denied = 401,
    logon_failed = 402,
    forbidden = 403,
    not_found = 404,
    internal_error = 500
  };
  // This is the error type that this HTTP library can return
  struct failure
  {
    status_code status{status_code::success};
    std::string url{};  // The failing URL
  };
  // Localise a result implementation to this library, holding
  // the logic error of incorrect observation to mean program
  template <class T>
  using result =  //
  OUTCOME_V2_NAMESPACE::result<T, failure, OUTCOME_V2_NAMESPACE::policy::terminate>;

  /* Performs a HTTP GET on the url, returning the body if successful,
  a failure with status_code if unsuccessful at the HTTP level, or a
  C++ exception throw if something catastrophic happened e.g. bad_alloc
  */
  result<std::string> get(std::string url);
}  // namespace httplib

namespace httplib
{
  result<std::string> get(std::string url)
  {
    (void) url;
#if 1
    return "hello world";
#else
    return failure{status_code::not_found, url};
#endif
  }
}  // namespace httplib

namespace filelib
{
  using QUICKCPPLIB_NAMESPACE::string_view::string_view;
  using filesystem::filesystem_error;
  using filesystem::path;
}  // namespace filelib

namespace app
{
  using QUICKCPPLIB_NAMESPACE::string_view::string_view;
}

// You may remember this from the tutorial section on Custom Payloads
namespace filelib
{
  // Error code + paths related to a failure. Also causes ADL discovery
  struct failure_info
  {
    std::error_code ec;
    path path1{}, path2{};
  };

  // Tell Outcome that failure_info is to be treated as a std::error_code
  inline const std::error_code &make_error_code(const failure_info &fi) { return fi.ec; }

  // Tell Outcome that no-value observation should throw a custom exception
  inline void outcome_throw_as_system_error_with_payload(failure_info fi)
  {
    // If the error code is not filesystem related e.g. ENOMEM, throw that
    // as a standard STL exception.
    OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(fi.ec);
    // Throw the exact same filesystem_error exception which the throwing
    throw filesystem_error(fi.ec.message(), std::move(fi.path1), std::move(fi.path2), fi.ec);
  }

  // Localise a result implementation specific to this namespace.
  template <class T> using result = OUTCOME_V2_NAMESPACE::result<T, failure_info>;

  // Writes a chunk of data to some file. Returns bytes written, or
  // failure_info. Never throws exceptions.
  result<size_t> write_file(string_view chunk) noexcept;
}  // namespace filelib

namespace filelib
{
  result<size_t> write_file(string_view chunk) noexcept
  {
    (void) chunk;
    return failure_info{make_error_code(std::errc::no_space_on_device), "somepath"};
  }
}  // namespace filelib

// There actually is a library for tidying HTML into XHTML called HTMLTidy
// HTMLTidy is actually a great tool for dealing with 1990s-era tag soup
// HTML, I highly recommend it.

// This isn't the API for Tidy, but let's assume it's a C library returning
// errno domained error codes. out must be freed with free() after use.
extern "C" int tidy_html(char **out, size_t *outlen, const char *in, size_t inlen);

extern "C" int tidy_html(char **out, size_t *outlen, const char *in, size_t inlen)
{
#if 1
  *out = (char *) malloc(inlen + 1);
  memcpy(*out, in, inlen + 1);
  *outlen = inlen;
  return 0;
#else
  return EROFS;
#endif
}

// This is the namespace of the application which is connecting together the httplib,
// filelib and tidylib libraries into a solution.
namespace app
{
  // Create an ADL bridge so copy/move hooks will be searched for in this namespace
  struct error_code : public std::error_code
  {
    // passthrough
    using std::error_code::error_code;
    error_code() = default;
    error_code(std::error_code ec)
        : std::error_code(ec)
    {
    }
  };
  // Localise an outcome implementation for this namespace
  template <class T>
  using outcome =  //
  OUTCOME_V2_NAMESPACE::outcome<T, error_code /*, std::exception_ptr */>;
  using OUTCOME_V2_NAMESPACE::success;
}  // namespace app

namespace app
{
  // Specialise an exception type for httplib errors
  struct httplib_error : std::runtime_error
  {
    // passthrough
    using std::runtime_error::runtime_error;
    httplib_error(httplib::failure _failure, std::string msg)
        : std::runtime_error(std::move(msg))
        , failure(std::move(_failure))
    {
    }

    // the original failure
    httplib::failure failure;
  };

  // Type erase httplib::result<U> into a httplib_error exception ptr
  template <class U>  //
  inline std::exception_ptr make_httplib_exception(const httplib::result<U> &src)
  {
    std::string str("httplib failed with error ");
    switch(src.error().status)
    {
    case httplib::status_code::success:
      str.append("success");
      break;
    case httplib::status_code::bad_request:
      str.append("bad request");
      break;
    case httplib::status_code::access_denied:
      str.append("access denied");
      break;
    case httplib::status_code::logon_failed:
      str.append("logon failed");
      break;
    case httplib::status_code::forbidden:
      str.append("forbidden");
      break;
    case httplib::status_code::not_found:
      str.append("not found");
      break;
    case httplib::status_code::internal_error:
      str.append("internal error");
      break;
    }
    str.append(" [url was ");
    str.append(src.error().url);
    str.append("]");
    return std::make_exception_ptr(httplib_error(src.error(), std::move(str)));
  }
}  // namespace app

// Inject custom ValueOrError conversion
OUTCOME_V2_NAMESPACE_BEGIN
namespace convert
{
  // Provide custom ValueOrError conversion from
  // httplib::result<U> into any app::outcome<T>
  template <class T, class U>  //
  struct value_or_error<app::outcome<T>, httplib::result<U>>
  {
    // False to indicate that this converter wants `result`/`outcome`
    // to NOT reject all other `result`
    static constexpr bool enable_result_inputs = true;
    // False to indicate that this converter wants `outcome` to NOT
    // reject all other `outcome`
    static constexpr bool enable_outcome_inputs = true;

    template <class X,                                                                              //
              typename = std::enable_if_t<std::is_same<httplib::result<U>, std::decay_t<X>>::value  //
                                          && std::is_constructible<T, U>::value>>                   //
    constexpr app::outcome<T> operator()(X &&src)
    {
      // Forward any successful value, else synthesise an exception ptr
      return src.has_value() ?                              //
             app::outcome<T>{std::forward<X>(src).value()}  //
             :
             app::outcome<T>{app::make_httplib_exception(std::forward<X>(src))};
    }
  };
}  // namespace convert

  // namespace app

// Inject custom ValueOrError conversion
OUTCOME_V2_NAMESPACE_BEGIN
namespace convert
{
  // Provide custom ValueOrError conversion from filelib::result<U>
  // into any app::outcome<T>
  template <class T, class U>  //
  struct value_or_error<app::outcome<T>, filelib::result<U>>
  {
    // True to indicate that this converter wants `result`/`outcome`
    // to NOT reject all other `result`
    static constexpr bool enable_result_inputs = true;
    // False to indicate that this converter wants `outcome` to NOT
    // reject all other `outcome`
    static constexpr bool enable_outcome_inputs = true;

    template <class X,                                                                              //
              typename = std::enable_if_t<std::is_same<filelib::result<U>, std::decay_t<X>>::value  //
                                          && std::is_constructible<T, U>::value>>                   //
    constexpr app::outcome<T> operator()(X &&src)
    {
      // Forward any successful value
      if(src.has_value())
      {
        return {std::forward<X>(src).value()};
      }

      // Synthesise a filesystem_error, exactly as if someone had
      auto &fi = src.error();
      OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(fi.ec);  // might throw
      return {std::make_exception_ptr(                                  //
      filelib::filesystem_error(fi.ec.message(), std::move(fi.path1), std::move(fi.path2), fi.ec))};
    }
  };
}  // namespace convert
OUTCOME_V2_NAMESPACE_END

namespace app
{
  // Specialise an exception type for tidylib errors
  struct tidylib_error : std::system_error
  {
    // passthrough
    using std::system_error::system_error;
    tidylib_error() = default;
    explicit tidylib_error(int c)
        : std::system_error(c, std::generic_category())
    {
    }
  };

  // Create a C++ invoking wrapper for the tidylib C API, modifying data with the returned data,
  // returing a unique_ptr to release storage on scope exit.
  struct call_free
  {
    template <class T> void operator()(T *p) { ::free(p); }
  };
  inline outcome<std::unique_ptr<char, call_free>> tidy_html(string_view &data)
  {
    char *out = nullptr;
    size_t outlen = 0;
    int errcode = ::tidy_html(&out, &outlen, data.data(), data.size());
    if(errcode != 0)
    {
      // If the error code matches a standard STL exception, throw as that.
      OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(std::error_code(errcode, std::generic_category()));
      // Otherwise wrap the error code into a tidylib_error exception throw
      return std::make_exception_ptr(tidylib_error(errcode));
    }
    // Reset input view to tidied html
    data = string_view(out, outlen);
    // Return a unique ptr to release storage on scope exit
    return std::unique_ptr<char, call_free>(out);
  }
}  // namespace app

namespace app
{
  // A markup function to indicate when we are ValueOrError converting
  template <class T> inline outcome<typename T::value_type> ext(T &&v)
  {  //
    return outcome<typename T::value_type>(std::move(v));
  }

  outcome<void> go()  // NOT noexcept, this can throw STL exceptions e.g. bad_alloc
  {
    // Note that explicit construction is required when converting between differing types
    // of outcome and result. This makes it explicit what you intend to do as conversion
    // may be a lot more expensive than moves.

    // Try to GET this URL. If an unsuccessful HTTP status is returned, serialise a string
    // containing a description of the HTTP status code and the URL which failed, storing
    // that into a httplib_error exception type which is stored as an exception ptr. The
    // TRY operation below will return that exception ptr to be rethrown in the caller.
    // Otherwise the fetched data is returned in a std::string data.
    OUTCOME_TRY(auto data, ext(httplib::get("http://www.nedproductions.biz/")));
    string_view data_view(data);

    // HTML tidy the fetched data. If the C library fails due to an error corresponding to
    // a standard library exception type, throw that. Otherwise, synthesise an exception
    // ptr of type tidylib_error which stores the error code returned in an error code with
    // TRY operation below will return that exception ptr to be rethrown in the caller.
    // Otherwise the tidied data is returned into holdmem, with the string view updated to
    // point at the tidied data.
    OUTCOME_TRY(auto holdmem, ext(tidy_html(data_view)));

    // Write the tidied data to some file. If the write fails, synthesise a filesystem_error
    // exception ptr exactly as if one called filelib::write_file(data_view).value().
    OUTCOME_TRY(auto written, ext(filelib::write_file(data_view)));
    return success();
  }
}  // namespace app

int main()
{
  try
  {
    app::go().value();
  }
  catch(const filelib::filesystem_error &e)
  {
    std::cerr << "Exception thrown, " << e.what()                               //
              << " (path1 = " << e.path1() << ", path2 = " << e.path2() << ")"  //
              << std::endl;
    return 1;
  }
  catch(const std::exception &e)
  {
    std::cerr << "Exception thrown, " << e.what() << std::endl;
    return 1;
  }
  return 0;
}


//  sp_nullptr_test.cpp

#include <boost/shared_ptr.hpp>
#include <boost/smart_ptr/detail/sp_nullptr_t.hpp>
#include <boost/detail/lightweight_test.hpp>
#include <cstddef>
#include <memory>

#if !defined( BOOST_NO_CXX11_NULLPTR )

struct X
{
    static int instances;

    X()
    {
        ++instances;
    }

    ~X()
    {
        --instances;
    }

private:

    X( X const & );
    X & operator=( X const & );
};

int X::instances = 0;

void f( boost::detail::sp_nullptr_t )
{
}

int main()
{
    {
        boost::shared_ptr<void> p( nullptr );

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 0 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    {
        boost::shared_ptr<int> p( nullptr, f );

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 1 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    {
        boost::shared_ptr<int> p( nullptr, f, std::allocator<int>() );

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 1 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    {
        boost::shared_ptr<int> p( new int );

        BOOST_TEST( p.get() != 0 );
        BOOST_TEST( p.use_count() == 1 );

        BOOST_TEST( p != nullptr );
        BOOST_TEST( nullptr != p );
        BOOST_TEST( !( p == nullptr ) );
        BOOST_TEST( !( nullptr == p ) );

        p = nullptr;

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 0 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    {
        BOOST_TEST( X::instances == 0 );

        boost::shared_ptr<X> p( new X );
        BOOST_TEST( X::instances == 1 );

        BOOST_TEST( p.get() != 0 );
        BOOST_TEST( p.use_count() == 1 );

        BOOST_TEST( p != nullptr );
        BOOST_TEST( nullptr != p );
        BOOST_TEST( !( p == nullptr ) );
        BOOST_TEST( !( nullptr == p ) );

        p = nullptr;
        BOOST_TEST( X::instances == 0 );

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 0 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    return boost::report_errors();
}

#else


//  sp_nullptr_test.cpp

#include <boost/shared_ptr.hpp>
#include <boost/smart_ptr/detail/sp_nullptr_t.hpp>
#include <boost/detail/lightweight_test.hpp>
#include <cstddef>
#include <memory>

#if !defined( BOOST_NO_CXX11_NULLPTR )

struct X
{
    static int instances;

    X()
    {
        ++instances;
    }

    ~X()
    {
        --instances;
    }

private:

    X( X const & );
    X & operator=( X const & );
};

int X::instances = 0;

void f( boost::detail::sp_nullptr_t )

int main()
{
    {
        boost::shared_ptr<void> p( nullptr );

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 0 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    {
        boost::shared_ptr<int> p( nullptr, f );

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 1 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    {
        boost::shared_ptr<int> p( nullptr, f, std::allocator<int>() );

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 1 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    {
        boost::shared_ptr<int> p( new int );

        BOOST_TEST( p.get() != 0 );
        BOOST_TEST( p.use_count() == 1 );

        BOOST_TEST( p != nullptr );
        BOOST_TEST( nullptr != p );
        BOOST_TEST( !( p == nullptr ) );
        BOOST_TEST( !( nullptr == p ) );

        p = nullptr;

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 0 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    {
        BOOST_TEST( X::instances == 0 );

        boost::shared_ptr<X> p( new X );
        BOOST_TEST( X::instances == 1 );

        BOOST_TEST( p.get() != 0 );
        BOOST_TEST( p.use_count() == 1 );

        BOOST_TEST( p != nullptr );
        BOOST_TEST( nullptr != p );
        BOOST_TEST( !( p == nullptr ) );
        BOOST_TEST( !( nullptr == p ) );

        p = nullptr;
        BOOST_TEST( X::instances == 0 );

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 0 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    return boost::report_errors();
}

#else

int main()
{
    return 0;
}

#endif


//  sp_nullptr_test.cpp

#include <boost/shared_ptr.hpp>
#include <boost/smart_ptr/detail/sp_nullptr_t.hpp>
#include <boost/detail/lightweight_test.hpp>
#include <cstddef>
#include <memory>

#if !defined( BOOST_NO_CXX11_NULLPTR )

struct X
{
    static int instances;

    X()
    {
        ++i

    }

    ~X()
    {
        --instances;
    }

private:

    X( X const & );
    X & operator=( X const & );
};

int X::instances = 0;

void f( boost::detail::sp_nullptr_t )
{
}

int main()
{
    {
        boost::shared_ptr<void> p( nullptr );

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 0 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    {
        boost::shared_ptr<int> p( nullptr, f );

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 1 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    {
        boost::shared_ptr<int> p( nullptr, f, std::allocator<int>() );

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 1 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    {
        boost::shared_ptr<int> p( new int );

        BOOST_TEST( p.get() != 0 );
        BOOST_TEST( p.use_count() == 1 );

        BOOST_TEST( p != nullptr );
        BOOST_TEST( nullptr != p );
        BOOST_TEST( !( p == nullptr ) );
        BOOST_TEST( !( nullptr == p ) );

        p = nullptr;

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 0 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    {
        BOOST_TEST( X::instances == 0 );

        boost::shared_ptr<X> p( new X );
        BOOST_TEST( X::instances == 1 );

        BOOST_TEST( p.get() != 0 );
        BOOST_TEST( p.use_count() == 1 );

        BOOST_TEST( p != nullptr );
        BOOST_TEST( nullptr != p );
        BOOST_TEST( !( p == nullptr ) );
        BOOST_TEST( !( nullptr == p ) );

        p = nullptr;
        BOOST_TEST( X::instances == 0 );

        BOOST_TEST( p.get() == 0 );
        BOOST_TEST( p.use_count() == 0 );

        BOOST_TEST( p == nullptr );
        BOOST_TEST( nullptr == p );
        BOOST_TEST( !( p != nullptr ) );
        BOOST_TEST( !( nullptr != p ) );
    }

    return boost::report_errors();
}

#else

int main()
{
    return 0;
}

#endif


void XMLHelper::fillElementFromVector3(TiXmlElement& elem, const Ogre::Vector3& vector)

// C++ Implementation: XMLHelper
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "XMLHelper.h"
#include <OgreVector.h>

namespace Ember {
namespace OgreView {

bool XMLHelper::Load(TiXmlDocument& xmlDoc, Ogre::DataStreamPtr stream) {
	size_t length(stream->size());

	if (length) {
		// If we have a file, assume it is all one big XML file, and read it in.
		// The document parser may decide the document ends sooner than the entire file, however.
		std::string data(stream->getAsString());

		xmlDoc.Parse(data.c_str());

		if (xmlDoc.Error()) {
			std::string errorDesc = xmlDoc.ErrorDesc();
			int errorLine = xmlDoc.ErrorRow();
			int errorColumn = xmlDoc.ErrorCol();
			std::stringstream ss;
			ss << "Failed to load xml file '" << stream->getName() << "'! Error at column: " << errorColumn << " line: " << errorLine << ". Error message: " << errorDesc;
			S_LOG_FAILURE(ss.str());
			return false;
		} else {
			return true;
		}
	}
	return false;
}

bool XMLHelper::Load(TiXmlDocument& xmlDoc, std::istream& stream, const boost::filesystem::path& path) {
	stream >> xmlDoc;

	if (xmlDoc.Error()) {
		std::string errorDesc = xmlDoc.ErrorDesc();
		int errorLine = xmlDoc.ErrorRow();
		int errorColumn = xmlDoc.ErrorCol();
		std::stringstream ss;
		ss << "Failed to load xml file '" << path << "'! Error at column: " << errorColumn << " line: " << errorLine << ". Error message: " << errorDesc;
		S_LOG_FAILURE(ss.str());
		return false;
	} else {
		return true;
	}
}

Ogre::Vector3 XMLHelper::fillVector3FromElement(TiXmlElement* elem) {
	Ogre::Real x = 0.0f, y = 0.0f, z = 0.0f;
	if (elem->Attribute("x")) {
		x = std::stof(elem->Attribute("x"));
	}
	if (elem->Attribute("y")) {
		y = std::stof(elem->Attribute("y"));
	}
	if (elem->Attribute("z")) {
		z = std::stof(elem->Attribute("z"));
	}

	return {x, y, z};
}

{
	elem.SetDoubleAttribute("x", vector.x);
	elem.SetDoubleAttribute("y", vector.y);
	elem.SetDoubleAttribute("z", vector.z);
}

Ogre::Quaternion XMLHelper::fillQuaternionFromElement(TiXmlElement* elem) {
	Ogre::Vector3 vector = fillVector3FromElement(elem);
	vector.normalise();
	Ogre::Degree degrees;
	//first check if degrees is specified, but also allow for radians to be specified
	if (elem->Attribute("degrees")) {
		degrees = std::stof(elem->Attribute("degrees"));
	} else if (elem->Attribute("radians")) {
		degrees = Ogre::Radian(std::stof(elem->Attribute("radians")));
	}
	Ogre::Quaternion q(degrees, vector);

	if (q.isNaN()) {
		S_LOG_WARNING("Reading rotation resulted in invalid quaternion.");
	}
	return q;

}

void XMLHelper::fillElementFromQuaternion(TiXmlElement& elem, const Ogre::Quaternion& quaternion) {
	//split the quaternion into an axis and a degree (our format allows us to store the angle element as a radian too, but I prefer degrees)
	Ogre::Degree degrees;
	Ogre::Vector3 axis;
	quaternion.ToAngleAxis(degrees, axis);
	fillElementFromVector3(elem, axis);
	elem.SetDoubleAttribute("degrees", degrees.valueDegrees());
}

}
}


{
	elem.SetDoubleAttribute("x", vector.x);
	elem.SetDoubleAttribute("y", vector.y);
	elem.SetDoubleAttribute("z", vector.z);
}

// C++ Implementation: XMLHelper
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "XMLHelper.h"
#include <OgreVector.h>

namespace Ember {
namespace OgreView {

bool XMLHelper::Load(TiXmlDocument& xmlDoc, Ogre::DataStreamPtr stream) {
	size_t length(stream->size());

	if (length) {
		// If we have a file, assume it is all one big XML file, and read it in.
		// The document parser may decide the document ends sooner than the entire file, however.
		std::string data(stream->getAsString());

		xmlDoc.Parse(data.c_str());

		if (xmlDoc.Error()) {
			std::string errorDesc = xmlDoc.ErrorDesc();
			int errorLine = xmlDoc.ErrorRow();
			int errorColumn = xmlDoc.ErrorCol();
			std::stringstream ss;
			ss << "Failed to load xml file '" << stream->getName() << "'! Error at column: " << errorColumn << " line: " << errorLine << ". Error message: " << errorDesc;
			S_LOG_FAILURE(ss.str());
			return false;
		} else {
			return true;
		}
	}
	return false;
}

bool XMLHelper::Load(TiXmlDocument& xmlDoc, std::istream& stream, const boost::filesystem::path& path) {
	stream >> xmlDoc;

	if (xmlDoc.Error()) {
		std::string errorDesc = xmlDoc.ErrorDesc();
		int errorLine = xmlDoc.ErrorRow();
		int errorColumn = xmlDoc.ErrorCol();
		std::stringstream ss;
		ss << "Failed to load xml file '" << path << "'! Error at column: " << errorColumn << " line: " << errorLine << ". Error message: " << errorDesc;
		S_LOG_FAILURE(ss.str());
		return false;
	} else {
		return true;
	}
}

Ogre::Vector3 XMLHelper::fillVector3FromElement(TiXmlElement* elem) {
	Ogre::Real x = 0.0f, y = 0.0f, z = 0.0f;
	if (elem->Attribute("x")) {
		x = std::stof(elem->Attribute("x"));
	}
	if (elem->Attribute("y")) {
		y = std::stof(elem->Attribute("y"));
	}
	if (elem->Attribute("z")) {
		z = std::stof(elem->Attribute("z"));
	}

	return {x, y, z};
}

void XMLHelper::fillElementFromVector3(TiXmlElement& elem, const Ogre::Vector3& vector)

Ogre::Quaternion XMLHelper::fillQuaternionFromElement(TiXmlElement* elem) {
	Ogre::Vector3 vector = fillVector3FromElement(elem);
	vector.normalise();
	Ogre::Degree degrees;
	//first check if degrees is specified, but also allow for radians to be specified
	if (elem->Attribute("degrees")) {
		degrees = std::stof(elem->Attribute("degrees"));
	} else if (elem->Attribute("radians")) {
		degrees = Ogre::Radian(std::stof(elem->Attribute("radians")));
	}
	Ogre::Quaternion q(degrees, vector);

	if (q.isNaN()) {
		S_LOG_WARNING("Reading rotation resulted in invalid quaternion.");
	}
	return q;

}

void XMLHelper::fillElementFromQuaternion(TiXmlElement& elem, const Ogre::Quaternion& quaternion) {
	//split the quaternion into an axis and a degree (our format allows us to store the angle element as a radian too, but I prefer degrees)
	Ogre::Degree degrees;
	Ogre::Vector3 axis;
	quaternion.ToAngleAxis(degrees, axis);
	fillElementFromVector3(elem, axis);
	elem.SetDoubleAttribute("degrees", degrees.valueDegrees());
}

}
}