#include <stan/math/prim.hpp>
#include <gtest/gtest.h>
#include <test/unit/util.hpp>
#include <limits>
#include <string>

class ErrorHandlingMatrixPosDef : public ::testing::Test {
 public:
  const char* function = "function";
  void SetUp() {}

  Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> y;
};

TEST_F(ErrorHandlingMatrixPosDef, checkPosDefinite) {
  using stan::math::check_pos_definite;
  y.resize(1, 1);
  y << 1;
  EXPECT_NO_THROW(check_pos_definite(function, "y", y));

  Eigen::LLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > llt_1(y);
  EXPECT_NO_THROW(check_pos_definite(function, "y", llt_1));

  Eigen::LDLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > ldlt_1
      = y.ldlt();
  EXPECT_NO_THROW(check_pos_definite(function, "y", ldlt_1));

  y.resize(3, 3);
  y << 1, 0, 0, 0, 1, 0, 0, 0, 1;
  EXPECT_NO_THROW(check_pos_definite(function, "y", y));

  Eigen::LLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > llt_2(y);
  EXPECT_NO_THROW(check_pos_definite(function, "y", llt_2));

  Eigen::LDLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > ldlt_2
      = y.ldlt();
  EXPECT_NO_THROW(check_pos_definite(function, "y", ldlt_2));
}

TEST_F(ErrorHandlingMatrixPosDef, checkPosDefinite_not_square) {
  using stan::math::check_pos_definite;
  std::stringstream expected_msg;

  y.resize(3, 4);
  expected_msg << "Expecting a square matrix; rows of y (3) and columns of "
               << "y (4) must match in size";
  EXPECT_THROW_MSG(check_pos_definite(function, "y", y), std::invalid_argument,
                   expected_msg.str());
}

TEST_F(ErrorHandlingMatrixPosDef, checkPosDefinite_0_size) {
  using stan::math::check_pos_definite;
  std::string expected_msg;

  expected_msg
      = "y must have a positive size, but is 0; "
        "dimension size expression = rows";
  EXPECT_THROW_MSG(check_pos_definite(function, "y", y), std::invalid_argument,
                   expected_msg);
  Eigen::MatrixXd x;
  x.resize(0, 0);
  Eigen::LLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > llt(
      x.rows());
  llt.compute(x);
  EXPECT_NO_THROW(check_pos_definite(function, "x", llt));
}

TEST_F(ErrorHandlingMatrixPosDef, checkPosDefinite_non_symmetric) {
  using stan::math::check_pos_definite;
  std::string expected_msg;

  y.resize(3, 3);
  y << 1, 0, 0, 0, 1, 0.5, 0, 0, 1;

  expected_msg = "y is not symmetric. y[2,3] = 0.5, but y[3,2] = 0";
  EXPECT_THROW_MSG(check_pos_definite(function, "y", y), std::domain_error,
                   expected_msg);

  Eigen::LLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > llt(y);
  EXPECT_NO_THROW(check_pos_definite(function, "y", llt));

  Eigen::LDLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > ldlt
      = y.ldlt();
  EXPECT_NO_THROW(check_pos_definite(function, "y", ldlt));
}

TEST_F(ErrorHandlingMatrixPosDef, checkPosDefinite_non_pos_definite) {
  using stan::math::check_pos_definite;
  std::stringstream expected_msg1_mat;
  std::stringstream expected_msg1_llt;
  std::stringstream expected_msg1_ldlt;
  std::stringstream expected_msg2_mat;
  std::stringstream expected_msg3_mat;
  std::stringstream expected_msg4;

  y.resize(3, 3);
  y << -1, 0, 0, 0, -1, 0, 0, 0, -1;

  expected_msg1_mat << "function: y is not positive definite.";
  EXPECT_THROW_MSG(check_pos_definite(function, "y", y), std::domain_error,
                   expected_msg1_mat.str());

  expected_msg1_llt << "function: Matrix y is not positive definite";
  Eigen::LLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > llt_err1(
      y);
  EXPECT_THROW_MSG(check_pos_definite(function, "y", llt_err1),
                   std::domain_error, expected_msg1_llt.str());
  expected_msg1_ldlt << "function: LDLT decomposition of y failed";
  Eigen::LDLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > ldlt_err1
      = y.ldlt();
  EXPECT_THROW_MSG(check_pos_definite(function, "y", ldlt_err1),
                   std::domain_error, expected_msg1_ldlt.str());
  y.resize(2, 2);
  y << 1, 2, 2, 1;
  expected_msg2_mat << "function: y is not positive definite.";
  EXPECT_THROW_MSG(check_pos_definite(function, "y", y), std::domain_error,
                   expected_msg2_mat.str());
  Eigen::LLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > llt_err2(
      y);
  EXPECT_THROW_MSG(check_pos_definite(function, "y", llt_err2),
                   std::domain_error, expected_msg1_llt.str());

  Eigen::LDLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > ldlt_err2
      = y.ldlt();
  EXPECT_THROW_MSG(check_pos_definite(function, "y", ldlt_err2),
                   std::domain_error, expected_msg1_ldlt.str());
  y << 1, 1, 1, 1;
  expected_msg3_mat << "function: y is not positive definite.";
  EXPECT_THROW_MSG(check_pos_definite(function, "y", y), std::domain_error,
                   expected_msg3_mat.str());

  Eigen::LLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > llt_err3(
      y);
  EXPECT_THROW_MSG(check_pos_definite(function, "y", llt_err3),
                   std::domain_error, expected_msg1_llt.str());
  Eigen::LDLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > ldlt_err3
      = y.ldlt();
  EXPECT_THROW_MSG(check_pos_definite(function, "y", ldlt_err3),
                   std::domain_error, expected_msg1_ldlt.str());
}

TEST_F(ErrorHandlingMatrixPosDef, checkPosDefinite_nan) {
  using stan::math::check_pos_definite;
  double nan = std::numeric_limits<double>::quiet_NaN();

  y.resize(1, 1);
  y << nan;

  EXPECT_THROW_MSG(check_pos_definite(function, "y", y), std::domain_error,
                   "y[1, 1] is nan");

  Eigen::LLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > llt_err1(
      y);
  EXPECT_THROW(check_pos_definite(function, "y", llt_err1), std::domain_error);

  Eigen::LDLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > ldlt_err1
      = y.ldlt();
  EXPECT_THROW(check_pos_definite(function, "y", ldlt_err1), std::domain_error);

  y.resize(3, 3);
  y << 2, -1, 0, -1, 2, -1, 0, -1, 2;
  EXPECT_NO_THROW(check_pos_definite(function, "y", y));
  for (int i = 0; i < y.rows(); i++)
    for (int j = 0; j < y.cols(); j++) {
      y << 2, -1, 0, -1, 2, -1, 0, -1, 2;
      y(i, j) = nan;
      if (i >= j) {
        // expected_msg.str("");
        // if (i == j)
        //   expected_msg << "function: y["
        //                << j*y.cols() + i + 1
        //                << "] is " << nan
        //                << ", but must not be nan!";
        // else
        //   expected_msg << "function: y is not symmetric. "
        //                << "y[" << j+1 << ", " << i+1 << "] = " << y(j, i)
        //                << ", but y[" << i+1 << ", " << j+1 << "] = "
        //                << y(i, j);
        EXPECT_THROW(check_pos_definite(function, "y", y),
                     // , expected_msg.str());
                     std::domain_error);
      }
    }

  y << 2, -1, nan, -1, 2, -1, nan, -1, nan;
  Eigen::LLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > llt_err2(
      y);
  EXPECT_THROW(check_pos_definite(function, "y", llt_err2), std::domain_error);

  Eigen::LDLT<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > ldlt_err2
      = y.ldlt();
  EXPECT_THROW(check_pos_definite(function, "y", ldlt_err2), std::domain_error);

  y << 0, 0, 0, 0, 0, 0, 0, 0, 0;
  EXPECT_THROW_MSG(check_pos_definite(function, "y", y), std::domain_error,
                   "function: y is not positive definite.");
}
