#!/usr/bin/env python3
"""Compatibility adapter for the original experimental import path.

New code should import RM75Kinematics and RM75IkSolver from ``rm75_ik``.
"""

from math import pi
from pathlib import Path

import numpy as np
import pinocchio as pin

from rm75_ik import IkOptions, JointLimits, RM75IkSolver, RM75Kinematics


class KinematicsSolver:
    def __init__(self, urdf_path="urdf_rm75/RM75-B.urdf", mesh_dir=None):
        del mesh_dir
        selected_path = Path(urdf_path)
        if not selected_path.is_file() and not selected_path.is_absolute():
            selected_path = Path(__file__).resolve().parent / selected_path
        self._urdf_path = selected_path
        self._limits = None
        self._tools = {}
        self._rebuild()

    def _rebuild(self):
        self.kinematics = RM75Kinematics(self._urdf_path, self._limits)
        self.solver = RM75IkSolver(self.kinematics)
        self.model = self.kinematics.model
        self.data = self.kinematics.data

    def add_tool_frames(self, frames):
        for name, attributes in frames.items():
            pose = np.asarray(attributes[0], dtype=float)
            if pose.shape != (7,):
                raise ValueError(f"tool {name!r} pose must have seven values")
            quaternion = pin.Quaternion(pose[6], pose[3], pose[4], pose[5])
            quaternion.normalize()
            self._tools[name] = pin.SE3(quaternion.matrix(), pose[:3])

    def cfg_j_limit(self, min_j=None, max_j=None, rad_flag=True):
        if min_j is None:
            min_j = [-pi, -2.2689, -pi, -2.3562, -pi, -2.234, -2 * pi]
        if max_j is None:
            max_j = [pi, 2.2689, pi, 2.3562, pi, 2.234, 2 * pi]
        lower = np.asarray(min_j, dtype=float)
        upper = np.asarray(max_j, dtype=float)
        if not rad_flag:
            lower = np.deg2rad(lower)
            upper = np.deg2rad(upper)
        self._limits = JointLimits("legacy", lower, upper)
        self._rebuild()

    def _tool(self, name):
        try:
            return self._tools[name]
        except KeyError as exc:
            raise ValueError(f"unknown tool frame: {name!r}") from exc

    def forward_kinematics(self, joint_angles, tool="no_tool"):
        pose = self.kinematics.forward(np.asarray(joint_angles), self._tool(tool))
        return np.concatenate(
            [pose.translation.copy(), pin.rpy.matrixToRpy(pose.rotation)]
        )

    def inverse_kinematics(
        self,
        target_position,
        target_rpy=None,
        target_quat=None,
        initial_guess=None,
        max_iter=500,
        tolerance=1e-3,
        debug=False,
        tool="no_tool",
    ):
        del debug
        if target_quat is not None:
            values = np.asarray(target_quat, dtype=float)
            quaternion = pin.Quaternion(values[3], values[0], values[1], values[2])
            rotation = quaternion.matrix()
        elif target_rpy is not None:
            rotation = pin.rpy.rpyToMatrix(*target_rpy)
        else:
            rotation = np.eye(3)
        tool_pose = self._tool(tool)
        target_tool = pin.SE3(rotation, np.asarray(target_position, dtype=float))
        target_flange = target_tool * tool_pose.inverse()
        seed = np.zeros(7) if initial_guess is None else np.asarray(initial_guess, dtype=float)
        result = self.solver.solve(
            target_flange,
            seed,
            IkOptions(
                position_tolerance_m=tolerance,
                orientation_tolerance_rad=tolerance,
                max_iterations=max_iter,
            ),
        )
        return (0, result.q.tolist()) if result.success else (-1, [])

    def compute_jacobian(self, joint_angles, tool="no_tool"):
        del tool
        return self.kinematics.jacobian(np.asarray(joint_angles, dtype=float))
