#ifndef STAN_MATH_PRIM_MAT_META_SEQ_VIEW_HPP
#define STAN_MATH_PRIM_MAT_META_SEQ_VIEW_HPP

#include <stan/math/prim/mat/fun/Eigen.hpp>
#include <vector>

namespace stan {
  namespace math {

    template <typename T>
    struct store_type {
      typedef const T& type;
    };
    template <>
    struct store_type<double> {
      typedef const double type;
    };
    template <>
    struct store_type<int> {
      typedef const int type;
    };

    template <typename T>
    struct pass_type {
      typedef const T& type;
    };
    template <>
    struct pass_type<double> {
      typedef double type;
    };
    template <>
    struct pass_type<int> {
      typedef int type;
    };

    // S assignable to T
    template <typename T, typename S>
    class seq_view {
    private:
      typename store_type<S>::type x_;
    public:
      explicit seq_view(typename pass_type<S>::type x)
        : x_(x) {
      }
      inline typename pass_type<T>::type
      operator[](int n) const {
        return x_;
      }
      int size() const {
        return 1;
      }
    };

    template <typename T, typename S>
    class seq_view<T, Eigen::Matrix<S, Eigen::Dynamic, 1> > {
    private:
      typename store_type<Eigen::Matrix<S, Eigen::Dynamic, 1> >::type x_;
    public:
      explicit seq_view(typename
                        pass_type<Eigen::Matrix<S, Eigen::Dynamic, 1> >::type x)
        : x_(x) {
      }
      inline typename pass_type<T>::type
      operator[](int n) const {
        return x_(n);
      }
      int size() const {
        return x_.size();
      }
    };

    template <typename T, typename S>
    class seq_view<T, Eigen::Matrix<S, 1, Eigen::Dynamic> > {
    private:
      typename store_type<Eigen::Matrix<S, 1, Eigen::Dynamic> >::type x_;
    public:
      explicit seq_view(typename pass_type
                        <Eigen::Matrix<S, 1, Eigen::Dynamic> >::type x)
        : x_(x) {
      }
      inline typename pass_type<T>::type
      operator[](int n) const {
        return x_(n);
      }
      int size() const {
        return x_.size();
      }
    };

    // row-major order of returns to match std::vector
    template <typename T, typename S>
    class seq_view<T, Eigen::Matrix<S, Eigen::Dynamic, Eigen::Dynamic> > {
    private:
      typename store_type<Eigen::Matrix
                          <S, Eigen::Dynamic, Eigen::Dynamic> >::type x_;
    public:
      explicit
      seq_view(typename pass_type<Eigen::Matrix
               <S, Eigen::Dynamic, Eigen::Dynamic> >::type x)
        : x_(x) {
      }
      inline typename pass_type<T>::type
      operator[](int n) const {
        return x_(n / x_.cols(), n % x_.cols());
      }
      int size() const {
        return x_.size();
      }
    };

    // question is how expensive the ctor is
    template <typename T, typename S>
    class seq_view<T, std::vector<S> > {
    private:
      typename store_type<std::vector<S> >::type x_;
      const size_t elt_size_;
    public:
      explicit seq_view(typename pass_type<std::vector<S> >::type x)
        : x_(x),
          elt_size_(x_.size() == 0 ? 0 : seq_view<T, S>(x_[0]).size()) {
      }
      inline typename pass_type<T>::type
      operator[](int n) const {
        return seq_view<T, S>(x_[n / elt_size_])[n % elt_size_];
      }
      int size() const {
        return x_.size() * elt_size_;
      }
    };

    // BELOW HERE JUST FOR EFFICIENCY

    template <typename T>
    class seq_view<T, std::vector<T> > {
    private:
      typename store_type<std::vector<T> >::type x_;
    public:
      explicit seq_view(typename pass_type<std::vector<T> >::type x)
        : x_(x) {
      }
      inline typename pass_type<T>::type
      operator[](int n) const {
        return x_[n];
      }
      int size() const {
        return x_.size();
      }
    };

    // if vector of S with S assignable to T, also works
    // use enable_if?  (and disable_if for the general case)
    template <typename T>
    class seq_view<T, std::vector<std::vector<T> > > {
    private:
      typename store_type<std::vector<std::vector<T> > >::type x_;
      const size_t cols_;
    public:
      explicit seq_view(typename pass_type
                        <std::vector<std::vector<T> > >::type x)
        : x_(x),
          cols_(x_.size() == 0 ? 0 : x_[0].size()) {  }
      inline typename pass_type<T>::type
      operator[](int n) const {
        return x_[n / cols_][n % cols_];
      }
      int size() const {
        return x_.size() * cols_;
      }
    };

    template <>
    class seq_view<double, std::vector<int> > {
    private:
      store_type<std::vector<int> >::type x_;
    public:
      explicit seq_view(pass_type<std::vector<int> >::type x)
        : x_(x) {
      }
      inline pass_type<double>::type operator[](int n) const {
        return x_[n];
      }
      int size() const {
        return x_.size();
      }
    };


  }
}
#endif
