#!/usr/bin/env python3

from copy import deepcopy
from threading import Thread

import rclpy
from geometry_msgs.msg import Point, Quaternion
from pymoveit2 import MoveIt2, MoveIt2Gripper
from pymoveit2.robots import panda
from rclpy.callback_groups import ReentrantCallbackGroup
from rclpy.node import Node


class MoveItThrowObject(Node):
    def __init__(self):
        super().__init__("ex_throw_object")

        # Create callback group that allows execution of callbacks in parallel without restrictions
        self._callback_group = ReentrantCallbackGroup()

        # Create MoveIt 2 interface
        self._moveit2 = MoveIt2(
            node=self,
            joint_names=panda.joint_names(),
            base_link_name=panda.base_link_name(),
            end_effector_name=panda.end_effector_name(),
            group_name=panda.MOVE_GROUP_ARM,
            callback_group=self._callback_group,
        )
        # Use upper joint velocity and acceleration limits
        self._moveit2.max_velocity = 1.0
        self._moveit2.max_acceleration = 1.0

        # Create MoveIt 2 interface for gripper
        self._moveit2_gripper = MoveIt2Gripper(
            node=self,
            gripper_joint_names=panda.gripper_joint_names(),
            open_gripper_joint_positions=panda.OPEN_GRIPPER_JOINT_POSITIONS,
            closed_gripper_joint_positions=panda.CLOSED_GRIPPER_JOINT_POSITIONS,
            gripper_group_name=panda.MOVE_GROUP_GRIPPER,
            callback_group=self._callback_group,
        )

        self.get_logger().info("Initialization successful.")

    def throw(self):
        """
        Plan and execute hard-coded trajectory with intention to throw an object
        """

        self.get_logger().info("Throwing... Wish me luck!")

        throwing_object_pos = Point(x=0.5, y=0.0, z=0.015)
        default_quat = Quaternion(x=1.0, y=0.0, z=0.0, w=0.0)

        # Open gripper
        self._moveit2_gripper.open()
        self._moveit2_gripper.wait_until_executed()

        # Move above object
        position_above_object = deepcopy(throwing_object_pos)
        position_above_object.z += 0.15
        self._moveit2.move_to_pose(
            position=position_above_object,
            quat_xyzw=default_quat,
        )
        self._moveit2.wait_until_executed()

        # Move to grasp position
        self._moveit2.move_to_pose(
            position=throwing_object_pos,
            quat_xyzw=default_quat,
        )
        self._moveit2.wait_until_executed()

        # Close gripper
        self._moveit2_gripper.close()
        self._moveit2_gripper.wait_until_executed()

        # Decrease speed
        self._moveit2.max_velocity = 0.25
        self._moveit2.max_acceleration = 0.1

        # Move above object (again)
        self._moveit2.move_to_pose(
            position=position_above_object,
            quat_xyzw=default_quat,
        )
        self._moveit2.wait_until_executed()

        # Move to pre-throw configuration
        joint_configuration_pre_throw = [0.0, -1.75, 0.0, -0.1, 0.0, 3.6, 0.8]
        self._moveit2.move_to_configuration(joint_configuration_pre_throw)
        self._moveit2.wait_until_executed()

        # Increase speed
        self._moveit2.max_velocity = 1.0
        self._moveit2.max_acceleration = 1.0

        # Throw itself
        joint_configuration_throw = [0.0, 1.0, 0.0, -1.1, 0.0, 1.9, 0.8]
        self._moveit2.move_to_configuration(joint_configuration_throw)

        # Release object while executing motion
        sleep_duration_s = 1.2
        if rclpy.ok():
            self.create_rate(1 / sleep_duration_s).sleep()
        self._moveit2_gripper.open()
        self._moveit2_gripper.wait_until_executed()
        self._moveit2.wait_until_executed()

        # Return to default configuration
        joint_configuration_default = [
            0.0,
            -0.7853981633974483,
            0.0,
            -2.356194490192345,
            0.0,
            1.5707963267948966,
            0.7853981633974483,
        ]
        self._moveit2.move_to_configuration(joint_configuration_default)
        self._moveit2.wait_until_executed()


def main(args=None):
    rclpy.init(args=args)

    object_thrower = MoveItThrowObject()

    # Spin the node in background thread(s)
    executor = rclpy.executors.MultiThreadedExecutor(3)
    executor.add_node(object_thrower)
    executor_thread = Thread(target=executor.spin, daemon=True, args=())
    executor_thread.start()

    # Wait for everything to setup
    sleep_duration_s = 2.0
    if rclpy.ok():
        object_thrower.create_rate(1 / sleep_duration_s).sleep()

    object_thrower.throw()

    rclpy.shutdown()
    exit(0)


if __name__ == "__main__":
    main()
