#
# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
# property and proprietary rights in and to this material, related
# documentation and any modifications thereto. Any use, reproduction,
# disclosure or distribution of this material and related documentation
# without an express license agreement from NVIDIA CORPORATION or
# its affiliates is strictly prohibited.
#

# Third Party
import pytest
import torch

# CuRobo
from curobo.cuda_robot_model.cuda_robot_model import CudaRobotModel, CudaRobotModelConfig
from curobo.cuda_robot_model.urdf_kinematics_parser import UrdfKinematicsParser
from curobo.types.base import TensorDeviceType
from curobo.types.math import Pose
from curobo.util_file import (
    file_exists,
    get_assets_path,
    get_robot_configs_path,
    join_path,
    load_yaml,
)

try:
    # CuRobo
    from curobo.cuda_robot_model.usd_kinematics_parser import UsdKinematicsParser
except ImportError:
    pytest.skip("usd-core is not available, skipping USD tests", allow_module_level=True)


def check_usd_file_exists():
    robot_file = "franka.yml"
    robot_params = load_yaml(join_path(get_robot_configs_path(), robot_file))["robot_cfg"]
    robot_usd = join_path(get_assets_path(), robot_params["kinematics"]["usd_path"])
    return file_exists(robot_usd)


if not check_usd_file_exists():
    pytest.skip("Franka Panda USD is not available, skipping USD tests", allow_module_level=True)


@pytest.fixture(scope="module")
def robot_params_all():
    robot_file = "franka.yml"
    robot_params = load_yaml(join_path(get_robot_configs_path(), robot_file))["robot_cfg"]
    robot_params["kinematics"]["ee_link"] = "panda_link7"
    robot_params["kinematics"]["base_link"] = "panda_link0"
    robot_params["kinematics"]["collision_link_names"] = []
    robot_params["kinematics"]["lock_joints"] = None
    robot_params["kinematics"]["extra_links"] = None

    return robot_params


@pytest.fixture(scope="module")
def usd_parser(robot_params_all):
    robot_params = robot_params_all["kinematics"]
    robot_usd = join_path(get_assets_path(), robot_params["usd_path"])
    kinematics_parser = UsdKinematicsParser(
        usd_path=robot_usd,
        flip_joints=robot_params["usd_flip_joints"],
        usd_robot_root=robot_params["usd_robot_root"],
    )
    return kinematics_parser


@pytest.fixture(scope="module")
def urdf_parser(robot_params_all):
    robot_params = robot_params_all["kinematics"]
    robot_urdf = join_path(get_assets_path(), robot_params["urdf_path"])
    kinematics_parser = UrdfKinematicsParser(robot_urdf)
    return kinematics_parser


@pytest.fixture(scope="module")
def retract_state(robot_params_all):
    tensor_args = TensorDeviceType()
    q = tensor_args.to_device(robot_params_all["kinematics"]["cspace"]["retract_config"]).view(
        1, -1
    )
    return q


@pytest.fixture(scope="module")
def usd_cuda_robot(robot_params_all):
    robot_params = robot_params_all["kinematics"]
    robot_params["use_usd_kinematics"] = True

    usd_cuda_config = CudaRobotModelConfig.from_data_dict(robot_params)

    usd_cuda_robot = CudaRobotModel(usd_cuda_config)
    return usd_cuda_robot


@pytest.fixture(scope="module")
def urdf_cuda_robot(robot_params_all):
    robot_params = robot_params_all["kinematics"]
    robot_params["use_usd_kinematics"] = False
    urdf_cuda_config = CudaRobotModelConfig.from_data_dict(robot_params)

    urdf_cuda_robot = CudaRobotModel(urdf_cuda_config)
    return urdf_cuda_robot


def test_chain_parse(urdf_parser, usd_parser, robot_params_all):
    robot_params = robot_params_all

    urdf_chain = urdf_parser.get_chain(
        robot_params["kinematics"]["base_link"], robot_params["kinematics"]["ee_link"]
    )

    usd_chain = usd_parser.get_chain(
        robot_params["kinematics"]["base_link"], robot_params["kinematics"]["ee_link"]
    )
    assert usd_chain == urdf_chain


def test_joint_transform_parse(usd_cuda_robot, urdf_cuda_robot):
    usd_pose = usd_cuda_robot.get_all_link_transforms()
    urdf_pose = urdf_cuda_robot.get_all_link_transforms()
    p, q = usd_pose.distance(urdf_pose)
    p = torch.linalg.norm(q)
    q = torch.linalg.norm(q)
    assert p < 1e-5 and q < 1e-5


def test_basic_ee_pose(usd_cuda_robot, urdf_cuda_robot, retract_state):
    q = retract_state[:, : usd_cuda_robot.get_dof()]

    usd_state = usd_cuda_robot.get_state(q)
    usd_pose = Pose(usd_state.ee_position, usd_state.ee_quaternion)

    urdf_state = urdf_cuda_robot.get_state(q)
    urdf_pose = Pose(urdf_state.ee_position, urdf_state.ee_quaternion)
    p_d, q_d = usd_pose.distance(urdf_pose)
    assert p_d < 1e-5
    assert q_d < 1e-5
