#pragma once

#include <utility>

#include <Eigen/Core>

namespace drake {
namespace math {
/**
 * For a symmetric positive semidefinite matrix Y, decompose it into XᵀX, where
 * the number of rows in X equals to the rank of Y.
 * Notice that this decomposition is not unique. For any orthonormal matrix U,
 * s.t UᵀU = identity, X_prime = UX also satisfies X_primeᵀX_prime = Y. Here
 * we only return one valid decomposition.
 * @param Y A symmetric positive semidefinite matrix.
 * @param zero_tol We will need to check if some value (for example, the
 * absolute value of Y's eigenvalues) is smaller than zero_tol. If it is, then
 * we deem that value as 0.
 * @param return_empty_if_not_psd If true, then return an empty matrix of size
 * 0-by-Y.cols() if Y is not PSD (either the decomposition fails or the
 * resulting eigenvalues are less that @p zero_tol). If false (the default),
 * then throw an exception if Y is not PSD.  This option is particularly useful
 * because it is brittle/expensive to test the exact success criteria before
 * calling this function.
 * @retval X. The matrix X satisfies XᵀX = Y and X.rows() = rank(Y).
 * @pre 1. Y is positive semidefinite or return_empty_if_not_psd = true.
 *      2. zero_tol is non-negative.
 * @throws std::exception when the pre-conditions are not satisfied.
 * @note We only use the lower triangular part of Y.
 */
Eigen::MatrixXd DecomposePSDmatrixIntoXtransposeTimesX(
    const Eigen::Ref<const Eigen::MatrixXd>& Y, double zero_tol,
    bool return_empty_if_not_psd = false);

/**
 * Rewrite a quadratic form xᵀQx + bᵀx + c to
 * (Rx+d)ᵀ(Rx+d)
 * where
 * <pre>
 * RᵀR = Q
 * Rᵀd = b / 2
 * dᵀd = c
 * </pre>
 *
 * This decomposition requires the matrix
 * <pre>
 * ⌈Q     b/2⌉
 * ⌊bᵀ/2    c⌋
 * </pre>
 * to be positive semidefinite.
 *
 * We return R and d with the minimal number of rows, namely the rows of R
 * and d equal to the rank of the matrix
 * <pre>
 * ⌈Q     b/2⌉
 * ⌊bᵀ/2    c⌋
 * </pre>
 *
 * Notice that R might have more rows than Q, For example, the quadratic
 * expression x² + 2x + 5 =(x+1)² + 2², it can be decomposed as
 * <pre>
 * ⎛⌈1⌉ * x + ⌈1⌉⎞ᵀ * ⎛⌈1⌉ * x + ⌈1⌉⎞
 * ⎝⌊0⌋       ⌊2⌋⎠    ⎝⌊0⌋       ⌊2⌋⎠
 * </pre>
 * Here R has 2 rows while Q only has 1 row.
 *
 * On the other hand the quadratic expression x² + 2x + 1 can be decomposed
 * as (x+1) * (x+1), where R has 1 row, same as Q.
 *
 * Also notice that this decomposition is not unique. For example, with any
 * permutation matrix P, we can define
 * <pre>
 * R₁ = P*R
 * d₁ = P*d
 * </pre>
 * Then (R₁*x+d₁)ᵀ(R₁*x+d₁) gives the same quadratic form.
 * @param Q The square matrix.
 * @param b The vector containing the linear coefficients.
 * @param c The constant term.
 * @param tol We will determine if this quadratic form is always non-negative,
 * by checking the Eigen values of the matrix
 * [Q    b/2]
 * [bᵀ/2   c]
 * are all greater than -tol. @default is 0.
 * @retval (R, d). R and d have the same number of rows. R.cols() == x.rows().
 * R.rows() equals to the rank of the matrix
 * <pre>
 *    [Q    b/2]
 *    [bᵀ/2   c]
 * </pre>
 * @pre 1. The quadratic form is always non-negative, namely the matrix
 *         <pre>
 *         [Q    b/2]
 *         [bᵀ/2   c]
 *         </pre>
 *         is positive semidefinite.
 *      2. `Q` and `b` are of the correct size.
 *      3. `tol` is non-negative.
 * @throws std::exception if the precondition is not satisfied.
 */
std::pair<Eigen::MatrixXd, Eigen::MatrixXd> DecomposePositiveQuadraticForm(
    const Eigen::Ref<const Eigen::MatrixXd>& Q,
    const Eigen::Ref<const Eigen::VectorXd>& b, double c, double tol = 0);

/** Given two quadratic forms, x'Sx > 0 and x'Px, (with P symmetric and full
 * rank), finds a change of variables x = Ty, which simultaneously diagonalizes
 * both forms (as inspired by "balanced truncation" in model-order reduction
 * [1]).  In this note, we use abs(M) to indicate the elementwise absolute
 * value.
 *
 * Adapting from [1], we observe that there is a family of coordinate systems
 * that can simultaneously diagonalize T'ST and T'PT.  Using D to denote a
 * diagonal matrix, we call the result S-normal if T'ST = I and abs(T'PT) = D⁻²,
 * call it P-normal if T'ST = D² and abs(T'PT) = I, and call it "balanced" if
 * T'ST = D and abs(T'PT) = D⁻¹. Note that if P > 0, then T'PT = D⁻¹.
 *
 * We find x=Ty such that T'ST = D and abs(T'PT) = D⁻¹, where D is diagonal. The
 * recipe is:
 * - Factorize S = LL', and choose R=L⁻¹.
 * - Take svd(RPR') = UΣV', and note that U=V for positive definite matrices,
 *     and V is U up to a sign flip of the singular vectors for all symmetric
 *     matrices.
 * - Choose T = R'U Σ^{-1/4}, where the matrix exponent can be taken elementwise
 *   because Σ is diagonal. This gives T'ST = Σ^{-1/2} (by using U'U=I), and
 *   abs(T'PT) = Σ^{1/2}.  If P > 0, then T'PT = Σ^{1/2}.
 *
 * Note that the numerical "balancing" can address the absolute scaling of the
 * quadratic forms, but not the relative scaling.  To understand this, consider
 * the scalar case: we have two quadratic functions, sx² and px², with s>0, p>0.
 * We'd like to choose x=Ty so that sT²y² and pT²y² are "balanced" (we'd like
 * them both to be close to y²).  We'll choose T=p^{-1/4}s^{-1/4}, which gives
 * sx² = sqrt(s/p)y², and px² = sqrt(p/s)y². For instance if s=1e8 and p=1e8,
 * then t=1e-4 and st^2 = pt^2 = 1.  But if s=10, p=1e7, then t=0.01, and st^2 =
 * 1e-3, pt^2 = 1e3.
 *
 * In the matrix case, the absolute scaling is important -- it ensures that the
 * two quadratic forms have the same matrix condition number and makes them as
 * close as possible to 1. Besides absolute scaling, in the matrix case the
 * balancing transform diagonalizes both quadratic forms.
 *
 * [1] B. Moore, “Principal component analysis in linear systems:
 * Controllability, observability, and model reduction,” IEEE Trans. Automat.
 * Contr., vol. 26, no. 1, pp. 17–32, Feb. 1981.
 */
Eigen::MatrixXd BalanceQuadraticForms(
    const Eigen::Ref<const Eigen::MatrixXd>& S,
    const Eigen::Ref<const Eigen::MatrixXd>& P);

}  // namespace math
}  // namespace drake
