#ifndef STAN_MODEL_TEST_GRADIENTS_HPP
#define STAN_MODEL_TEST_GRADIENTS_HPP

#include <stan/callbacks/writer.hpp>
#include <stan/model/finite_diff_grad.hpp>
#include <stan/model/log_prob_grad.hpp>
#include <cmath>
#include <sstream>
#include <vector>

namespace stan {
  namespace model {

    /**
     * Test the log_prob_grad() function's ability to produce
     * accurate gradients using finite differences.  This shouldn't
     * be necessary when using autodiff, but is useful for finding
     * bugs in hand-written code (or var).
     *
     * @tparam propto True if calculation is up to proportion
     * (double-only terms dropped).
     * @tparam jacobian_adjust_transform True if the log absolute
     * Jacobian determinant of inverse parameter transforms is added to the
     * log probability.
     * @tparam Model Class of model.
     * @param[in] model Model.
     * @param[in] params_r Real-valued parameter vector.
     * @param[in] params_i Integer-valued parameter vector.
     * @param[in] epsilon Real-valued scalar saying how much to perturb.
     *   Reasonable value is 1e-6.
     * @param[in] error Real-valued scalar saying how much error to allow.
     *   Reasonable value is 1e-6.
     * @param[in,out] interrupt callback to be called at every iteration
     * @param[in,out] writer Writer for messages
     * @return number of failed gradient comparisons versus allowed
     * error, so 0 if all gradients pass
     */
    template <bool propto, bool jacobian_adjust_transform, class Model>
    int test_gradients(const Model& model,
                       std::vector<double>& params_r,
                       std::vector<int>& params_i,
                       double epsilon,
                       double error,
                       stan::callbacks::interrupt& interrupt,
                       stan::callbacks::writer& writer) {
      std::stringstream msg;
      std::vector<double> grad;
      double lp = log_prob_grad<propto, jacobian_adjust_transform>(model,
                                                                   params_r,
                                                                   params_i,
                                                                   grad,
                                                                   &msg);
      if (msg.str().length() > 0)
        writer(msg.str());

      std::vector<double> grad_fd;
      finite_diff_grad<false, true, Model>(model, interrupt, params_r, params_i,
                                           grad_fd, epsilon, &msg);
      if (msg.str().length() > 0)
        writer(msg.str());

      int num_failed = 0;

      std::stringstream lp_msg;
      lp_msg << " Log probability=" << lp;

      writer();
      writer(lp_msg.str());
      writer();

      std::stringstream header;
      header << std::setw(10) << "param idx"
             << std::setw(16) << "value"
             << std::setw(16) << "model"
             << std::setw(16) << "finite diff"
             << std::setw(16) << "error";

      writer(header.str());

      for (size_t k = 0; k < params_r.size(); k++) {
        std::stringstream line;
        line << std::setw(10) << k
             << std::setw(16) << params_r[k]
             << std::setw(16) << grad[k]
             << std::setw(16) << grad_fd[k]
             << std::setw(16) << (grad[k] - grad_fd[k]);
        writer(line.str());
        if (std::fabs(grad[k] - grad_fd[k]) > error)
          num_failed++;
      }
      return num_failed;
    }

  }
}
#endif
