#ifndef STAN_MATH_PRIM_MAT_FUN_MULTIPLY_HPP
#define STAN_MATH_PRIM_MAT_FUN_MULTIPLY_HPP

#include <stan/math/prim/mat/fun/Eigen.hpp>
#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/arr/err/check_matching_sizes.hpp>
#include <stan/math/prim/mat/err/check_multiplicable.hpp>
#ifdef STAN_OPENCL
#include <stan/math/opencl/opencl.hpp>
#endif
#include <type_traits>

namespace stan {
namespace math {

/**
 * Return specified matrix multiplied by specified scalar.
 * @tparam R Row type for matrix.
 * @tparam C Column type for matrix.
 * @param m Matrix.
 * @param c Scalar.
 * @return Product of matrix and scalar.
 */
template <int R, int C, typename T1, typename T2,
          typename = enable_if_all_arithmetic<T1, T2>>
inline Eigen::Matrix<return_type_t<T1, T2>, R, C> multiply(
    const Eigen::Matrix<T1, R, C>& m, T2 c) {
  return c * m;
}

/**
 * Return specified scalar multiplied by specified matrix.
 * @tparam R Row type for matrix.
 * @tparam C Column type for matrix.
 * @param c Scalar.
 * @param m Matrix.
 * @return Product of scalar and matrix.
 */
template <int R, int C, typename T1, typename T2,
          typename = enable_if_all_arithmetic<T1, T2>>
inline Eigen::Matrix<return_type_t<T1, T2>, R, C> multiply(
    T1 c, const Eigen::Matrix<T2, R, C>& m) {
  return c * m;
}

/**
 * Return the product of the specified matrices.  The number of
 * columns in the first matrix must be the same as the number of rows
 * in the second matrix.
 * @param m1 First matrix.
 * @param m2 Second matrix.
 * @return The product of the first and second matrices.
 * @throw std::domain_error if the number of columns of m1 does not match
 *   the number of rows of m2.
 */
template <int R1, int C1, int R2, int C2, typename T1, typename T2,
          typename = enable_if_all_arithmetic<T1, T2>>
inline Eigen::Matrix<return_type_t<T1, T2>, R1, C2> multiply(
    const Eigen::Matrix<T1, R1, C1>& m1, const Eigen::Matrix<T2, R2, C2>& m2) {
  check_multiplicable("multiply", "m1", m1, "m2", m2);
#ifdef STAN_OPENCL
  if (m1.rows() * m1.cols() * m2.cols()
      > opencl_context.tuning_opts().multiply_dim_prod_worth_transfer) {
    matrix_cl<double> m1_cl(m1);
    matrix_cl<double> m2_cl(m2);
    matrix_cl<double> m3_cl = m1_cl * m2_cl;
    return from_matrix_cl(m3_cl);
  } else {
    return m1 * m2;
  }
#else
  return m1 * m2;
#endif
}

/**
 * Return the scalar product of the specified row vector and
 * specified column vector.  The return is the same as the dot
 * product.  The two vectors must be the same size.
 * @param rv Row vector.
 * @param v Column vector.
 * @return Scalar result of multiplying row vector by column vector.
 * @throw std::domain_error if rv and v are not the same size.
 */
template <int C1, int R2, typename T1, typename T2,
          typename = enable_if_all_arithmetic<T1, T2>>
inline return_type_t<T1, T2> multiply(const Eigen::Matrix<T1, 1, C1>& rv,
                                      const Eigen::Matrix<T2, R2, 1>& v) {
  check_matching_sizes("multiply", "rv", rv, "v", v);
  return rv.dot(v);
}

/**
 * Return specified matrix multiplied by specified scalar.
 * @tparam R Row type for matrix.
 * @tparam C Column type for matrix.
 * @param m Matrix.
 * @param c Scalar.
 * @return Product of matrix and scalar.
 */
template <typename T1, typename T2, typename = enable_if_all_arithmetic<T1, T2>>
inline return_type_t<T1, T2> multiply(T1 m, T2 c) {
  return c * m;
}

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