#include "drake/multibody/inverse_kinematics/global_inverse_kinematics.h"

#include <array>
#include <limits>
#include <stack>
#include <string>

#include "drake/common/eigen_types.h"
#include "drake/common/scope_exit.h"
#include "drake/math/roll_pitch_yaw.h"
#include "drake/math/rotation_matrix.h"
#include "drake/multibody/tree/revolute_joint.h"
#include "drake/solvers/rotation_constraint.h"
#include "drake/solvers/solve.h"

using Eigen::Matrix3d;
using Eigen::Vector3d;

using std::string;

using drake::math::RigidTransformd;
using drake::solvers::VectorDecisionVariable;
using drake::symbolic::Expression;

namespace drake {
namespace multibody {
namespace {
std::map<BodyIndex, JointIndex> GetBodyToJointMap(
    const MultibodyPlant<double>& plant) {
  // First loop through each joint, stores the map from the child body to the
  // joint.
  std::map<BodyIndex, JointIndex> body_to_joint_map;
  for (JointIndex joint_index : plant.GetJointIndices()) {
    body_to_joint_map.emplace(plant.get_joint(joint_index).child_body().index(),
                              joint_index);
  }
  return body_to_joint_map;
}

std::unordered_set<BodyIndex> GetWeldToWorldBodyIndexSet(
    const MultibodyPlant<double>& plant) {
  const std::vector<const RigidBody<double>*> weld_to_world_bodies =
      plant.GetBodiesWeldedTo(plant.world_body());
  std::unordered_set<BodyIndex> weld_to_world_body_index_set;
  for (const auto weld_body : weld_to_world_bodies) {
    weld_to_world_body_index_set.insert(weld_body->index());
  }
  return weld_to_world_body_index_set;
}

bool IsWeld(const Joint<double>& joint) {
  const bool is_weld = !joint.can_rotate() && !joint.can_translate() &&
                       joint.num_positions() == 0 &&
                       joint.num_velocities() == 0;
  if (is_weld) {
    DRAKE_THROW_UNLESS(dynamic_cast<const WeldJoint<double>*>(&joint) !=
                       nullptr);
  }
  return is_weld;
}

bool IsRevolute(const Joint<double>& joint) {
  const bool is_revolute = joint.can_rotate() && !joint.can_translate() &&
                           joint.num_positions() == 1 &&
                           joint.num_velocities() == 1;
  if (is_revolute) {
    DRAKE_THROW_UNLESS(dynamic_cast<const RevoluteJoint<double>*>(&joint) !=
                       nullptr);
  }
  return is_revolute;
}
}  // namespace

GlobalInverseKinematics::GlobalInverseKinematics(
    const MultibodyPlant<double>& plant,
    const GlobalInverseKinematics::Options& options)
    : prog_{},
      plant_(plant),
      joint_lower_bounds_{Eigen::VectorXd::Constant(
          plant_.num_positions(), -std::numeric_limits<double>::infinity())},
      joint_upper_bounds_{Eigen::VectorXd::Constant(
          plant_.num_positions(), std::numeric_limits<double>::infinity())} {
  const int num_bodies = plant_.num_bodies();
  R_WB_.resize(num_bodies);
  p_WBo_.resize(num_bodies);
  const Eigen::VectorXd q_lower = plant_.GetPositionLowerLimits();
  const Eigen::VectorXd q_upper = plant_.GetPositionUpperLimits();

  solvers::MixedIntegerRotationConstraintGenerator rotation_generator(
      options.approach, options.num_intervals_per_half_axis,
      options.interval_binning);

  const std::map<BodyIndex, JointIndex> body_to_joint_map =
      GetBodyToJointMap(plant_);
  // Loop through each body in the robot, to add the constraint that the bodies
  // are welded by joints.
  const std::unordered_set<BodyIndex> weld_to_world_body_index_set =
      GetWeldToWorldBodyIndexSet(plant_);
  // This dummy_plant_context is used to compute the pose of a body welded to
  // the world.
  auto dummy_plant_context = plant_.CreateDefaultContext();
  // First go through each body to assign the pose variables.
  for (BodyIndex body_idx{1}; body_idx < num_bodies; ++body_idx) {
    const RigidBody<double>& body = plant_.get_body(body_idx);
    const string body_R_name = body.name() + "_R";
    const string body_pos_name = body.name() + "_pos";
    p_WBo_[body_idx] = prog_.NewContinuousVariables<3>(body_pos_name);
    if (weld_to_world_body_index_set.contains(body_idx)) {
      // This body is welded to the world.
      R_WB_[body_idx] = prog_.NewContinuousVariables<3, 3>(body_R_name);
    } else {
      // This body is not rigidly fixed to the world.
      R_WB_[body_idx] = solvers::NewRotationMatrixVars(&prog_, body_R_name);
    }
  }
  // Now go through each body to add the kinematic constraint between each body
  // and its parent.
  for (BodyIndex body_idx{1}; body_idx < num_bodies; ++body_idx) {
    const RigidBody<double>& body = plant_.get_body(body_idx);
    // If the body is fixed to the world, then fix the decision variables on
    // the body position and orientation.
    if (weld_to_world_body_index_set.contains(body_idx)) {
      // This body is welded to the world.
      const math::RigidTransformd X_WB = plant_.CalcRelativeTransform(
          *dummy_plant_context, plant_.world_frame(), body.body_frame());
      // TODO(hongkai.dai): clean up this for loop using
      // elementwise matrix constraint when it is ready.
      for (int i = 0; i < 3; ++i) {
        prog_.AddBoundingBoxConstraint(X_WB.rotation().matrix().col(i),
                                       X_WB.rotation().matrix().col(i),
                                       R_WB_[body_idx].col(i));
      }
      prog_.AddBoundingBoxConstraint(X_WB.translation(), X_WB.translation(),
                                     p_WBo_[body_idx]);
    } else {
      // This body is not rigidly fixed to the world.
      if (!options.linear_constraint_only) {
        solvers::AddRotationMatrixOrthonormalSocpConstraint(&prog_,
                                                            R_WB_[body_idx]);
      }
      if (body.is_floating()) {
        // This is the floating base case, just add the rotation matrix
        // constraint.
        rotation_generator.AddToProgram(R_WB_[body_idx], &prog_);
        // No need to add the kinematic constraint between the parent and the
        // child link. Skip the code below.
        continue;
      }

      // If the body has a parent, then add the constraint to connect the
      // parent body with this body through a joint.
      const auto joint = &(plant_.get_joint(body_to_joint_map.at(body_idx)));
      if (joint->parent_body().index().is_valid()) {
        // Frame Jp is the inboard frame of the joint, rigidly attached to the
        // parent link. Frame Jc is the outboard frame of the joint, rigidly
        // attached to the child link.
        const int parent_idx = joint->parent_body().index();
        const RigidTransformd X_PJp =
            joint->frame_on_parent().GetFixedPoseInBodyFrame();
        const RigidTransformd X_CJc =
            joint->frame_on_child().GetFixedPoseInBodyFrame();
        if (IsWeld(*joint)) {
          const WeldJoint<double>* weld_joint =
              static_cast<const WeldJoint<double>*>(joint);

          const RigidTransformd X_JpJc = weld_joint->X_FM();
          const RigidTransformd X_PC =
              X_PJp * X_JpJc * X_CJc.inverse();
          // Fixed to the parent body.

          // The position can be computed from the parent body pose.
          // p_WC = p_WP + R_WP * p_PC
          // where C is the child body frame.
          //       P is the parent body frame.
          //       W is the world frame.
          prog_.AddLinearEqualityConstraint(
              p_WBo_[parent_idx] + R_WB_[parent_idx] * X_PC.translation() -
                  p_WBo_[body_idx],
              Vector3d::Zero());

          // The orientation can be computed from the parent body orientation.
          // R_WP * R_PC = R_WC
          Matrix3<Expression> orient_invariance =
              R_WB_[parent_idx] * X_PC.rotation().matrix() - R_WB_[body_idx];
          for (int i = 0; i < 3; ++i) {
            prog_.AddLinearEqualityConstraint(orient_invariance.col(i),
                                              Vector3d::Zero());
          }
        } else if (IsRevolute(*joint)) {
          const RevoluteJoint<double>* revolute_joint =
              static_cast<const RevoluteJoint<double>*>(joint);
          // Adding mixed-integer constraint will add binary variables into
          // the program.
          rotation_generator.AddToProgram(R_WB_[body_idx], &prog_);

          // axis is the vector of the rotation axis in the joint
          // inboard/outboard frame.
          const Vector3d axis = revolute_joint->revolute_axis();

          // Add the constraint R_WC * R_CJc * axis_Jc = R_WP * R_PJp * axis_Jp,
          // where axis_Jc = axis_Jp since the rotation axis is invaraiant in
          // the inboard frame Jp and the outboard frame Jc.
          prog_.AddLinearEqualityConstraint(
              R_WB_[body_idx] * X_CJc.rotation().matrix() * axis -
                  R_WB_[parent_idx] * X_PJp.rotation().matrix() * axis,
              Vector3d::Zero());

          // The position of the rotation axis is the same on both child and
          // parent bodies.
          prog_.AddLinearEqualityConstraint(
              p_WBo_[parent_idx] + R_WB_[parent_idx] * X_PJp.translation() -
                  p_WBo_[body_idx] - R_WB_[body_idx] * X_CJc.translation(),
              Vector3d::Zero());

          // Now we process the joint limits constraint.
          const double joint_lb = q_lower(revolute_joint->position_start());
          const double joint_ub = q_upper(revolute_joint->position_start());
          AddJointLimitConstraint(body_idx, joint_lb, joint_ub,
                                  options.linear_constraint_only);
        } else {
          throw std::runtime_error("Unsupported joint type.");
        }
      }
    }
  }
}

const solvers::MatrixDecisionVariable<3, 3>&
GlobalInverseKinematics::body_rotation_matrix(BodyIndex body_index) const {
  if (body_index >= plant_.num_bodies() || body_index <= 0) {
    throw std::runtime_error("body index out of range.");
  }
  return R_WB_[body_index];
}

const solvers::VectorDecisionVariable<3>&
GlobalInverseKinematics::body_position(BodyIndex body_index) const {
  if (body_index >= plant_.num_bodies() || body_index <= 0) {
    throw std::runtime_error("body index out of range.");
  }
  return p_WBo_[body_index];
}

void GlobalInverseKinematics::ReconstructGeneralizedPositionSolutionForBody(
    const solvers::MathematicalProgramResult& result, BodyIndex body_idx,
    const std::map<BodyIndex, JointIndex>& body_to_joint_map,
    const std::unordered_set<BodyIndex>& weld_to_world_body_index_set,
    Eigen::Ref<Eigen::VectorXd> q,
    std::vector<Eigen::Matrix3d>* reconstruct_R_WB) const {
  const RigidBody<double>& body = plant_.get_body(body_idx);
  const Matrix3d R_WC = result.GetSolution(R_WB_[body_idx]);
  if (body.is_floating()) {
    // p_WBi is the position of the body frame in the world frame.
    const Vector3d p_WBi = result.GetSolution(p_WBo_[body_idx]);
    const math::RotationMatrix<double> normalized_rotmat =
        math::RotationMatrix<double>::ProjectToRotationMatrix(R_WC);

    q.segment<3>(body.floating_positions_start()) = p_WBi;
    if (body.has_quaternion_dofs()) {
      // The position order is x-y-z-qw-qx-qy-qz, namely translation
      // first, and quaternion second.
      q.segment<4>(body.floating_positions_start() + 3) =
          normalized_rotmat.ToQuaternionAsVector4();
    } else {
      // The position order is x-y-z-roll-pitch-yaw.
      q.segment<3>(body.floating_positions_start() + 3) =
          math::RollPitchYaw<double>(normalized_rotmat).vector();
    }
    reconstruct_R_WB->at(body_idx) = normalized_rotmat.matrix();
    return;
  }
  // This dummy_plant_context is used to compute the pose of a body welded to
  // the world.
  auto dummy_plant_context = plant_.CreateDefaultContext();
  const Joint<double>& joint = plant_.get_joint(body_to_joint_map.at(body_idx));
  const RigidBody<double>& parent = joint.parent_body();
  if (!weld_to_world_body_index_set.contains(body_idx)) {
    // R_WP is the rotation matrix of parent frame to the world frame.
    const Matrix3d& R_WP = reconstruct_R_WB->at(parent.index());
    const RigidTransformd X_PJp =
        joint.frame_on_parent().GetFixedPoseInBodyFrame();
    const RigidTransformd X_CJc =
        joint.frame_on_child().GetFixedPoseInBodyFrame();

    // For each different type of joints, use a separate branch to compute
    // the posture for that joint.
    if (joint.num_positions() == 1) {
      const int position_idx = joint.position_start();
      const double joint_lb = joint_lower_bounds_(position_idx);
      const double joint_ub = joint_upper_bounds_(position_idx);
      // Should NOT do this evil dynamic cast here, but currently we do
      // not have a method to tell if a joint is revolute or not.
      if (dynamic_cast<const RevoluteJoint<double>*>(&joint) != nullptr) {
        const RevoluteJoint<double>* revolute_joint =
            dynamic_cast<const RevoluteJoint<double>*>(&joint);
        const Matrix3d R_JpJc = X_PJp.rotation().matrix().transpose() *
                                R_WP.transpose() * R_WC *
                                X_CJc.rotation().matrix();
        // The matrix R_JpJc is very likely not on SO(3). The reason is
        // that we use a relaxation of the rotation matrix, and thus
        // R_WC and R_WP might not lie on SO(3) exactly. Here we need to project
        // R_JpJc to SO(3), with joint axis as the rotation axis, and
        // joint limits as the lower and upper bound on the rotation angle.
        const Vector3d axis_F = revolute_joint->revolute_axis();
        const double revolute_joint_angle = math::ProjectMatToRotMatWithAxis(
            R_JpJc, axis_F, joint_lb, joint_ub);
        q(position_idx) = revolute_joint_angle;
        reconstruct_R_WB->at(body_idx) =
            R_WP * X_PJp.rotation().matrix() *
            Eigen::AngleAxisd(revolute_joint_angle, axis_F).toRotationMatrix() *
            X_CJc.rotation().matrix().transpose();
      } else {
        // TODO(hongkai.dai): add prismatic and helical joints.
        throw std::runtime_error("Unsupported joint type.");
      }
    } else if (joint.num_positions() == 0) {
      // Deliberately left empty because the joint is removed by welding the
      // parent body to the child body.
    }
  } else {
    // The reconstructed body orientation is just the world fixed
    // orientation.
    const RigidTransformd X_WB = plant_.CalcRelativeTransform(
        *dummy_plant_context, plant_.world_frame(), body.body_frame());
    reconstruct_R_WB->at(body_idx) = X_WB.rotation().matrix();
  }
}

Eigen::VectorXd GlobalInverseKinematics::ReconstructGeneralizedPositionSolution(
    const solvers::MathematicalProgramResult& result) const {
  Eigen::VectorXd q(plant_.num_positions());
  // First loop through each joint, stores the map from the child body to the
  // joint.
  const std::map<BodyIndex, JointIndex> body_to_joint_map =
      GetBodyToJointMap(plant_);
  const std::unordered_set<BodyIndex> weld_to_world_body_index_set =
      GetWeldToWorldBodyIndexSet(plant_);
  // reconstruct_R_WB[i] is the orientation of body i'th body frame expressed in
  // the world frame, computed from the reconstructed posture.
  std::vector<Eigen::Matrix3d> reconstruct_R_WB(plant_.num_bodies());
  // is_link_visited[i] is set to true, if the angle of the joint on link i has
  // been reconstructed.
  std::vector<bool> is_link_visited(plant_.num_bodies(), false);
  // The first one is the world frame, thus the orientation is identity.
  reconstruct_R_WB[0].setIdentity();
  is_link_visited[0] = true;
  int num_link_visited = 1;
  BodyIndex body_idx{1};
  while (num_link_visited < plant_.num_bodies()) {
    if (!is_link_visited[body_idx]) {
      // unvisited_links records all the unvisited links, along the kinematic
      // path from the root to the body with index body_idx (including
      // body_idx).
      std::stack<BodyIndex> unvisited_links;
      unvisited_links.push(body_idx);
      BodyIndex parent_idx{};
      if (plant_.get_body(body_idx).is_floating()) {
        parent_idx = plant_.world_body().index();
      } else {
        parent_idx = plant_.get_joint(body_to_joint_map.at(body_idx))
                         .parent_body()
                         .index();
      }
      while (!is_link_visited[parent_idx]) {
        unvisited_links.push(parent_idx);
        // Now update parent_idx
        if (plant_.get_body(BodyIndex{parent_idx}).is_floating()) {
          parent_idx = plant_.world_body().index();
        } else {
          parent_idx =
              plant_.get_joint(body_to_joint_map.at(BodyIndex{parent_idx}))
                  .parent_body()
                  .index();
        }
      }
      // Now the link parent_idx has been visited.
      while (!unvisited_links.empty()) {
        const BodyIndex unvisited_link_idx = unvisited_links.top();
        unvisited_links.pop();
        ReconstructGeneralizedPositionSolutionForBody(
            result, unvisited_link_idx, body_to_joint_map,
            weld_to_world_body_index_set, q, &reconstruct_R_WB);
        is_link_visited[unvisited_link_idx] = true;
        ++num_link_visited;
      }
    }
    ++body_idx;
  }
  return q;
}

solvers::Binding<solvers::LinearConstraint>
GlobalInverseKinematics::AddWorldPositionConstraint(
    BodyIndex body_idx, const Eigen::Vector3d& p_BQ,
    const Eigen::Vector3d& box_lb_F, const Eigen::Vector3d& box_ub_F,
    const RigidTransformd& X_WF) {
  if (body_idx >= plant_.num_bodies() || body_idx <= 0) {
    throw std::runtime_error("body index out of range.");
  }
  const Vector3<Expression> p_WQ = p_WBo_[body_idx] + R_WB_[body_idx] * p_BQ;
  return prog_.AddLinearConstraint(X_WF.inverse().cast<Expression>() * p_WQ,
                                   box_lb_F, box_ub_F);
}

solvers::Binding<solvers::LinearConstraint>
GlobalInverseKinematics::AddWorldRelativePositionConstraint(
    BodyIndex body_idx_B, const Eigen::Vector3d& p_BQ,
    BodyIndex body_idx_A, const Eigen::Vector3d& p_AP,
    const Eigen::Vector3d& box_lb_F, const Eigen::Vector3d& box_ub_F,
    const RigidTransformd& X_WF) {
  if (body_idx_B >= plant_.num_bodies() || body_idx_B <= 0) {
    throw std::runtime_error("body index out of range.");
  }
  if (body_idx_A >= plant_.num_bodies() || body_idx_A <= 0) {
    throw std::runtime_error("body index out of range.");
  }
  const Vector3<Expression> p_WQ =
      p_WBo_[body_idx_B] + R_WB_[body_idx_B] * p_BQ;
  const Vector3<Expression> p_WP =
      p_WBo_[body_idx_A] + R_WB_[body_idx_A] * p_AP;
  return prog_.AddLinearConstraint(
      X_WF.rotation().inverse().cast<Expression>() * (p_WQ - p_WP), box_lb_F,
      box_ub_F);
}

solvers::Binding<solvers::LinearConstraint>
GlobalInverseKinematics::AddWorldOrientationConstraint(
    BodyIndex body_idx, const Eigen::Quaterniond& desired_orientation,
    double angle_tol) {
  if (body_idx >= plant_.num_bodies() || body_idx <= 0) {
    throw std::runtime_error("body index out of range.");
  }
  // The rotation matrix error R_e satisfies
  // trace(R_e) = 2 * cos(θ) + 1, where θ is the rotation angle between
  // desired orientation and the current orientation. Thus the constraint is
  // 2 * cos(angle_tol) + 1 <= trace(R_e) <= 3
  Matrix3<Expression> rotation_matrix_err =
      desired_orientation.toRotationMatrix() * R_WB_[body_idx].transpose();
  double lb = angle_tol < M_PI ? 2 * cos(angle_tol) + 1 : -1;
  return prog_.AddLinearConstraint(rotation_matrix_err.trace(), lb, 3);
}

void GlobalInverseKinematics::AddPostureCost(
    const Eigen::Ref<const Eigen::VectorXd>& q_desired,
    const Eigen::Ref<const Eigen::VectorXd>& body_position_cost,
    const Eigen::Ref<const Eigen::VectorXd>& body_orientation_cost) {
  const int num_bodies = plant_.num_bodies();
  if (body_position_cost.rows() != num_bodies) {
    std::ostringstream oss;
    oss << "body_position_cost should have " << num_bodies << " rows.";
    throw std::runtime_error(oss.str());
  }
  if (body_orientation_cost.rows() != num_bodies) {
    std::ostringstream oss;
    oss << "body_orientation_cost should have " << num_bodies << " rows.";
    throw std::runtime_error(oss.str());
  }
  for (int i = 1; i < num_bodies; ++i) {
    if (body_position_cost(i) < 0) {
      std::ostringstream oss;
      oss << "body_position_cost(" << i << ") is negative.";
      throw std::runtime_error(oss.str());
    }
    if (body_orientation_cost(i) < 0) {
      std::ostringstream oss;
      oss << "body_orientation_cost(" << i << ") is negative.";
      throw std::runtime_error(oss.str());
    }
  }
  auto context = plant_.CreateDefaultContext();
  plant_.SetPositions(context.get(), q_desired);

  // Sum up the orientation error for each body to orient_err_sum.
  Expression orient_err_sum(0);
  // p_WBo_err(i) is the slack variable, representing the position error for
  // the (i+1)'th body, which is the Euclidean distance from the body origin
  // position, to the desired position.
  solvers::VectorXDecisionVariable p_WBo_err =
      prog_.NewContinuousVariables(num_bodies - 1, "p_WBo_error");
  for (int i = 1; i < num_bodies; ++i) {
    // body 0 is the world. There is no position or orientation error on the
    // world, so we just skip i = 0 and start from i = 1.
    const auto& X_WB_desired = plant_.CalcRelativeTransform(
        *context, plant_.world_frame(),
        plant_.get_body(BodyIndex{i}).body_frame());
    // Add the constraint p_WBo_err(i-1) >= body_position_cost(i) *
    // |p_WBo(i) - p_WBo_desired(i) |
    Vector4<symbolic::Expression> pos_error_expr;
    pos_error_expr << p_WBo_err(i - 1),
        body_position_cost(i) * (p_WBo_[i] - X_WB_desired.translation());
    prog_.AddLorentzConeConstraint(pos_error_expr);

    // The orientation error is on the angle θ between the body orientation and
    // the desired orientation, namely 1 - cos(θ).
    // cos(θ) can be computed as (trace( R_WB_desired * R_WB_[i]ᵀ) - 1) / 2.
    // To see how the angle is computed from a rotation matrix, please refer to
    // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/
    orient_err_sum +=
        body_orientation_cost(i) *
        (1 -
         ((X_WB_desired.rotation().matrix() * R_WB_[i].transpose()).trace() -
          1) /
             2);
  }

  // The total cost is the summation of the position error and the orientation
  // error.
  prog_.AddCost(p_WBo_err.cast<Expression>().sum() + orient_err_sum);
}

solvers::VectorXDecisionVariable
GlobalInverseKinematics::BodyPointInOneOfRegions(
    BodyIndex body_index, const Eigen::Ref<const Eigen::Vector3d>& p_BQ,
    const std::vector<Eigen::Matrix3Xd>& region_vertices) {
  const auto& R_WB = body_rotation_matrix(body_index);
  const auto& p_WBo = body_position(body_index);
  const int num_regions = region_vertices.size();
  const string& body_name = plant_.get_body(body_index).name();
  solvers::VectorXDecisionVariable z =
      prog_.NewBinaryVariables(num_regions, "z_" + body_name);
  std::vector<solvers::VectorXDecisionVariable> w(num_regions);

  // We will write p_WQ in two ways, we first write p_WQ as
  // sum_i (w_i1 * v_i1 + w_i2 * v_i2 + ... + w_in * v_in). As the convex
  // combination of vertices in one of the regions.
  Vector3<symbolic::Expression> p_WQ;
  p_WQ << 0, 0, 0;
  for (int i = 0; i < num_regions; ++i) {
    const int num_vertices_i = region_vertices[i].cols();
    if (num_vertices_i < 3) {
      throw std::runtime_error("Each region should have at least 3 vertices.");
    }
    w[i] = prog_.NewContinuousVariables(
        num_vertices_i, "w_" + body_name + "_region_" + std::to_string(i));
    prog_.AddLinearConstraint(w[i].cast<symbolic::Expression>().sum() - z(i) ==
                              0);
    prog_.AddBoundingBoxConstraint(Eigen::VectorXd::Zero(num_vertices_i),
                                   Eigen::VectorXd::Ones(num_vertices_i), w[i]);
    p_WQ += region_vertices[i] * w[i];
  }

  prog_.AddLinearConstraint(z.cast<symbolic::Expression>().sum() == 1);

  // p_WQ must match the body pose, as p_WQ = p_WBo + R_WB * p_BQ
  prog_.AddLinearEqualityConstraint(p_WBo + R_WB * p_BQ - p_WQ,
                                    Eigen::Vector3d::Zero());

  return z;
}

solvers::VectorXDecisionVariable
GlobalInverseKinematics::BodySphereInOneOfPolytopes(
    BodyIndex body_index, const Eigen::Ref<const Eigen::Vector3d>& p_BQ,
    double radius,
    const std::vector<GlobalInverseKinematics::Polytope3D>& polytopes) {
  if (body_index >= plant_.num_bodies() || body_index <= 0) {
    throw std::runtime_error("body index out of range.");
  }
  DRAKE_DEMAND(radius >= 0);
  const int num_polytopes = static_cast<int>(polytopes.size());
  const auto z = prog_.NewBinaryVariables(num_polytopes, "z");
  // z1 + ... + zn = 1
  prog_.AddLinearEqualityConstraint(Eigen::RowVectorXd::Ones(num_polytopes), 1,
                                    z);

  const auto y =
      prog_.NewContinuousVariables<3, Eigen::Dynamic>(3, num_polytopes, "y");
  const Vector3<symbolic::Expression> p_WQ =
      p_WBo_[body_index] + R_WB_[body_index] * p_BQ;
  // p_WQ = y.col(0) + ... + y.col(n)
  prog_.AddLinearEqualityConstraint(
      p_WQ - y.cast<symbolic::Expression>().rowwise().sum(),
      Eigen::Vector3d::Zero());

  for (int i = 0; i < num_polytopes; ++i) {
    DRAKE_DEMAND(polytopes[i].A.rows() == polytopes[i].b.rows());
    prog_.AddLinearConstraint(
        polytopes[i].A * y.col(i) <=
        (polytopes[i].b - polytopes[i].A.rowwise().norm() * radius) * z(i));
  }

  return z;
}

// Approximate a quadratic constraint (which could be formulated as a Lorentz
// cone constraint) xᵀx ≤ c² by
// -c ≤ xᵢ ≤ c
// ± xᵢ ± xⱼ ≤ √2 * c
// ± x₀ ± x₁ ± x₂ ≤ √3 * c
// These linear approximation are obtained as the tangential planes at some
// points on the surface of the sphere xᵀx ≤ c².
void ApproximateBoundedNormByLinearConstraints(
    const Eigen::Ref<const Vector3<symbolic::Expression>>& x, double c,
    solvers::MathematicalProgram* prog) {
  DRAKE_DEMAND(c >= 0);
  // -c ≤ xᵢ ≤ c
  prog->AddLinearConstraint(x, Eigen::Vector3d::Constant(-c),
                            Eigen::Vector3d::Constant(c));
  const double sqrt2_c = std::sqrt(2) * c;
  const double sqrt3_c = std::sqrt(3) * c;
  // ± xᵢ ± xⱼ ≤ √2 * c
  for (int i = 0; i < 3; ++i) {
    for (int j = i + 1; j < 3; ++j) {
      prog->AddLinearConstraint(x(i) + x(j), -sqrt2_c, sqrt2_c);
      prog->AddLinearConstraint(x(i) - x(j), -sqrt2_c, sqrt2_c);
    }
  }
  // ± x₀ ± x₁ ± x₂ ≤ √3 * c
  prog->AddLinearConstraint(x(0) + x(1) + x(2), -sqrt3_c, sqrt3_c);
  prog->AddLinearConstraint(x(0) + x(1) - x(2), -sqrt3_c, sqrt3_c);
  prog->AddLinearConstraint(x(0) - x(1) + x(2), -sqrt3_c, sqrt3_c);
  prog->AddLinearConstraint(x(0) - x(1) - x(2), -sqrt3_c, sqrt3_c);
}

void GlobalInverseKinematics::AddJointLimitConstraint(
    BodyIndex body_index, double joint_lower_bound, double joint_upper_bound,
    bool linear_constraint_approximation) {
  if (joint_lower_bound > joint_upper_bound) {
    throw std::runtime_error(
        "The joint lower bound should be no larger than the upper bound.");
  }
  if (plant_.get_body(body_index).is_floating()) {
    throw std::runtime_error(
        "The body is floating, do not use AddJointLimitConstraint(), impose "
        "the bounds on R_WB and p_WB directly.");
  }
  const Joint<double>* joint{nullptr};
  for (JointIndex joint_index : plant_.GetJointIndices()) {
    if (plant_.get_joint(joint_index).child_body().index() == body_index) {
      joint = &(plant_.get_joint(joint_index));
      break;
    }
  }
  if (joint == nullptr) {
    throw std::runtime_error(
        fmt::format("The body {} is not the child of any joint in the plant.",
                    plant_.get_body(body_index).name()));
  }
  const RigidBody<double>& parent = joint->parent_body();
  const int parent_idx = parent.index();
  const RigidTransformd X_PJp =
      joint->frame_on_parent().GetFixedPoseInBodyFrame();
  const RigidTransformd X_CJc =
      joint->frame_on_child().GetFixedPoseInBodyFrame();
  switch (joint->num_velocities()) {
    case 0: {
      // Fixed to the parent body.
      throw std::runtime_error("Cannot impose joint limits for a fixed joint.");
    }
    case 1: {
      // If the new bound [joint_lower_bound joint_upper_bound] is not tighter
      // than the existing bound, then we ignore it, without adding new
      // constraints.
      bool is_limits_tightened = false;
      const int position_idx = joint->position_start();
      if (joint_lower_bound > joint_lower_bounds_(position_idx)) {
        joint_lower_bounds_(position_idx) = joint_lower_bound;
        is_limits_tightened = true;
      }
      if (joint_upper_bound < joint_upper_bounds_(position_idx)) {
        joint_upper_bounds_(position_idx) = joint_upper_bound;
        is_limits_tightened = true;
      }
      if (is_limits_tightened) {
        // Should NOT do this evil dynamic cast here, but currently we do
        // not have a method to tell if a joint is revolute or not.
        if (dynamic_cast<const RevoluteJoint<double>*>(joint) != nullptr) {
          const auto* revolute_joint =
              dynamic_cast<const RevoluteJoint<double>*>(joint);
          // axis_F is the vector of the rotation axis in the joint
          // inboard/outboard frame.
          const Vector3d axis_F = revolute_joint->revolute_axis();

          // Now we process the joint limits constraint.
          const double joint_bound = (joint_upper_bounds_[position_idx] -
                                      joint_lower_bounds_[position_idx]) /
                                     2;

          if (joint_bound < M_PI) {
            // We use the fact that if the angle between two unit length
            // vectors u and v is smaller than α, it is equivalent to
            // |u - v| <= 2*sin(α/2)
            // which is a second order cone constraint.

            // If the rotation angle θ satisfies
            // a <= θ <= b
            // This is equivalent to
            // -α <= θ - (a+b)/2 <= α
            // where α = (b-a) / 2, (a+b) / 2 is the joint offset, such that
            // the bounds on β = θ - (a+b)/2 are symmetric.
            // We use the following notation:
            // R_WP     The rotation matrix of parent frame `P` to world
            //          frame `W`.
            // R_WC     The rotation matrix of child frame `C` to world
            //          frame `W`.
            // R_PJp    The rotation matrix of joint frame `Jp` to parent
            //          frame `P`.
            // R(k, β)  The rotation matrix along joint axis k by angle β.
            // R_JcC    The rotation matrix of child frame `C` to the
            //          outboard frame `Jc`.
            // The kinematics constraint is
            // R_WP * R_PJp * R(k, θ) * R_JcC = R_WC.
            // This is equivalent to
            // R_WP * R_PJp * R(k, (a+b)/2) * R(k, β)) = R_WC * R_CJc.
            // So to constrain that -α <= β <= α,
            // we can constrain the angle between the two vectors
            // R_WC * R_CJc * v and R_WP * R_PJp * R(k,(a+b)/2) * v is no larger
            // than α, where v is a unit length vector perpendicular to the
            // rotation axis k, in the joint frame. Thus we can constrain that
            // |R_WC*R_CJc*v - R_WP * R_PJp * R(k,(a+b)/2)*v | <= 2*sin (α / 2)
            // as we explained above.

            // First generate a vector v_C that is perpendicular to rotation
            // axis, in child frame.
            Vector3d v_C = axis_F.cross(Vector3d(1, 0, 0));
            double v_C_norm = v_C.norm();
            if (v_C_norm < sqrt(2) / 2) {
              // axis_F is almost parallel to [1; 0; 0]. Try another axis
              // [0, 1, 0]
              v_C = axis_F.cross(Vector3d(0, 1, 0));
              v_C_norm = v_C.norm();
            }
            // Normalizes the revolute vector.
            v_C /= v_C_norm;

            // The constraint would be tighter, if we choose many unit
            // length vector `v`, perpendicular to the joint axis, in the
            // joint frame. Here to balance between the size of the
            // optimization problem, and the tightness of the convex
            // relaxation, we just use four vectors in `v`. Notice that
            // v_basis contains the orthonormal basis of the null space
            // null(axis_F).
            std::array<Eigen::Vector3d, 2> v_basis = {{v_C, axis_F.cross(v_C)}};
            v_basis[1] /= v_basis[1].norm();

            std::array<Eigen::Vector3d, 4> v_samples;
            v_samples[0] = v_basis[0];
            v_samples[1] = v_basis[1];
            v_samples[2] = v_basis[0] + v_basis[1];
            v_samples[2] /= v_samples[2].norm();
            v_samples[3] = v_basis[0] - v_basis[1];
            v_samples[3] /= v_samples[3].norm();

            // rotmat_joint_offset is R(k, (a+b)/2) explained above.
            const Matrix3d rotmat_joint_offset =
                Eigen::AngleAxisd((joint_lower_bounds_[position_idx] +
                                   joint_upper_bounds_[position_idx]) /
                                      2,
                                  axis_F)
                    .toRotationMatrix();

            // joint_limit_expr is going to be within the Lorentz cone.
            Eigen::Matrix<Expression, 4, 1> joint_limit_expr;
            const double joint_limit_lorentz_rhs = 2 * sin(joint_bound / 2);
            joint_limit_expr(0) = joint_limit_lorentz_rhs;
            for (const auto& v : v_samples) {
              // joint_limit_expr.tail<3> is
              // R_WC * R_CJc * v - R_WP * R_PJp * R(k,(a+b)/2) * v mentioned
              // above.
              joint_limit_expr.tail<3>() =
                  R_WB_[body_index] * X_CJc.rotation().matrix() * v -
                  R_WB_[parent_idx] * X_PJp.rotation().matrix() *
                      rotmat_joint_offset * v;
              if (linear_constraint_approximation) {
                ApproximateBoundedNormByLinearConstraints(
                    joint_limit_expr.tail<3>(), joint_limit_lorentz_rhs,
                    &prog_);

              } else {
                prog_.AddLorentzConeConstraint(joint_limit_expr);
              }
            }

            const std::unordered_set<BodyIndex> weld_to_world_body_index_set =
                GetWeldToWorldBodyIndexSet(plant_);
            // This dummy_plant_context is used to compute the pose of a body
            // welded to the world.
            auto dummy_plant_context = plant_.CreateDefaultContext();
            if (weld_to_world_body_index_set.contains(BodyIndex{parent_idx})) {
              const RigidTransformd X_WP = plant_.CalcRelativeTransform(
                  *dummy_plant_context, plant_.world_frame(),
                  plant_.get_body(BodyIndex{parent_idx}).body_frame());
              // If the parent body is rigidly fixed to the world. Then we
              // can impose a tighter constraint. Based on the derivation
              // above, we have
              // R(k, β) = [R_WP * R_PJp * R(k, (a+b)/2)]ᵀ * R_WC * R_CJc
              // as a linear expression of the decision variable R_WC
              // (notice that R_WP is constant, since the parent body is
              // rigidly fixed to the world.
              // Any unit length vector `v` that is perpendicular to
              // joint axis `axis_F` in the joint Frame, can be written as
              //   v = V * u, uᵀ * u = 1
              // where V = [v_basis[0] v_basis[1]] containing the basis
              // vectors for the linear space Null(axis_F).
              // On the other hand, we know
              //   vᵀ * R(k, β) * v = cos(β) >= cos(α)
              // due to the joint limits constraint
              //   -α <= β <= α.
              // So we have the condition that
              // uᵀ * u = 1
              //    => uᵀ * Vᵀ * R(k, β) * V * u >= cos(α)
              // Using S-lemma, we know this implication is equivalent to
              // Vᵀ * [R(k, β) + R(k, β)ᵀ]/2 * V - cos(α) * I is p.s.d
              // We let a 2 x 2 matrix
              //   M = Vᵀ * [R(k, β) + R(k, β)ᵀ]/2 * V - cos(α) * I
              // A 2 x 2 matrix M being positive semidefinite (p.s.d) is
              // equivalent to the condition that
              // [M(0, 0), M(1, 1), M(1, 0)] is in the rotated Lorentz cone.
              // R_joint_beta is R(k, β) in the documentation.
              Eigen::Matrix<symbolic::Expression, 3, 3> R_joint_beta =
                  (X_WP.rotation().matrix() * X_PJp.rotation().matrix() *
                   rotmat_joint_offset)
                      .transpose() *
                  R_WB_[body_index] * X_CJc.rotation().matrix();
              const double joint_bound_cos{std::cos(joint_bound)};
              if (!linear_constraint_approximation) {
                Eigen::Matrix<double, 3, 2> V;
                V << v_basis[0], v_basis[1];
                const Eigen::Matrix<symbolic::Expression, 2, 2> M =
                    V.transpose() * (R_joint_beta + R_joint_beta.transpose()) /
                        2 * V -
                    joint_bound_cos * Eigen::Matrix2d::Identity();
                prog_.AddRotatedLorentzConeConstraint(
                    Vector3<symbolic::Expression>(M(0, 0), M(1, 1), M(1, 0)));
              }

              // From Rodriguez formula, we know that -α <= β <= α implies
              // trace(R(k, β)) = 1 + 2 * cos(β) >= 1 + 2*cos(α)
              // So we can impose the constraint
              // 1+2*cos(α) ≤ trace(R(k, β))
              const symbolic::Expression R_joint_beta_trace{
                  R_joint_beta.trace()};
              prog_.AddLinearConstraint(R_joint_beta_trace >=
                                        1 + 2 * joint_bound_cos);
            }
          }
        } else {
          // TODO(hongkai.dai): add prismatic and helical joint.
          throw std::runtime_error("Unsupported joint type.");
        }
      }
      break;
    }
    case 6: {
      break;
    }
    default:
      throw std::runtime_error("Unsupported joint type.");
  }
}

// TODO(russt): This method is currently calling Solve in order to compute the
// integer variables (given the poses). This is an easy, but relatively
// expensive way; we could instead compute them in closed form based on the
// implementation of the McCormick envelope constraints.
void GlobalInverseKinematics::SetInitialGuess(
    const Eigen::Ref<const Eigen::VectorXd>& q) {
  auto context = plant_.CreateDefaultContext();
  plant_.SetPositions(context.get(), q);

  std::vector<solvers::Binding<solvers::BoundingBoxConstraint>> bindings;

  // body 0 is the world. There is no position or orientation error on the
  // world, so we just skip i = 0 and start from i = 1.
  for (int i = 1; i < plant_.num_bodies(); ++i) {
    const auto& X_WB = plant_.CalcRelativeTransform(
        *context, plant_.world_frame(),
        plant_.get_body(BodyIndex{i}).body_frame());

    bindings.push_back(prog_.AddBoundingBoxConstraint(
        X_WB.translation(), X_WB.translation(), p_WBo_[i]));
    bindings.push_back(prog_.AddBoundingBoxConstraint(
        X_WB.rotation().matrix(), X_WB.rotation().matrix(), R_WB_[i]));
  }

  ScopeExit guard([&]() {
    for (const auto& b : bindings) {
      prog_.RemoveConstraint(b);
    }
  });

  const auto result = solvers::Solve(prog_);

  if (!result.is_success()) {
    throw std::runtime_error(
        "SetInitialGuess tried to solve a variant of your IK problem, but "
        "failed.");
  }

  prog_.SetInitialGuessForAllVariables(result.GetSolution());
}

}  // namespace multibody
}  // namespace drake
