#ifndef TBPLAS_MEMORY_VIEW_H
#define TBPLAS_MEMORY_VIEW_H

#include <cstdint>

namespace memory_view {

/**
 * @brief Memory view on matrix in row-major order.
 *
 * @tparam data_t data type of array
 *
 * @note
 *   1. We always 64 bit pointer type in this class.
 *   2. DO NOT use cached offsets. They are thread-unsafe when OMP is enabled.
 */
template <typename data_t>
class MemoryView {
private:
    /// @brief pointer to data
    data_t* data_ = nullptr;

    /// @brief number of columns
    int64_t num_col_ = 0;

public:
    /**
     * @brief Construct a new Memory View object
     *
     * @param[in] data pointer to data
     * @param[in] num_col number of columns
     */
    explicit MemoryView(data_t* data, const int64_t& num_col)
        : data_(data)
        , num_col_(num_col)
    {
    }

    /**
     * @brief Get reference to data_[i, j]
     *
     * @param[in] i row index
     * @param[in] j column index
     * @return reference to data_[i, j]
     */
    data_t& operator()(const int64_t& i, const int64_t& j = 0)
    {
        return data_[i * num_col_ + j];
    }

    /**
     * @brief Get address of data_[i, j]
     *
     * @param[in] i row index
     * @param[in] j column index
     * @return address of data_[i, j]
     */
    data_t* addr(const int64_t& i, const int64_t& j = 0)
    {
        return data_ + i * num_col_ + j;
    }
};

/**
 * @brief Constant memory view on matrix in row-major order.
 *
 * @tparam data_t data type of array
 */
template <typename data_t>
class ConstMemoryView {
private:
    /// @brief pointer to data
    const data_t* data_ = nullptr;

    /// @brief number of columns
    int64_t num_col_ = 0;

public:
    /**
     * @brief Construct a new Memory View object
     *
     * @param[in] data pointer to data
     * @param[in] num_col number of columns
     */
    explicit ConstMemoryView(const data_t* data, const int64_t& num_col)
        : data_(data)
        , num_col_(num_col)
    {
    }

    /**
     * @brief Get reference to data_[i, j]
     *
     * @param[in] i row index
     * @param[in] j column index
     * @return reference to data_[i, j]
     */
    const data_t& operator()(const int64_t& i, const int64_t& j = 0) const
    {
        return data_[i * num_col_ + j];
    }

    /**
     * @brief Get address of data_[i, j]
     *
     * @param[in] i row index
     * @param[in] j column index
     * @return address of data_[i, j]
     */
    const data_t* addr(const int64_t& i, const int64_t& j = 0) const
    {
        return data_ + i * num_col_ + j;
    }
};
} // namespace memory_view

#endif