#ifndef STAN_MATH_REV_CORE_VAR_HPP
#define STAN_MATH_REV_CORE_VAR_HPP

#include <stan/math/rev/core/vari.hpp>
#include <stan/math/rev/core/grad.hpp>
#include <stan/math/rev/core/chainable_alloc.hpp>
#include <boost/math/tools/config.hpp>
#include <ostream>
#include <vector>
#include <complex>
#include <string>
#include <exception>

namespace stan {
namespace math {

// forward declare
static void grad(vari* vi);

/**
 * Independent (input) and dependent (output) variables for gradients.
 *
 * This class acts as a smart pointer, with resources managed by
 * an arena-based memory manager scoped to a single gradient
 * calculation.
 *
 * An var is constructed with a double and used like any
 * other scalar.  Arithmetical functions like negation, addition,
 * and subtraction, as well as a range of mathematical functions
 * like exponentiation and powers are overridden to operate on
 * var values objects.
 */
class var {
 public:
  // FIXME: doc what this is for
  using Scalar = double;

  /**
   * Pointer to the implementation of this variable.
   *
   * This value should not be modified, but may be accessed in
   * <code>var</code> operators to construct <code>vari</code>
   * instances.
   */
  vari* vi_;

  /**
   * Return <code>true</code> if this variable has been
   * declared, but not been defined.  Any attempt to use an
   * undefined variable's value or adjoint will result in a
   * segmentation fault.
   *
   * @return <code>true</code> if this variable does not yet have
   * a defined variable.
   */
  bool is_uninitialized() { return (vi_ == static_cast<vari*>(nullptr)); }

  /**
   * Construct a variable for later assignment.
   *
   * This is implemented as a no-op, leaving the underlying implementation
   * dangling.  Before an assignment, the behavior is thus undefined just
   * as for a basic double.
   */
  var() : vi_(static_cast<vari*>(nullptr)) {}

  /**
   * Construct a variable from a pointer to a variable implementation.
   *
   * @param vi Variable implementation.
   */
  var(vari* vi) : vi_(vi) {}  // NOLINT

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  var(float x) : vi_(new vari(static_cast<double>(x))) {}  // NOLINT

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument as
   * a value and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  var(double x) : vi_(new vari(x)) {}  // NOLINT

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  var(long double x) : vi_(new vari(x)) {}  // NOLINT

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  var(bool x) : vi_(new vari(static_cast<double>(x))) {}  // NOLINT

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  var(char x) : vi_(new vari(static_cast<double>(x))) {}  // NOLINT

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  var(short x) : vi_(new vari(static_cast<double>(x))) {}  // NOLINT

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  var(int x) : vi_(new vari(static_cast<double>(x))) {}  // NOLINT

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  var(long x) : vi_(new vari(static_cast<double>(x))) {}  // NOLINT

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  var(unsigned char x)  // NOLINT(runtime/explicit)
      : vi_(new vari(static_cast<double>(x))) {}

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  // NOLINTNEXTLINE
  var(unsigned short x) : vi_(new vari(static_cast<double>(x))) {}

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  var(unsigned int x) : vi_(new vari(static_cast<double>(x))) {}  // NOLINT

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  // NOLINTNEXTLINE
  var(unsigned long x) : vi_(new vari(static_cast<double>(x))) {}  // NOLINT

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint. Only works
   * if the imaginary part is zero.
   *
   * @param x Value of the variable.
   */
  explicit var(const std::complex<double>& x) {
    if (imag(x) == 0) {
      vi_ = new vari(real(x));
    } else {
      std::stringstream ss;
      ss << "Imaginary part of std::complex used to construct var"
         << " must be zero. Found real part = " << real(x) << " and "
         << " found imaginary part = " << imag(x) << std::endl;
      std::string msg = ss.str();
      throw std::invalid_argument(msg);
    }
  }

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint. Only works
   * if the imaginary part is zero.
   *
   * @param x Value of the variable.
   */
  explicit var(const std::complex<float>& x) {
    if (imag(x) == 0) {
      vi_ = new vari(static_cast<double>(real(x)));
    } else {
      std::stringstream ss;
      ss << "Imaginary part of std::complex used to construct var"
         << " must be zero. Found real part = " << real(x) << " and "
         << " found imaginary part = " << imag(x) << std::endl;
      std::string msg = ss.str();
      throw std::invalid_argument(msg);
    }
  }

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint. Only works
   * if the imaginary part is zero.
   *
   * @param x Value of the variable.
   */
  explicit var(const std::complex<long double>& x) {
    if (imag(x) == 0) {
      vi_ = new vari(static_cast<double>(real(x)));
    } else {
      std::stringstream ss;
      ss << "Imaginary part of std::complex used to construct var"
         << " must be zero. Found real part = " << real(x) << " and "
         << " found imaginary part = " << imag(x) << std::endl;
      std::string msg = ss.str();
      throw std::invalid_argument(msg);
    }
  }

#ifdef _WIN64

  // these two ctors are for Win64 to enable 64-bit signed
  // and unsigned integers, because long and unsigned long
  // are still 32-bit

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  var(size_t x) : vi_(new vari(static_cast<double>(x))) {}  // NOLINT

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  var(ptrdiff_t x) : vi_(new vari(static_cast<double>(x))) {}  // NOLINT
#endif

#ifdef BOOST_MATH_USE_FLOAT128

  // this ctor is for later GCCs that have the __float128
  // type enabled, because it gets enabled by boost

  /**
   * Construct a variable from the specified arithmetic argument
   * by constructing a new <code>vari</code> with the argument
   * cast to <code>double</code>, and a zero adjoint.
   *
   * @param x Value of the variable.
   */
  var(__float128 x) : vi_(new vari(static_cast<double>(x))) {}  // NOLINT

#endif

  /**
   * Return the value of this variable.
   *
   * @return The value of this variable.
   */
  inline double val() const { return vi_->val_; }

  /**
   * Return the derivative of the root expression with
   * respect to this expression.  This method only works
   * after one of the <code>grad()</code> methods has been
   * called.
   *
   * @return Adjoint for this variable.
   */
  inline double adj() const { return vi_->adj_; }

  /**
   * Compute the gradient of this (dependent) variable with respect to
   * the specified vector of (independent) variables, assigning the
   * specified vector to the gradient.
   *
   * The grad() function does <i>not</i> recover memory.  In Stan
   * 2.4 and earlier, this function did recover memory.
   *
   * @param x Vector of independent variables.
   * @param g Gradient vector of partial derivatives of this
   * variable with respect to x.
   */
  void grad(std::vector<var>& x, std::vector<double>& g) {
    stan::math::grad(vi_);
    g.resize(x.size());
    for (size_t i = 0; i < x.size(); ++i) {
      g[i] = x[i].vi_->adj_;
    }
  }

  /**
   * Compute the gradient of this (dependent) variable with respect
   * to all (independent) variables.
   *
   * The grad() function does <i>not</i> recover memory.
   */
  void grad() { stan::math::grad(vi_); }

  // POINTER OVERRIDES

  /**
   * Return a reference to underlying implementation of this variable.
   *
   * If <code>x</code> is of type <code>var</code>, then applying
   * this operator, <code>*x</code>, has the same behavior as
   * <code>*(x.vi_)</code>.
   *
   * <i>Warning</i>:  The returned reference does not track changes to
   * this variable.
   *
   * @return variable
   */
  inline vari& operator*() { return *vi_; }

  /**
   * Return a pointer to the underlying implementation of this variable.
   *
   * If <code>x</code> is of type <code>var</code>, then applying
   * this operator, <code>x-&gt;</code>, behaves the same way as
   * <code>x.vi_-&gt;</code>.
   *
   * <i>Warning</i>: The returned result does not track changes to
   * this variable.
   */
  inline vari* operator->() { return vi_; }

  // COMPOUND ASSIGNMENT OPERATORS

  /**
   * The compound add/assignment operator for variables (C++).
   *
   * If this variable is a and the argument is the variable b,
   * then (a += b) behaves exactly the same way as (a = a + b),
   * creating an intermediate variable representing (a + b).
   *
   * @param b The variable to add to this variable.
   * @return The result of adding the specified variable to this variable.
   */
  inline var& operator+=(const var& b);

  /**
   * The compound add/assignment operator for scalars (C++).
   *
   * If this variable is a and the argument is the scalar b, then
   * (a += b) behaves exactly the same way as (a = a + b).  Note
   * that the result is an assignable lvalue.
   *
   * @param b The scalar to add to this variable.
   * @return The result of adding the specified variable to this variable.
   */
  inline var& operator+=(double b);

  /**
   * The compound subtract/assignment operator for variables (C++).
   *
   * If this variable is a and the argument is the variable b,
   * then (a -= b) behaves exactly the same way as (a = a - b).
   * Note that the result is an assignable lvalue.
   *
   * @param b The variable to subtract from this variable.
   * @return The result of subtracting the specified variable from
   * this variable.
   */
  inline var& operator-=(const var& b);

  /**
   * The compound subtract/assignment operator for scalars (C++).
   *
   * If this variable is a and the argument is the scalar b, then
   * (a -= b) behaves exactly the same way as (a = a - b).  Note
   * that the result is an assignable lvalue.
   *
   * @param b The scalar to subtract from this variable.
   * @return The result of subtracting the specified variable from this
   * variable.
   */
  inline var& operator-=(double b);

  /**
   * The compound multiply/assignment operator for variables (C++).
   *
   * If this variable is a and the argument is the variable b,
   * then (a *= b) behaves exactly the same way as (a = a * b).
   * Note that the result is an assignable lvalue.
   *
   * @param b The variable to multiply this variable by.
   * @return The result of multiplying this variable by the
   * specified variable.
   */
  inline var& operator*=(const var& b);

  /**
   * The compound multiply/assignment operator for scalars (C++).
   *
   * If this variable is a and the argument is the scalar b, then
   * (a *= b) behaves exactly the same way as (a = a * b).  Note
   * that the result is an assignable lvalue.
   *
   * @param b The scalar to multiply this variable by.
   * @return The result of multplying this variable by the specified
   * variable.
   */
  inline var& operator*=(double b);

  /**
   * The compound divide/assignment operator for variables (C++).  If this
   * variable is a and the argument is the variable b, then (a /= b)
   * behaves exactly the same way as (a = a / b).  Note that the
   * result is an assignable lvalue.
   *
   * @param b The variable to divide this variable by.
   * @return The result of dividing this variable by the
   * specified variable.
   */
  inline var& operator/=(const var& b);

  /**
   * The compound divide/assignment operator for scalars (C++).
   *
   * If this variable is a and the argument is the scalar b, then
   * (a /= b) behaves exactly the same way as (a = a / b).  Note
   * that the result is an assignable lvalue.
   *
   * @param b The scalar to divide this variable by.
   * @return The result of dividing this variable by the specified
   * variable.
   */
  inline var& operator/=(double b);

  /**
   * Write the value of this auto-dif variable and its adjoint to
   * the specified output stream.
   *
   * @param os Output stream to which to write.
   * @param v Variable to write.
   * @return Reference to the specified output stream.
   */
  friend std::ostream& operator<<(std::ostream& os, const var& v) {
    if (v.vi_ == nullptr) {
      return os << "uninitialized";
    }
    return os << v.val();
  }
};

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