#ifndef TYPES_INCLUDE_GUARD_H
#define TYPES_INCLUDE_GUARD_H

#include <Rinternals.h>
#include <cstdint>
#include <vector>
#include <stdexcept>

#include "int_utils.h"

/**
 * Multivariate Binary Dataset
 *
 * Note: The maximum binary vector size for one observation is 32.
 */
class MVBinary : public std::vector<uint32_t> {
public:
    MVBinary(std::size_t n, std::size_t p) : p{p} {
        this->reserve(n);
    }
    template<typename _InputIterator>
    MVBinary(_InputIterator first, _InputIterator last, std::size_t p)
        : p{p}
        , std::vector<uint32_t>(first, last) { };

    std::size_t dim() const { return p; }
    std::size_t nrow() const { return size(); }
    std::size_t ncol() const { return p; }

private:
    std::size_t p = 0;
};

/**
 * View to a SEXP numeric vector representing a half vectorized matrix.
 *
 * This means that there esists a `p` such that the length is `p (p + 1) / 2`.
 */
class VechView {
public:

    VechView(const std::size_t p)
        : _sexp{nullptr}
        , _size{p * (p + 1) / 2}
        , _dim{p}
        , _data{new double[_size]} { }

    // Only ctor, its a SEXP view
    VechView(SEXP x) : _sexp{x} {
        // check type
        if (TYPEOF(x) != REALSXP) {
            throw std::invalid_argument("expected numeric vector");
        }
        // get size, compute underlying dimension and validate size consistency
        // which is that `_size = length(x) = _dim (_dim + 1) / 2`.
        _size = Rf_length(x);
        if (!(_dim = invTriag(_size))) {
            throw std::invalid_argument("Expected `length(theta) == p * (p + 1) / 2`");
        }
        // set data memory hook
        _data = REAL(x);
    }
    // // for now, do not allow to use `Rcpp::wrap`
    // operator SEXP()       { return _sexp; };
    // operator SEXP() const { return _sexp; };

    // dtor in case of owning the data instead of beeing a view to a SEXP the
    // aquired memory needs to be free
    ~VechView() {
        if (!_sexp) {
            delete[] _data;
        }
    }

    std::size_t size() const { return _size; }
    std::size_t dim()  const { return _dim; }

    std::size_t index(std::size_t i, std::size_t j) const {
        return (i * (2 * _dim - 1 - i)) / 2 + j;
    }
    std::size_t index(std::size_t i) const { return index(i, i); }

    double  operator[](std::size_t i) const { return _data[i]; }
    double& operator[](std::size_t i)       { return _data[i]; }
    double  operator()(std::size_t i, std::size_t j) const {
        return _data[(i * (2 * _dim - 1 - i)) / 2 + j];
    }
    double& operator()(std::size_t i, std::size_t j)       {
        return _data[(i * (2 * _dim - 1 - i)) / 2 + j];
    }
    double  operator()(std::size_t i) const { return (*this)(i, i); }
    double& operator()(std::size_t i)       { return (*this)(i, i); }

          double*  begin()       { return _data; }
          double*  end()         { return _data + _size; }
    const double*  begin() const { return _data; }
    const double*  end()   const { return _data + _size; }
    const double* cbegin()       { return _data; }
    const double* cend()         { return _data + _size; }

private:
    SEXP _sexp;         // original R object of which this is a view to the data
    std::size_t _size;  // length of _data
    std::size_t _dim;   // dimension of references _data, relation to _size
                        // is given by `2 _size = _dim (_dim + 1)`
    double* _data;      // pointer to underlying data
    // bool _owner = false;// set to true if the SEXP object is created and needs
    //                     // to be unprotected (from the GC) at destruction
};

#endif /* TYPES_INCLUDE_GUARD_H */
