#ifndef STAN_MATH_PRIM_SCAL_PROB_NORMAL_SUFFICIENT_LPDF_HPP
#define STAN_MATH_PRIM_SCAL_PROB_NORMAL_SUFFICIENT_LPDF_HPP

#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/scal/prob/normal_lpdf.hpp>
#include <stan/math/prim/scal/err/check_consistent_sizes.hpp>
#include <stan/math/prim/scal/err/check_finite.hpp>
#include <stan/math/prim/scal/err/check_positive.hpp>
#include <stan/math/prim/scal/err/check_nonnegative.hpp>
#include <stan/math/prim/scal/fun/size_zero.hpp>
#include <stan/math/prim/scal/fun/constants.hpp>
#include <stan/math/prim/scal/fun/value_of.hpp>

namespace stan {
namespace math {

/**
 * The log of the normal density for the specified scalar(s) given
 * the specified mean(s) and deviation(s).
 * y, s_quared, mu, or sigma can each be either
 * a scalar, a std vector or Eigen vector.
 * n can be either a single int or an std vector of ints.
 * Any vector inputs must be the same length.
 *
 * <p>The result log probability is defined to be the sum of the
 * log probabilities for each observation/mean/deviation triple.
 *
 * @tparam T_y Type of sample average parameter.
 * @tparam T_s Type of sample squared errors parameter.
 * @tparam T_n Type of sample size parameter.
 * @tparam T_loc Type of location parameter.
 * @tparam T_scale Type of scale parameter.
 * @param y_bar (Sequence of) scalar(s) (sample average(s)).
 * @param s_squared (Sequence of) sum(s) of sample squared errors
 * @param n_obs (Sequence of) sample size(s)
 * @param mu (Sequence of) location parameter(s)
 * for the normal distribution.
 * @param sigma (Sequence of) scale parameters for the normal
 * distribution.
 * @return The log of the product of the densities.
 * @throw std::domain_error if either n or sigma are not positive,
 * if s_squared is negative or if any parameter is not finite.
 */
template <bool propto, typename T_y, typename T_s, typename T_n, typename T_loc,
          typename T_scale>
return_type_t<T_y, T_s, T_loc, T_scale> normal_sufficient_lpdf(
    const T_y& y_bar, const T_s& s_squared, const T_n& n_obs, const T_loc& mu,
    const T_scale& sigma) {
  static const char* function = "normal_sufficient_lpdf";
  using T_partials_return = partials_return_t<T_y, T_s, T_n, T_loc, T_scale>;

  using std::log;

  // check if any vectors are zero length
  if (size_zero(y_bar, s_squared, n_obs, mu, sigma)) {
    return 0.0;
  }

  // set up return value accumulator
  T_partials_return logp(0.0);

  // validate args (here done over var, which should be OK)
  check_finite(function, "Location parameter sufficient statistic", y_bar);
  check_finite(function, "Scale parameter sufficient statistic", s_squared);
  check_nonnegative(function, "Scale parameter sufficient statistic",
                    s_squared);
  check_finite(function, "Number of observations", n_obs);
  check_positive(function, "Number of observations", n_obs);
  check_finite(function, "Location parameter", mu);
  check_finite(function, "Scale parameter", sigma);
  check_positive(function, "Scale parameter", sigma);
  check_consistent_sizes(function, "Location parameter sufficient statistic",
                         y_bar, "Scale parameter sufficient statistic",
                         s_squared, "Number of observations", n_obs,
                         "Location parameter", mu, "Scale parameter", sigma);
  // check if no variables are involved and prop-to
  if (!include_summand<propto, T_y, T_s, T_loc, T_scale>::value) {
    return 0.0;
  }

  // set up template expressions wrapping scalars into vector views
  operands_and_partials<T_y, T_s, T_loc, T_scale> ops_partials(y_bar, s_squared,
                                                               mu, sigma);

  scalar_seq_view<const T_y> y_bar_vec(y_bar);
  scalar_seq_view<const T_s> s_squared_vec(s_squared);
  scalar_seq_view<const T_n> n_obs_vec(n_obs);
  scalar_seq_view<const T_loc> mu_vec(mu);
  scalar_seq_view<const T_scale> sigma_vec(sigma);
  size_t N = max_size(y_bar, s_squared, n_obs, mu, sigma);

  for (size_t i = 0; i < N; i++) {
    const T_partials_return y_bar_dbl = value_of(y_bar_vec[i]);
    const T_partials_return s_squared_dbl = value_of(s_squared_vec[i]);
    const T_partials_return n_obs_dbl = n_obs_vec[i];
    const T_partials_return mu_dbl = value_of(mu_vec[i]);
    const T_partials_return sigma_dbl = value_of(sigma_vec[i]);
    const T_partials_return sigma_squared = pow(sigma_dbl, 2);

    if (include_summand<propto>::value) {
      logp += NEG_LOG_SQRT_TWO_PI * n_obs_dbl;
    }

    if (include_summand<propto, T_scale>::value) {
      logp -= n_obs_dbl * log(sigma_dbl);
    }

    const T_partials_return cons_expr
        = (s_squared_dbl + n_obs_dbl * pow(y_bar_dbl - mu_dbl, 2));

    logp -= cons_expr / (2 * sigma_squared);

    // gradients
    if (!is_constant_all<T_y, T_loc>::value) {
      const T_partials_return common_derivative
          = n_obs_dbl * (mu_dbl - y_bar_dbl) / sigma_squared;
      if (!is_constant_all<T_y>::value) {
        ops_partials.edge1_.partials_[i] += common_derivative;
      }
      if (!is_constant_all<T_loc>::value) {
        ops_partials.edge3_.partials_[i] -= common_derivative;
      }
    }
    if (!is_constant_all<T_s>::value) {
      ops_partials.edge2_.partials_[i] -= 0.5 / sigma_squared;
    }
    if (!is_constant_all<T_scale>::value) {
      ops_partials.edge4_.partials_[i]
          += cons_expr / pow(sigma_dbl, 3) - n_obs_dbl / sigma_dbl;
    }
  }
  return ops_partials.build(logp);
}

template <typename T_y, typename T_s, typename T_n, typename T_loc,
          typename T_scale>
inline return_type_t<T_y, T_s, T_loc, T_scale> normal_sufficient_lpdf(
    const T_y& y_bar, const T_s& s_squared, const T_n& n_obs, const T_loc& mu,
    const T_scale& sigma) {
  return normal_sufficient_lpdf<false>(y_bar, s_squared, n_obs, mu, sigma);
}

}  // namespace math
}  // namespace stan
#endif
