#ifndef STAN_MATH_REV_SCAL_META_OPERANDS_AND_PARTIALS_HPP
#define STAN_MATH_REV_SCAL_META_OPERANDS_AND_PARTIALS_HPP

#include <stan/math/rev/core/chainablestack.hpp>
#include <stan/math/rev/core/precomputed_gradients.hpp>
#include <stan/math/rev/core/var.hpp>
#include <stan/math/rev/core/vari.hpp>
#include <stan/math/prim/scal/meta/operands_and_partials.hpp>
#include <stan/math/prim/scal/meta/is_vector_like.hpp>

namespace stan {
  namespace math {
    namespace detail {
      // ViewElt = double, Arg = var d_ holds double
      template <typename ViewElt>
      class ops_partials_edge<ViewElt, var>
        : public ops_partials_edge_singular<ViewElt, var> {
      public:
        ops_partials_edge(const var& op)
          : ops_partials_edge_singular<ViewElt, var>(op) {}
        void dump_partials(ViewElt* partials) {
          *partials = this->partials[0];
        }
        void dump_operands(vari** varis) {
          *varis = this->operand.vi_;
        }
      };
    } // end namespace detail
    template <typename Op1, typename Op2, typename Op3, typename Op4>
    class operands_and_partials<Op1, Op2, Op3, Op4, var> {
    public:
      // these are going to be stack local and get collected
      detail::ops_partials_edge<double, Op1> edge1_;
      detail::ops_partials_edge<double, Op2> edge2_;
      detail::ops_partials_edge<double, Op3> edge3_;
      detail::ops_partials_edge<double, Op4> edge4_;
      operands_and_partials(const Op1& o1)
        : edge1_(o1) { }
      operands_and_partials(const Op1& o1, const Op2& o2)
        : edge1_(o1), edge2_(o2) { }
      operands_and_partials(const Op1& o1, const Op2& o2, const Op3& o3)
        : edge1_(o1), edge2_(o2), edge3_(o3) { }
      operands_and_partials(const Op1& o1, const Op2& o2, const Op3& o3, const Op4& o4)
        : edge1_(o1), edge2_(o2), edge3_(o3), edge4_(o4) { }

      // this is what matters in terms of going on autodiff stack
      var build(double value) {
        size_t size = edge1_.size() + edge2_.size() + edge3_.size() + edge4_.size();
        vari** varis = ChainableStack::memalloc_.alloc_array<vari*>(size);
        int idx = 0;
        edge1_.dump_operands(&varis[idx]);
        edge2_.dump_operands(&varis[idx += edge1_.size()]);
        edge3_.dump_operands(&varis[idx += edge2_.size()]);
        edge4_.dump_operands(&varis[idx += edge3_.size()]);

        double* partials = ChainableStack::memalloc_.alloc_array<double>(size);
        idx = 0;
        edge1_.dump_partials(&partials[idx]);
        edge2_.dump_partials(&partials[idx += edge1_.size()]);
        edge3_.dump_partials(&partials[idx += edge2_.size()]);
        edge4_.dump_partials(&partials[idx += edge3_.size()]);

        return var(new precomputed_gradients_vari(value, size, varis, partials));
      };
    };
  }
}
#endif