#include <stan/math/prim.hpp>
#include <gtest/gtest.h>

stan::math::matrix_d generate_large_L_tri_mat() {
  stan::math::matrix_d x;
  double vals[10000];

  vals[0] = 0.1;
  for (int i = 1; i < 10000; ++i)
    vals[i] = vals[i - 1] + 0.1123456;

  x = Eigen::Map<Eigen::Matrix<double, 100, 100> >(vals);
  x *= 1e10;

  return x;
}

void test_multiply_lower_tri_self_transpose(const stan::math::matrix_d& x) {
  using stan::math::multiply_lower_tri_self_transpose;
  stan::math::matrix_d y = multiply_lower_tri_self_transpose(x);
  stan::math::matrix_d xp = x;
  for (int m = 0; m < xp.rows(); ++m)
    for (int n = m + 1; n < xp.cols(); ++n)
      xp(m, n) = 0;

  stan::math::matrix_d xxt = xp * xp.transpose();
  EXPECT_EQ(y.rows(), xxt.rows());
  EXPECT_EQ(y.cols(), xxt.cols());
  for (int m = 0; m < y.rows(); ++m)
    for (int n = 0; n < y.cols(); ++n)
      EXPECT_FLOAT_EQ(xxt(m, n), y(m, n));
}

TEST(MathMatrixPrimMat, multiply_lower_tri_self_transpose) {
  using stan::math::check_symmetric;
  using stan::math::multiply_lower_tri_self_transpose;
  static const char* function
      = "stan::math::multiply_lower_tri_self_transpose(%1%)";
  stan::math::matrix_d x;
  test_multiply_lower_tri_self_transpose(x);

  x = stan::math::matrix_d(1, 1);
  x << 3.0;
  test_multiply_lower_tri_self_transpose(x);

  x = stan::math::matrix_d(2, 2);
  x << 1.0, 0.0, 2.0, 3.0;
  test_multiply_lower_tri_self_transpose(x);

  x = stan::math::matrix_d(3, 3);
  x << 1.0, 0.0, 0.0, 2.0, 3.0, 0.0, 4.0, 5.0, 6.0;
  test_multiply_lower_tri_self_transpose(x);

  x = stan::math::matrix_d(3, 3);
  x << 1.0, 0.0, 100000.0, 2.0, 3.0, 0.0, 4.0, 5.0, 6.0;
  test_multiply_lower_tri_self_transpose(x);

  x = stan::math::matrix_d(3, 2);
  x << 1.0, 0.0, 2.0, 3.0, 4.0, 5.0;
  test_multiply_lower_tri_self_transpose(x);

  x = stan::math::matrix_d(2, 3);
  x << 1.0, 0.0, 0.0, 2.0, 3.0, 0.0;
  test_multiply_lower_tri_self_transpose(x);

  x = generate_large_L_tri_mat();
  EXPECT_NO_THROW(check_symmetric(function, "Symmetric matrix",
                                  multiply_lower_tri_self_transpose(x)));
}
