#!/usr/bin/env python3
# ============================================================================
# gc_slam_mapping.launch.py
# 功能：Gazebo 仿真环境 + SLAM 建图启动文件
#
# 适用场景：
#   - 在 Gazebo 仿真中遥控机器人建立环境地图
#   - 在已有地图基础上增量更新
#   - 单独调试 slam_toolbox 建图参数
#
# 架构：
#   Gazebo 仿真器          — 加载 zhihui.world（智慧楼墙体环境）
#   spawn_entity           — 在 Gazebo 中生成机器人模型
#   robot_state_publisher  — 发布机器人 TF 树（URDF 模型）
#   joint_state_publisher  — 发布关节状态
#   async_slam_toolbox_node — 激光 SLAM 建图（mapping 模式）
#                             发布 map→odom TF
#                             发布 /map 话题（实时地图）
#
# 使用方式：
#   ros2 launch gc_navigation2_slamtoolbox gc_slam_mapping.launch.py
#
# 保存地图：
#   ros2 service call /slam_toolbox/serialize_map slam_toolbox/srv/SerializePoseGraph \
#     "{filename: '/home/guoch/test_ws/src/gc_navigation2_slamtoolbox/maps/my_map'}"
#   ros2 run nav2_map_server map_saver_cli -f <map_name>
# ============================================================================

import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, ExecuteProcess, SetEnvironmentVariable
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command


def generate_launch_description():
    """生成 LaunchDescription，启动 Gazebo + slam_toolbox 建图"""

    # ============================ 1. 包路径 =========================================
    pkg_dir = get_package_share_directory('gc_navigation2_slamtoolbox')
    origincar_urdf_dir = get_package_share_directory('origincar_description')

    # ============================ 2. 声明启动参数 ====================================
    use_sim_time = LaunchConfiguration('use_sim_time', default='true')

    # slam_params_file: slam_toolbox 配置文件
    slam_params_file = LaunchConfiguration(
        'slam_params_file',
        default=os.path.join(pkg_dir, 'config', 'slam_toolbox_async.yaml'))

    # map_file_name: 可选，加载已有序列化地图继续建图
    map_file_name = LaunchConfiguration('map_file_name', default='')

    # model: 机器人 URDF 模型路径（用于 xacro 处理）
    # origincar.urdf 是纯 URDF（不含 xacro 宏），xacro 会原样透传
    default_model_path = os.path.join(
        origincar_urdf_dir, 'urdf', 'origincar.urdf')
    model = DeclareLaunchArgument(
        name='model', default_value=default_model_path)

    # spawn_model: Gazebo 中生成机器人用的 URDF（不含 xacro 宏）
    spawn_model_path = os.path.join(
        origincar_urdf_dir, 'urdf', 'origincar.urdf')

    # ============================ 3. 设置 GAZEBO_MODEL_PATH ==========================
    # 让 Gazebo 能找到 zhihui 模型，同时保留标准模型路径
    gazebo_model_path = os.path.join(pkg_dir, 'world')
    existing_gazebo_path = os.environ.get('GAZEBO_MODEL_PATH', '')
    if existing_gazebo_path:
        full_gazebo_path = f'{gazebo_model_path}:{existing_gazebo_path}'
    else:
        full_gazebo_path = gazebo_model_path
    set_gazebo_model_path = SetEnvironmentVariable(
        name='GAZEBO_MODEL_PATH',
        value=full_gazebo_path
    )

    # ============================ 4. 启动 Gazebo 仿真器 ==============================
    gazebo_world_path = os.path.join(pkg_dir, 'world', 'zhihui.world')
    start_gazebo_cmd = ExecuteProcess(
        cmd=['gazebo', '--verbose',
             '-s', 'libgazebo_ros_init.so',
             '-s', 'libgazebo_ros_factory.so',
             gazebo_world_path],
        output='screen'
    )

    # ============================ 5. 机器人初始位姿参数 ==============================
    # 通过命令行参数覆盖，例如：
    #   ros2 launch ... spawn_x:=1.0 spawn_y:=-2.0 spawn_yaw:=1.57
    spawn_x = LaunchConfiguration('spawn_x', default='-4.311092')
    spawn_y = LaunchConfiguration('spawn_y', default='-4.299756')
    spawn_z = LaunchConfiguration('spawn_z', default='0.0')
    spawn_yaw = LaunchConfiguration('spawn_yaw', default='0.0')

    # 将位姿参数设为环境变量，供 spawn 命令使用
    set_spawn_x = SetEnvironmentVariable('SPAWN_X', spawn_x)
    set_spawn_y = SetEnvironmentVariable('SPAWN_Y', spawn_y)
    set_spawn_z = SetEnvironmentVariable('SPAWN_Z', spawn_z)
    set_spawn_yaw = SetEnvironmentVariable('SPAWN_YAW', spawn_yaw)

    # ============================ 6. 在 Gazebo 中生成机器人 ==========================
    # 通过 bash 读取环境变量，确保位姿参数正确传递
    robot_name_in_model = 'mycar'
    spawn_entity_cmd = ExecuteProcess(
        cmd=['bash', '-c',
             'ros2 run gazebo_ros spawn_entity.py '
             '-entity mycar '
             f'-file {spawn_model_path} '
             '-x $SPAWN_X -y $SPAWN_Y -z $SPAWN_Z -Y $SPAWN_YAW'],
        output='screen'
    )

    # ============================ 7. robot_state_publisher ===========================
    robot_description = ParameterValue(
        Command(['xacro ', LaunchConfiguration('model')]),
        value_type=str
    )
    robot_state_publisher_node = Node(
        package='robot_state_publisher',
        executable='robot_state_publisher',
        name='robot_state_publisher',
        output='screen',
        parameters=[{
            'robot_description': robot_description,
            'use_sim_time': True,
            'publish_frequency': 30.0,
        }],
    )

    # ============================ 8. joint_state_publisher ===========================
    joint_state_publisher_node = Node(
        package='joint_state_publisher',
        executable='joint_state_publisher',
        name='joint_state_publisher',
        output='screen',
        parameters=[{'use_sim_time': use_sim_time}],
    )

    # ============================ 9. slam_toolbox 建图节点 ============================
    slam_toolbox_node = Node(
        package='slam_toolbox',
        executable='async_slam_toolbox_node',
        name='slam_toolbox',
        output='screen',
        parameters=[
            slam_params_file,
            {
                'use_sim_time': use_sim_time,
                'map_file_name': map_file_name,
            },
        ],
    )

    # ============================ 10. 组装 LaunchDescription =========================
    return LaunchDescription([
        DeclareLaunchArgument(
            'use_sim_time',
            default_value='true',
            description='使用仿真时间（Gazebo=true, 实车=false)'),
        DeclareLaunchArgument(
            'slam_params_file',
            default_value=os.path.join(pkg_dir, 'config', 'slam_toolbox_async.yaml'),
            description='slam_toolbox 参数配置文件路径'),
        DeclareLaunchArgument(
            'map_file_name',
            default_value='',
            description='已有序列化地图路径（.posegraph），留空则从零开始建图'),
        DeclareLaunchArgument(
            'spawn_x', default_value='-4.311092',
            description='机器人初始 x 坐标（m）'),
        DeclareLaunchArgument(
            'spawn_y', default_value='-4.299756',
            description='机器人初始 y 坐标（m）'),
        DeclareLaunchArgument(
            'spawn_z', default_value='0.0',
            description='机器人初始 z 坐标（m）'),
        DeclareLaunchArgument(
            'spawn_yaw', default_value='0.0',
            description='机器人初始偏航角（rad）'),
        model,

        set_gazebo_model_path,
        set_spawn_x,
        set_spawn_y,
        set_spawn_z,
        set_spawn_yaw,
        start_gazebo_cmd,
        spawn_entity_cmd,
        robot_state_publisher_node,
        joint_state_publisher_node,
        slam_toolbox_node,
    ])
