#ifndef STAN_MATH_FWD_CORE_FVAR_HPP
#define STAN_MATH_FWD_CORE_FVAR_HPP

#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/fun/is_nan.hpp>
#include <ostream>
#include <type_traits>

namespace stan {
namespace math {

/**
 * This template class represents scalars used in forward-mode
 * automatic differentiation, which consist of values and directional
 * derivatives of the specified template type.  When performing
 * operations on instances of this class, all operands should be
 * either primitive integer or double values or dual numbers
 * representing derivatives in the same direction.  The typical use
 * case is to have a unit length directional derivative in the
 * direction of a single independent variable.
 *
 * By using reverse-mode automatic derivative variables, second-order
 * derivatives may be calculated.  By using fvar&lt;var&gt;
 * instances, third-order derivatives may be calculated.  These are
 * called mixed-mode automatic differentiation variable in Stan.
 *
 * Specialized functionals that perform differentiation on
 * functors may be found in the matrix subdirectories of the
 * reverse, forward, and mixed-mode directories.
 *
 * The <a
 * href="https://en.wikipedia.org/wiki/Automatic_differentiation">Wikipedia
 * page on automatic differentiation</a> describes how forward-mode
 * automatic differentiation works mathematically in terms of dual
 * numbers.
 *
 * @tparam T type of value and tangent
 */
template <typename T>
struct fvar {
  /**
   * The type of values and tangents.
   */
  using Scalar = std::decay_t<T>;

  /**
   * The value of this variable.
   */
  Scalar val_;

  /**
   * Return the value of this variable.
   *
   * @return value of this variable
   */
  Scalar val() const { return val_; }

  /**
   * The tangent (derivative) of this variable.
   */
  Scalar d_;

  /**
   * Return the tangent (derivative) of this variable.
   *
   * @return tangent of this variable
   */
  Scalar d() const { return d_; }

  /**
   * Construct a forward variable with zero value and tangent.
   */
  fvar() : val_(0.0), d_(0.0) {}  // NOLINT

  /**
   * Construct a forward variable with the specified value and
   * zero tangent.
   *
   * @tparam V type of value (must be assignable to T)
   * @param[in] v value
   */
  template <typename V, std::enable_if_t<ad_promotable<V, T>::value>* = nullptr,
            require_not_same_t<V, fvar<T>>* = nullptr>
  fvar(const V& v) : val_(v), d_(0) {}  // NOLINT(runtime/explicit)

  /**
   * Construct a forward variable with the specified value and
   * tangent.
   *
   * @tparam V type of value (must be assignable to class type T)
   * @tparam D type of tangent (must be assignable class type T)
   * @param[in] v value
   * @param[in] d tangent
   */
  template <typename V, typename D>
  fvar(const V& v, const D& d) : val_(v), d_(d) {}

  /**
   * Add the specified variable to this variable and return a
   * reference to this variable.
   *
   * @param[in] x2 variable to add
   * @return reference to this variable after addition
   */
  inline fvar<T>& operator+=(const fvar<T>& x2) {
    val_ += x2.val_;
    d_ += x2.d_;
    return *this;
  }

  /**
   * Add the specified value to this variable and return a
   * reference to this variable.
   *
   * @param[in] x2 value to add
   * @return reference to this variable after addition
   */
  inline fvar<T>& operator+=(double x2) {
    val_ += x2;
    return *this;
  }

  /**
   * Subtract the specified variable from this variable and return a
   * reference to this variable.
   *
   * @param[in] x2 variable to subtract
   * @return reference to this variable after subtraction
   */
  inline fvar<T>& operator-=(const fvar<T>& x2) {
    val_ -= x2.val_;
    d_ -= x2.d_;
    return *this;
  }

  /**
   * Subtract the specified value from this variable and return a
   * reference to this variable.
   *
   * @param[in] x2 value to add
   * @return reference to this variable after subtraction
   */
  inline fvar<T>& operator-=(double x2) {
    val_ -= x2;
    return *this;
  }

  /**
   * Multiply this variable by the the specified variable and
   * return a reference to this variable.
   *
   * @param[in] x2 variable to multiply
   * @return reference to this variable after multiplication
   */
  inline fvar<T>& operator*=(const fvar<T>& x2) {
    d_ = d_ * x2.val_ + val_ * x2.d_;
    val_ *= x2.val_;
    return *this;
  }

  /**
   * Multiply this variable by the the specified value and
   * return a reference to this variable.
   *
   * @param[in] x2 value to multiply
   * @return reference to this variable after multiplication
   */
  inline fvar<T>& operator*=(double x2) {
    val_ *= x2;
    d_ *= x2;
    return *this;
  }

  /**
   * Divide this variable by the the specified variable and
   * return a reference to this variable.
   *
   * @param[in] x2 variable to divide this variable by
   * @return reference to this variable after division
   */
  inline fvar<T>& operator/=(const fvar<T>& x2) {
    d_ = (d_ * x2.val_ - val_ * x2.d_) / (x2.val_ * x2.val_);
    val_ /= x2.val_;
    return *this;
  }

  /**
   * Divide this value by the the specified variable and
   * return a reference to this variable.
   *
   * @param[in] x2 value to divide this variable by
   * @return reference to this variable after division
   */
  inline fvar<T>& operator/=(double x2) {
    val_ /= x2;
    d_ /= x2;
    return *this;
  }

  /**
   * Increment this variable by one and return a reference to this
   * variable after the increment.
   *
   * @return reference to this variable after increment
   */
  inline fvar<T>& operator++() {
    ++val_;
    return *this;
  }

  /**
   * Increment this variable by one and return a reference to a
   * copy of this variable before it was incremented.
   *
   * @return reference to copy of this variable before increment
   */
  inline fvar<T> operator++(int /*dummy*/) {
    fvar<T> result(val_, d_);
    ++val_;
    return result;
  }

  /**
   * Decrement this variable by one and return a reference to this
   * variable after the decrement.
   *
   * @return reference to this variable after decrement
   */
  inline fvar<T>& operator--() {
    --val_;
    return *this;
  }

  /**
   * Decrement this variable by one and return a reference to a
   * copy of this variable before it was decremented.
   *
   * @return reference to copy of this variable before decrement
   */
  inline fvar<T> operator--(int /*dummy*/) {
    fvar<T> result(val_, d_);
    --val_;
    return result;
  }

  /**
   * Write the value of the specified variable to the specified
   * output stream, returning a reference to the output stream.
   *
   * @param[in,out] os stream for writing value
   * @param[in] v variable whose value is written
   * @return reference to the specified output stream
   */
  friend std::ostream& operator<<(std::ostream& os, const fvar<T>& v) {
    return os << v.val_;
  }
};

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