// SPDX-License-Identifier: Apache-2.0
//
// Copyright 2008-2016 Conrad Sanderson (http://conradsanderson.id.au)
// Copyright 2008-2016 National ICT Australia (NICTA)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

//! \addtogroup SpSubview
//! @{

template <typename eT>
inline SpSubview<eT>::~SpSubview() {
  arma_debug_sigprint_this(this);
}

template <typename eT>
inline SpSubview<eT>::SpSubview(const SpMat<eT>& in_m, const uword in_row1,
                                const uword in_col1, const uword in_n_rows,
                                const uword in_n_cols)
    : m(in_m),
      aux_row1(in_row1),
      aux_col1(in_col1),
      n_rows(in_n_rows),
      n_cols(in_n_cols),
      n_elem(in_n_rows * in_n_cols),
      n_nonzero(0) {
  arma_debug_sigprint_this(this);

  m.sync_csc();

  // count the number of non-zeros in the subview
  uword count = 0;

  if (n_rows == m.n_rows) {
    count = m.col_ptrs[aux_col1 + n_cols] - m.col_ptrs[aux_col1];
  } else {
    arma_debug_print("counting non-zeros in sparse subview");

    uword lend = m.col_ptrs[in_col1 + in_n_cols];
    uword lend_row = in_row1 + in_n_rows;

    for (uword i = m.col_ptrs[in_col1]; i < lend; ++i) {
      const uword m_row_indices_i = m.row_indices[i];

      const bool condition = (m_row_indices_i >= in_row1) && (m_row_indices_i < lend_row);

      count += condition ? uword(1) : uword(0);
    }
  }

  access::rw(n_nonzero) = count;
}

template <typename eT>
inline SpSubview<eT>::SpSubview(const SpSubview<eT>& in)
    : m(in.m),
      aux_row1(in.aux_row1),
      aux_col1(in.aux_col1),
      n_rows(in.n_rows),
      n_cols(in.n_cols),
      n_elem(in.n_elem),
      n_nonzero(in.n_nonzero) {
  arma_debug_sigprint(arma_str::format("this: %x; in: %x") % this % &in);
}

template <typename eT>
inline SpSubview<eT>::SpSubview(SpSubview<eT>&& in)
    : m(in.m),
      aux_row1(in.aux_row1),
      aux_col1(in.aux_col1),
      n_rows(in.n_rows),
      n_cols(in.n_cols),
      n_elem(in.n_elem),
      n_nonzero(in.n_nonzero) {
  arma_debug_sigprint(arma_str::format("this: %x; in: %x") % this % &in);

  // for paranoia

  access::rw(in.aux_row1) = 0;
  access::rw(in.aux_col1) = 0;
  access::rw(in.n_rows) = 0;
  access::rw(in.n_cols) = 0;
  access::rw(in.n_elem) = 0;
  access::rw(in.n_nonzero) = 0;
}

template <typename eT>
inline const SpSubview<eT>& SpSubview<eT>::operator+=(const eT val) {
  arma_debug_sigprint();

  if (val == eT(0)) {
    return *this;
  }

  Mat<eT> tmp((*this).n_rows, (*this).n_cols, arma_nozeros_indicator());

  tmp.fill(val);

  return (*this).operator=((*this) + tmp);
}

template <typename eT>
inline const SpSubview<eT>& SpSubview<eT>::operator-=(const eT val) {
  arma_debug_sigprint();

  if (val == eT(0)) {
    return *this;
  }

  Mat<eT> tmp((*this).n_rows, (*this).n_cols, arma_nozeros_indicator());

  tmp.fill(val);

  return (*this).operator=((*this) - tmp);
}

template <typename eT>
inline const SpSubview<eT>& SpSubview<eT>::operator*=(const eT val) {
  arma_debug_sigprint();

  if (val == eT(0)) {
    (*this).zeros();
    return *this;
  }

  if ((n_elem == 0) || (n_nonzero == 0)) {
    return *this;
  }

  m.sync_csc();
  m.invalidate_cache();

  const uword lstart_row = aux_row1;
  const uword lend_row = aux_row1 + n_rows;

  const uword lstart_col = aux_col1;
  const uword lend_col = aux_col1 + n_cols;

  const uword* m_row_indices = m.row_indices;
  eT* m_values = access::rwp(m.values);

  bool has_zero = false;

  for (uword c = lstart_col; c < lend_col; ++c) {
    const uword r_start = m.col_ptrs[c];
    const uword r_end = m.col_ptrs[c + 1];

    for (uword r = r_start; r < r_end; ++r) {
      const uword m_row_indices_r = m_row_indices[r];

      if ((m_row_indices_r >= lstart_row) && (m_row_indices_r < lend_row)) {
        eT& m_values_r = m_values[r];

        m_values_r *= val;

        if (m_values_r == eT(0)) {
          has_zero = true;
        }
      }
    }
  }

  if (has_zero) {
    const uword old_m_n_nonzero = m.n_nonzero;

    access::rw(m).remove_zeros();

    if (m.n_nonzero != old_m_n_nonzero) {
      access::rw(n_nonzero) = n_nonzero - (old_m_n_nonzero - m.n_nonzero);
    }
  }

  return *this;
}

template <typename eT>
inline const SpSubview<eT>& SpSubview<eT>::operator/=(const eT val) {
  arma_debug_sigprint();

  arma_conform_check((val == eT(0)), "element-wise division: division by zero");

  m.sync_csc();
  m.invalidate_cache();

  const uword lstart_row = aux_row1;
  const uword lend_row = aux_row1 + n_rows;

  const uword lstart_col = aux_col1;
  const uword lend_col = aux_col1 + n_cols;

  const uword* m_row_indices = m.row_indices;
  eT* m_values = access::rwp(m.values);

  bool has_zero = false;

  for (uword c = lstart_col; c < lend_col; ++c) {
    const uword r_start = m.col_ptrs[c];
    const uword r_end = m.col_ptrs[c + 1];

    for (uword r = r_start; r < r_end; ++r) {
      const uword m_row_indices_r = m_row_indices[r];

      if ((m_row_indices_r >= lstart_row) && (m_row_indices_r < lend_row)) {
        eT& m_values_r = m_values[r];

        m_values_r /= val;

        if (m_values_r == eT(0)) {
          has_zero = true;
        }
      }
    }
  }

  if (has_zero) {
    const uword old_m_n_nonzero = m.n_nonzero;

    access::rw(m).remove_zeros();

    if (m.n_nonzero != old_m_n_nonzero) {
      access::rw(n_nonzero) = n_nonzero - (old_m_n_nonzero - m.n_nonzero);
    }
  }

  return *this;
}

template <typename eT>
template <typename T1>
inline const SpSubview<eT>& SpSubview<eT>::operator=(const Base<eT, T1>& in) {
  arma_debug_sigprint();

  if (is_same_type<T1, Gen<Mat<eT>, gen_zeros> >::yes) {
    const Proxy<T1> P(in.get_ref());

    arma_conform_assert_same_size(n_rows, n_cols, P.get_n_rows(), P.get_n_cols(),
                                  "insertion into sparse submatrix");

    (*this).zeros();

    return *this;
  }

  if (is_same_type<T1, Gen<Mat<eT>, gen_eye> >::yes) {
    const Proxy<T1> P(in.get_ref());

    arma_conform_assert_same_size(n_rows, n_cols, P.get_n_rows(), P.get_n_cols(),
                                  "insertion into sparse submatrix");

    (*this).eye();

    return *this;
  }

  const quasi_unwrap<T1> U(in.get_ref());

  arma_conform_assert_same_size(n_rows, n_cols, U.M.n_rows, U.M.n_cols,
                                "insertion into sparse submatrix");

  spglue_merge::subview_merge(*this, U.M);

  return *this;
}

template <typename eT>
template <typename T1>
inline const SpSubview<eT>& SpSubview<eT>::operator+=(const Base<eT, T1>& x) {
  arma_debug_sigprint();

  return (*this).operator=((*this) + x.get_ref());
}

template <typename eT>
template <typename T1>
inline const SpSubview<eT>& SpSubview<eT>::operator-=(const Base<eT, T1>& x) {
  arma_debug_sigprint();

  return (*this).operator=((*this) - x.get_ref());
}

template <typename eT>
template <typename T1>
inline const SpSubview<eT>& SpSubview<eT>::operator*=(const Base<eT, T1>& x) {
  arma_debug_sigprint();

  SpMat<eT> tmp(*this);

  tmp *= x.get_ref();

  return (*this).operator=(tmp);
}

template <typename eT>
template <typename T1>
inline const SpSubview<eT>& SpSubview<eT>::operator%=(const Base<eT, T1>& x) {
  arma_debug_sigprint();

  SpSubview<eT>& sv = (*this);

  const quasi_unwrap<T1> U(x.get_ref());
  const Mat<eT>& B = U.M;

  arma_conform_assert_same_size(sv.n_rows, sv.n_cols, B.n_rows, B.n_cols,
                                "element-wise multiplication");

  SpMat<eT>& sv_m = access::rw(sv.m);

  sv_m.sync_csc();
  sv_m.invalidate_cache();

  const uword m_row_start = sv.aux_row1;
  const uword m_row_end = sv.aux_row1 + sv.n_rows - 1;

  const uword m_col_start = sv.aux_col1;
  const uword m_col_end = sv.aux_col1 + sv.n_cols - 1;

  constexpr eT zero = eT(0);

  bool has_zero = false;
  uword count = 0;

  for (uword m_col = m_col_start; m_col <= m_col_end; ++m_col) {
    const uword sv_col = m_col - m_col_start;

    const uword index_start = sv_m.col_ptrs[m_col];
    const uword index_end = sv_m.col_ptrs[m_col + 1];

    for (uword i = index_start; i < index_end; ++i) {
      const uword m_row = sv_m.row_indices[i];

      if (m_row < m_row_start) {
        continue;
      }
      if (m_row > m_row_end) {
        break;
      }

      const uword sv_row = m_row - m_row_start;

      eT& m_val = access::rw(sv_m.values[i]);

      const eT result = m_val * B.at(sv_row, sv_col);

      m_val = result;

      if (result == zero) {
        has_zero = true;
      } else {
        ++count;
      }
    }
  }

  if (has_zero) {
    sv_m.remove_zeros();
  }

  access::rw(sv.n_nonzero) = count;

  return (*this);
}

template <typename eT>
template <typename T1>
inline const SpSubview<eT>& SpSubview<eT>::operator/=(const Base<eT, T1>& x) {
  arma_debug_sigprint();

  const SpSubview<eT>& A = (*this);

  const quasi_unwrap<T1> U(x.get_ref());
  const Mat<eT>& B = U.M;

  arma_conform_assert_same_size(A.n_rows, A.n_cols, B.n_rows, B.n_cols,
                                "element-wise division");

  bool result_ok = true;

  constexpr eT zero = eT(0);

  const uword B_n_rows = B.n_rows;
  const uword B_n_cols = B.n_cols;

  for (uword c = 0; c < B_n_cols; ++c) {
    for (uword r = 0; r < B_n_rows; ++r) {
      // a zero in B and A at the same location implies the division result is NaN;
      // hence a zero in A (not stored) needs to be changed into a non-zero

      // for efficiency, an element in B is checked before checking the corresponding
      // element in A

      if ((B.at(r, c) == zero) && (A.at(r, c) == zero)) {
        result_ok = false;
        break;
      }
    }

    if (result_ok == false) {
      break;
    }
  }

  if (result_ok) {
    const_iterator cit = A.begin();
    const_iterator cit_end = A.end();

    while (cit != cit_end) {
      const eT tmp = (*cit) / B.at(cit.row(), cit.col());

      if (tmp == zero) {
        result_ok = false;
        break;
      }

      ++cit;
    }
  }

  if (result_ok) {
    iterator it = (*this).begin();
    iterator it_end = (*this).end();

    while (it != it_end) {
      (*it) /= B.at(it.row(), it.col());

      ++it;
    }
  } else {
    (*this).operator=((*this) / B);
  }

  return (*this);
}

template <typename eT>
inline const SpSubview<eT>& SpSubview<eT>::operator=(const SpSubview<eT>& x) {
  arma_debug_sigprint();

  return (*this).operator_equ_common(x);
}

template <typename eT>
template <typename T1>
inline const SpSubview<eT>& SpSubview<eT>::operator=(const SpBase<eT, T1>& x) {
  arma_debug_sigprint();

  return (*this).operator_equ_common(x.get_ref());
}

template <typename eT>
template <typename T1>
inline const SpSubview<eT>& SpSubview<eT>::operator_equ_common(const SpBase<eT, T1>& in) {
  arma_debug_sigprint();

  const unwrap_spmat<T1> U(in.get_ref());

  arma_conform_assert_same_size(n_rows, n_cols, U.M.n_rows, U.M.n_cols,
                                "insertion into sparse submatrix");

  if (U.is_alias(m)) {
    const SpMat<eT> tmp(U.M);

    spglue_merge::subview_merge(*this, tmp);
  } else {
    spglue_merge::subview_merge(*this, U.M);
  }

  return *this;
}

template <typename eT>
template <typename T1>
inline const SpSubview<eT>& SpSubview<eT>::operator+=(const SpBase<eT, T1>& x) {
  arma_debug_sigprint();

  // TODO: implement dedicated machinery
  return (*this).operator=((*this) + x.get_ref());
}

template <typename eT>
template <typename T1>
inline const SpSubview<eT>& SpSubview<eT>::operator-=(const SpBase<eT, T1>& x) {
  arma_debug_sigprint();

  // TODO: implement dedicated machinery
  return (*this).operator=((*this) - x.get_ref());
}

template <typename eT>
template <typename T1>
inline const SpSubview<eT>& SpSubview<eT>::operator*=(const SpBase<eT, T1>& x) {
  arma_debug_sigprint();

  return (*this).operator=((*this) * x.get_ref());
}

template <typename eT>
template <typename T1>
inline const SpSubview<eT>& SpSubview<eT>::operator%=(const SpBase<eT, T1>& x) {
  arma_debug_sigprint();

  // TODO: implement dedicated machinery
  return (*this).operator=((*this) % x.get_ref());
}

template <typename eT>
template <typename T1>
inline const SpSubview<eT>& SpSubview<eT>::operator/=(const SpBase<eT, T1>& x) {
  arma_debug_sigprint();

  // NOTE: use of this function is not advised; it is implemented only for completeness

  SpProxy<T1> p(x.get_ref());

  arma_conform_assert_same_size(n_rows, n_cols, p.get_n_rows(), p.get_n_cols(),
                                "element-wise division");

  if (p.is_alias(m) == false) {
    for (uword lcol = 0; lcol < n_cols; ++lcol)
      for (uword lrow = 0; lrow < n_rows; ++lrow) {
        at(lrow, lcol) /= p.at(lrow, lcol);
      }
  } else {
    const SpMat<eT> tmp(p.Q);

    (*this).operator/=(tmp);
  }

  return *this;
}

//! apply a functor to each element
template <typename eT>
template <typename functor>
inline void SpSubview<eT>::for_each(functor F) {
  arma_debug_sigprint();

  m.sync_csc();
  m.invalidate_cache();

  const uword lstart_row = aux_row1;
  const uword lend_row = aux_row1 + n_rows;

  const uword lstart_col = aux_col1;
  const uword lend_col = aux_col1 + n_cols;

  const uword* m_row_indices = m.row_indices;
  eT* m_values = access::rwp(m.values);

  bool has_zero = false;

  for (uword c = lstart_col; c < lend_col; ++c) {
    const uword r_start = m.col_ptrs[c];
    const uword r_end = m.col_ptrs[c + 1];

    for (uword r = r_start; r < r_end; ++r) {
      const uword m_row_indices_r = m_row_indices[r];

      if ((m_row_indices_r >= lstart_row) && (m_row_indices_r < lend_row)) {
        eT& m_values_r = m_values[r];

        F(m_values_r);

        if (m_values_r == eT(0)) {
          has_zero = true;
        }
      }
    }
  }

  if (has_zero) {
    const uword old_m_n_nonzero = m.n_nonzero;

    access::rw(m).remove_zeros();

    if (m.n_nonzero != old_m_n_nonzero) {
      access::rw(n_nonzero) = n_nonzero - (old_m_n_nonzero - m.n_nonzero);
    }
  }
}

template <typename eT>
template <typename functor>
inline void SpSubview<eT>::for_each(functor F) const {
  arma_debug_sigprint();

  m.sync_csc();

  const uword lstart_row = aux_row1;
  const uword lend_row = aux_row1 + n_rows;

  const uword lstart_col = aux_col1;
  const uword lend_col = aux_col1 + n_cols;

  const uword* m_row_indices = m.row_indices;

  for (uword c = lstart_col; c < lend_col; ++c) {
    const uword r_start = m.col_ptrs[c];
    const uword r_end = m.col_ptrs[c + 1];

    for (uword r = r_start; r < r_end; ++r) {
      const uword m_row_indices_r = m_row_indices[r];

      if ((m_row_indices_r >= lstart_row) && (m_row_indices_r < lend_row)) {
        F(m.values[r]);
      }
    }
  }
}

//! transform each element using a functor
template <typename eT>
template <typename functor>
inline void SpSubview<eT>::transform(functor F) {
  arma_debug_sigprint();

  m.sync_csc();
  m.invalidate_cache();

  const uword lstart_row = aux_row1;
  const uword lend_row = aux_row1 + n_rows;

  const uword lstart_col = aux_col1;
  const uword lend_col = aux_col1 + n_cols;

  const uword* m_row_indices = m.row_indices;
  eT* m_values = access::rwp(m.values);

  bool has_zero = false;

  for (uword c = lstart_col; c < lend_col; ++c) {
    const uword r_start = m.col_ptrs[c];
    const uword r_end = m.col_ptrs[c + 1];

    for (uword r = r_start; r < r_end; ++r) {
      const uword m_row_indices_r = m_row_indices[r];

      if ((m_row_indices_r >= lstart_row) && (m_row_indices_r < lend_row)) {
        eT& m_values_r = m_values[r];

        m_values_r = eT(F(m_values_r));

        if (m_values_r == eT(0)) {
          has_zero = true;
        }
      }
    }
  }

  if (has_zero) {
    const uword old_m_n_nonzero = m.n_nonzero;

    access::rw(m).remove_zeros();

    if (m.n_nonzero != old_m_n_nonzero) {
      access::rw(n_nonzero) = n_nonzero - (old_m_n_nonzero - m.n_nonzero);
    }
  }
}

template <typename eT>
inline void SpSubview<eT>::replace(const eT old_val, const eT new_val) {
  arma_debug_sigprint();

  if (old_val == eT(0)) {
    if (new_val != eT(0)) {
      Mat<eT> tmp(*this);

      tmp.replace(old_val, new_val);

      (*this).operator=(tmp);
    }

    return;
  }

  m.sync_csc();
  m.invalidate_cache();

  const uword lstart_row = aux_row1;
  const uword lend_row = aux_row1 + n_rows;

  const uword lstart_col = aux_col1;
  const uword lend_col = aux_col1 + n_cols;

  const uword* m_row_indices = m.row_indices;
  eT* m_values = access::rwp(m.values);

  if (arma_isnan(old_val)) {
    for (uword c = lstart_col; c < lend_col; ++c) {
      const uword r_start = m.col_ptrs[c];
      const uword r_end = m.col_ptrs[c + 1];

      for (uword r = r_start; r < r_end; ++r) {
        const uword m_row_indices_r = m_row_indices[r];

        if ((m_row_indices_r >= lstart_row) && (m_row_indices_r < lend_row)) {
          eT& val = m_values[r];

          val = (arma_isnan(val)) ? new_val : val;
        }
      }
    }
  } else {
    for (uword c = lstart_col; c < lend_col; ++c) {
      const uword r_start = m.col_ptrs[c];
      const uword r_end = m.col_ptrs[c + 1];

      for (uword r = r_start; r < r_end; ++r) {
        const uword m_row_indices_r = m_row_indices[r];

        if ((m_row_indices_r >= lstart_row) && (m_row_indices_r < lend_row)) {
          eT& val = m_values[r];

          val = (val == old_val) ? new_val : val;
        }
      }
    }
  }

  if (new_val == eT(0)) {
    access::rw(m).remove_zeros();
  }
}

template <typename eT>
inline void SpSubview<eT>::clean(const typename get_pod_type<eT>::result threshold) {
  arma_debug_sigprint();

  if ((n_elem == 0) || (n_nonzero == 0)) {
    return;
  }

  // TODO: replace with a more efficient implementation

  SpMat<eT> tmp(*this);

  tmp.clean(threshold);

  if (is_cx<eT>::yes) {
    (*this).operator=(tmp);
  } else if (tmp.n_nonzero != n_nonzero) {
    (*this).operator=(tmp);
  }
}

template <typename eT>
inline void SpSubview<eT>::clamp(const eT min_val, const eT max_val) {
  arma_debug_sigprint();

  if (is_cx<eT>::no) {
    arma_conform_check((access::tmp_real(min_val) > access::tmp_real(max_val)),
                       "SpSubview::clamp(): min_val must be less than max_val");
  } else {
    arma_conform_check(
        (access::tmp_real(min_val) > access::tmp_real(max_val)),
        "SpSubview::clamp(): real(min_val) must be less than real(max_val)");
    arma_conform_check(
        (access::tmp_imag(min_val) > access::tmp_imag(max_val)),
        "SpSubview::clamp(): imag(min_val) must be less than imag(max_val)");
  }

  if ((n_elem == 0) || (n_nonzero == 0)) {
    return;
  }

  // TODO: replace with a more efficient implementation

  SpMat<eT> tmp(*this);

  tmp.clamp(min_val, max_val);

  (*this).operator=(tmp);
}

template <typename eT>
inline void SpSubview<eT>::fill(const eT val) {
  arma_debug_sigprint();

  if (val != eT(0)) {
    Mat<eT> tmp((*this).n_rows, (*this).n_cols, arma_nozeros_indicator());

    tmp.fill(val);

    (*this).operator=(tmp);
  } else {
    (*this).zeros();
  }
}

template <typename eT>
inline void SpSubview<eT>::zeros() {
  arma_debug_sigprint();

  if ((n_elem == 0) || (n_nonzero == 0)) {
    return;
  }

  if ((m.n_nonzero - n_nonzero) == 0) {
    access::rw(m).zeros();
    access::rw(n_nonzero) = 0;
    return;
  }

  SpMat<eT> tmp(arma_reserve_indicator(), m.n_rows, m.n_cols, m.n_nonzero - n_nonzero);

  const uword sv_row_start = aux_row1;
  const uword sv_col_start = aux_col1;

  const uword sv_row_end = aux_row1 + n_rows - 1;
  const uword sv_col_end = aux_col1 + n_cols - 1;

  typename SpMat<eT>::const_iterator m_it = m.begin();
  typename SpMat<eT>::const_iterator m_it_end = m.end();

  uword tmp_count = 0;

  for (; m_it != m_it_end; ++m_it) {
    const uword m_it_row = m_it.row();
    const uword m_it_col = m_it.col();

    const bool inside_box = ((m_it_row >= sv_row_start) && (m_it_row <= sv_row_end)) &&
                            ((m_it_col >= sv_col_start) && (m_it_col <= sv_col_end));

    if (inside_box == false) {
      access::rw(tmp.values[tmp_count]) = (*m_it);
      access::rw(tmp.row_indices[tmp_count]) = m_it_row;
      access::rw(tmp.col_ptrs[m_it_col + 1])++;
      ++tmp_count;
    }
  }

  for (uword i = 0; i < tmp.n_cols; ++i) {
    access::rw(tmp.col_ptrs[i + 1]) += tmp.col_ptrs[i];
  }

  access::rw(m).steal_mem(tmp);

  access::rw(n_nonzero) = 0;
}

template <typename eT>
inline void SpSubview<eT>::ones() {
  arma_debug_sigprint();

  (*this).fill(eT(1));
}

template <typename eT>
inline void SpSubview<eT>::eye() {
  arma_debug_sigprint();

  SpMat<eT> tmp;

  tmp.eye((*this).n_rows, (*this).n_cols);

  (*this).operator=(tmp);
}

template <typename eT>
inline void SpSubview<eT>::randu() {
  arma_debug_sigprint();

  Mat<eT> tmp((*this).n_rows, (*this).n_cols, fill::randu);

  (*this).operator=(tmp);
}

template <typename eT>
inline void SpSubview<eT>::randn() {
  arma_debug_sigprint();

  Mat<eT> tmp((*this).n_rows, (*this).n_cols, fill::randn);

  (*this).operator=(tmp);
}

template <typename eT>
inline SpSubview_MapMat_val<eT> SpSubview<eT>::operator[](const uword i) {
  const uword lrow = i % n_rows;
  const uword lcol = i / n_rows;

  return (*this).at(lrow, lcol);
}

template <typename eT>
inline eT SpSubview<eT>::operator[](const uword i) const {
  const uword lrow = i % n_rows;
  const uword lcol = i / n_rows;

  return (*this).at(lrow, lcol);
}

template <typename eT>
inline SpSubview_MapMat_val<eT> SpSubview<eT>::operator()(const uword i) {
  arma_conform_check_bounds((i >= n_elem), "SpSubview::operator(): index out of bounds");

  const uword lrow = i % n_rows;
  const uword lcol = i / n_rows;

  return (*this).at(lrow, lcol);
}

template <typename eT>
inline eT SpSubview<eT>::operator()(const uword i) const {
  arma_conform_check_bounds((i >= n_elem), "SpSubview::operator(): index out of bounds");

  const uword lrow = i % n_rows;
  const uword lcol = i / n_rows;

  return (*this).at(lrow, lcol);
}

template <typename eT>
inline SpSubview_MapMat_val<eT> SpSubview<eT>::operator()(const uword in_row,
                                                          const uword in_col) {
  arma_conform_check_bounds((in_row >= n_rows) || (in_col >= n_cols),
                            "SpSubview::operator(): index out of bounds");

  return (*this).at(in_row, in_col);
}

template <typename eT>
inline eT SpSubview<eT>::operator()(const uword in_row, const uword in_col) const {
  arma_conform_check_bounds((in_row >= n_rows) || (in_col >= n_cols),
                            "SpSubview::operator(): index out of bounds");

  return (*this).at(in_row, in_col);
}

template <typename eT>
inline SpSubview_MapMat_val<eT> SpSubview<eT>::at(const uword i) {
  const uword lrow = i % n_rows;
  const uword lcol = i / n_cols;

  return (*this).at(lrow, lcol);
}

template <typename eT>
inline eT SpSubview<eT>::at(const uword i) const {
  const uword lrow = i % n_rows;
  const uword lcol = i / n_cols;

  return (*this).at(lrow, lcol);
}

template <typename eT>
inline SpSubview_MapMat_val<eT> SpSubview<eT>::at(const uword in_row,
                                                  const uword in_col) {
  return SpSubview_MapMat_val<eT>((*this), m.cache, aux_row1 + in_row, aux_col1 + in_col);
}

template <typename eT>
inline eT SpSubview<eT>::at(const uword in_row, const uword in_col) const {
  return m.at(aux_row1 + in_row, aux_col1 + in_col);
}

template <typename eT>
inline bool SpSubview<eT>::check_overlap(const SpSubview<eT>& x) const {
  const SpSubview<eT>& t = *this;

  if (&t.m != &x.m) {
    return false;
  } else {
    if ((t.n_elem == 0) || (x.n_elem == 0)) {
      return false;
    } else {
      const uword t_row_start = t.aux_row1;
      const uword t_row_end_p1 = t_row_start + t.n_rows;

      const uword t_col_start = t.aux_col1;
      const uword t_col_end_p1 = t_col_start + t.n_cols;

      const uword x_row_start = x.aux_row1;
      const uword x_row_end_p1 = x_row_start + x.n_rows;

      const uword x_col_start = x.aux_col1;
      const uword x_col_end_p1 = x_col_start + x.n_cols;

      const bool outside_rows =
          ((x_row_start >= t_row_end_p1) || (t_row_start >= x_row_end_p1));
      const bool outside_cols =
          ((x_col_start >= t_col_end_p1) || (t_col_start >= x_col_end_p1));

      return ((outside_rows == false) && (outside_cols == false));
    }
  }
}

template <typename eT>
inline bool SpSubview<eT>::is_vec() const {
  return ((n_rows == 1) || (n_cols == 1));
}

template <typename eT>
inline SpSubview_row<eT> SpSubview<eT>::row(const uword row_num) {
  arma_debug_sigprint();

  arma_conform_check_bounds(row_num >= n_rows, "SpSubview::row(): out of bounds");

  return SpSubview_row<eT>(const_cast<SpMat<eT>&>(m), row_num + aux_row1, aux_col1,
                           n_cols);
}

template <typename eT>
inline const SpSubview_row<eT> SpSubview<eT>::row(const uword row_num) const {
  arma_debug_sigprint();

  arma_conform_check_bounds(row_num >= n_rows, "SpSubview::row(): out of bounds");

  return SpSubview_row<eT>(m, row_num + aux_row1, aux_col1, n_cols);
}

template <typename eT>
inline SpSubview_col<eT> SpSubview<eT>::col(const uword col_num) {
  arma_debug_sigprint();

  arma_conform_check_bounds(col_num >= n_cols, "SpSubview::col(): out of bounds");

  return SpSubview_col<eT>(const_cast<SpMat<eT>&>(m), col_num + aux_col1, aux_row1,
                           n_rows);
}

template <typename eT>
inline const SpSubview_col<eT> SpSubview<eT>::col(const uword col_num) const {
  arma_debug_sigprint();

  arma_conform_check_bounds(col_num >= n_cols, "SpSubview::col(): out of bounds");

  return SpSubview_col<eT>(m, col_num + aux_col1, aux_row1, n_rows);
}

template <typename eT>
inline SpSubview<eT> SpSubview<eT>::rows(const uword in_row1, const uword in_row2) {
  arma_debug_sigprint();

  arma_conform_check_bounds(
      (in_row1 > in_row2) || (in_row2 >= n_rows),
      "SpSubview::rows(): indices out of bounds or incorrectly used");

  return submat(in_row1, 0, in_row2, n_cols - 1);
}

template <typename eT>
inline const SpSubview<eT> SpSubview<eT>::rows(const uword in_row1,
                                               const uword in_row2) const {
  arma_debug_sigprint();

  arma_conform_check_bounds(
      (in_row1 > in_row2) || (in_row2 >= n_rows),
      "SpSubview::rows(): indices out of bounds or incorrectly used");

  return submat(in_row1, 0, in_row2, n_cols - 1);
}

template <typename eT>
inline SpSubview<eT> SpSubview<eT>::cols(const uword in_col1, const uword in_col2) {
  arma_debug_sigprint();

  arma_conform_check_bounds(
      (in_col1 > in_col2) || (in_col2 >= n_cols),
      "SpSubview::cols(): indices out of bounds or incorrectly used");

  return submat(0, in_col1, n_rows - 1, in_col2);
}

template <typename eT>
inline const SpSubview<eT> SpSubview<eT>::cols(const uword in_col1,
                                               const uword in_col2) const {
  arma_debug_sigprint();

  arma_conform_check_bounds(
      (in_col1 > in_col2) || (in_col2 >= n_cols),
      "SpSubview::cols(): indices out of bounds or incorrectly used");

  return submat(0, in_col1, n_rows - 1, in_col2);
}

template <typename eT>
inline SpSubview<eT> SpSubview<eT>::submat(const uword in_row1, const uword in_col1,
                                           const uword in_row2, const uword in_col2) {
  arma_debug_sigprint();

  arma_conform_check_bounds(
      (in_row1 > in_row2) || (in_col1 > in_col2) || (in_row2 >= n_rows) ||
          (in_col2 >= n_cols),
      "SpSubview::submat(): indices out of bounds or incorrectly used");

  return access::rw(m).submat(in_row1 + aux_row1, in_col1 + aux_col1, in_row2 + aux_row1,
                              in_col2 + aux_col1);
}

template <typename eT>
inline const SpSubview<eT> SpSubview<eT>::submat(const uword in_row1, const uword in_col1,
                                                 const uword in_row2,
                                                 const uword in_col2) const {
  arma_debug_sigprint();

  arma_conform_check_bounds(
      (in_row1 > in_row2) || (in_col1 > in_col2) || (in_row2 >= n_rows) ||
          (in_col2 >= n_cols),
      "SpSubview::submat(): indices out of bounds or incorrectly used");

  return m.submat(in_row1 + aux_row1, in_col1 + aux_col1, in_row2 + aux_row1,
                  in_col2 + aux_col1);
}

template <typename eT>
inline SpSubview<eT> SpSubview<eT>::submat(const span& row_span, const span& col_span) {
  arma_debug_sigprint();

  const bool row_all = row_span.whole;
  const bool col_all = row_span.whole;

  const uword in_row1 = row_all ? 0 : row_span.a;
  const uword in_row2 = row_all ? n_rows : row_span.b;

  const uword in_col1 = col_all ? 0 : col_span.a;
  const uword in_col2 = col_all ? n_cols : col_span.b;

  arma_conform_check_bounds(
      (row_all ? false : ((in_row1 > in_row2) || (in_row2 >= n_rows))) ||
          (col_all ? false : ((in_col1 > in_col2) || (in_col2 >= n_cols))),
      "SpSubview::submat(): indices out of bounds or incorrectly used");

  return submat(in_row1, in_col1, in_row2, in_col2);
}

template <typename eT>
inline const SpSubview<eT> SpSubview<eT>::submat(const span& row_span,
                                                 const span& col_span) const {
  arma_debug_sigprint();

  const bool row_all = row_span.whole;
  const bool col_all = row_span.whole;

  const uword in_row1 = row_all ? 0 : row_span.a;
  const uword in_row2 = row_all ? n_rows - 1 : row_span.b;

  const uword in_col1 = col_all ? 0 : col_span.a;
  const uword in_col2 = col_all ? n_cols - 1 : col_span.b;

  arma_conform_check_bounds(
      (row_all ? false : ((in_row1 > in_row2) || (in_row2 >= n_rows))) ||
          (col_all ? false : ((in_col1 > in_col2) || (in_col2 >= n_cols))),
      "SpSubview::submat(): indices out of bounds or incorrectly used");

  return submat(in_row1, in_col1, in_row2, in_col2);
}

template <typename eT>
inline SpSubview<eT> SpSubview<eT>::operator()(const uword row_num,
                                               const span& col_span) {
  arma_debug_sigprint();

  return submat(span(row_num, row_num), col_span);
}

template <typename eT>
inline const SpSubview<eT> SpSubview<eT>::operator()(const uword row_num,
                                                     const span& col_span) const {
  arma_debug_sigprint();

  return submat(span(row_num, row_num), col_span);
}

template <typename eT>
inline SpSubview<eT> SpSubview<eT>::operator()(const span& row_span,
                                               const uword col_num) {
  arma_debug_sigprint();

  return submat(row_span, span(col_num, col_num));
}

template <typename eT>
inline const SpSubview<eT> SpSubview<eT>::operator()(const span& row_span,
                                                     const uword col_num) const {
  arma_debug_sigprint();

  return submat(row_span, span(col_num, col_num));
}

template <typename eT>
inline SpSubview<eT> SpSubview<eT>::operator()(const span& row_span,
                                               const span& col_span) {
  arma_debug_sigprint();

  return submat(row_span, col_span);
}

template <typename eT>
inline const SpSubview<eT> SpSubview<eT>::operator()(const span& row_span,
                                                     const span& col_span) const {
  arma_debug_sigprint();

  return submat(row_span, col_span);
}

template <typename eT>
inline void SpSubview<eT>::swap_rows(const uword in_row1, const uword in_row2) {
  arma_debug_sigprint();

  arma_conform_check((in_row1 >= n_rows) || (in_row2 >= n_rows),
                     "SpSubview::swap_rows(): invalid row index");

  const uword lstart_col = aux_col1;
  const uword lend_col = aux_col1 + n_cols;

  for (uword c = lstart_col; c < lend_col; ++c) {
    const eT val = access::rw(m).at(in_row1 + aux_row1, c);
    access::rw(m).at(in_row2 + aux_row1, c) = eT(access::rw(m).at(in_row1 + aux_row1, c));
    access::rw(m).at(in_row1 + aux_row1, c) = val;
  }
}

template <typename eT>
inline void SpSubview<eT>::swap_cols(const uword in_col1, const uword in_col2) {
  arma_debug_sigprint();

  arma_conform_check((in_col1 >= n_cols) || (in_col2 >= n_cols),
                     "SpSubview::swap_cols(): invalid column index");

  const uword lstart_row = aux_row1;
  const uword lend_row = aux_row1 + n_rows;

  for (uword r = lstart_row; r < lend_row; ++r) {
    const eT val = access::rw(m).at(r, in_col1 + aux_col1);
    access::rw(m).at(r, in_col1 + aux_col1) = eT(access::rw(m).at(r, in_col2 + aux_col1));
    access::rw(m).at(r, in_col2 + aux_col1) = val;
  }
}

template <typename eT>
inline typename SpSubview<eT>::iterator SpSubview<eT>::begin() {
  m.sync_csc();

  return iterator(*this);
}

template <typename eT>
inline typename SpSubview<eT>::const_iterator SpSubview<eT>::begin() const {
  m.sync_csc();

  return const_iterator(*this);
}

template <typename eT>
inline typename SpSubview<eT>::const_iterator SpSubview<eT>::cbegin() const {
  m.sync_csc();

  return const_iterator(*this);
}

template <typename eT>
inline typename SpSubview<eT>::iterator SpSubview<eT>::begin_col(const uword col_num) {
  m.sync_csc();

  return iterator(*this, 0, col_num);
}

template <typename eT>
inline typename SpSubview<eT>::const_iterator SpSubview<eT>::begin_col(
    const uword col_num) const {
  m.sync_csc();

  return const_iterator(*this, 0, col_num);
}

template <typename eT>
inline typename SpSubview<eT>::row_iterator SpSubview<eT>::begin_row(
    const uword row_num) {
  m.sync_csc();

  return row_iterator(*this, row_num, 0);
}

template <typename eT>
inline typename SpSubview<eT>::const_row_iterator SpSubview<eT>::begin_row(
    const uword row_num) const {
  m.sync_csc();

  return const_row_iterator(*this, row_num, 0);
}

template <typename eT>
inline typename SpSubview<eT>::iterator SpSubview<eT>::end() {
  m.sync_csc();

  return iterator(*this, 0, n_cols, n_nonzero, m.n_nonzero - n_nonzero);
}

template <typename eT>
inline typename SpSubview<eT>::const_iterator SpSubview<eT>::end() const {
  m.sync_csc();

  return const_iterator(*this, 0, n_cols, n_nonzero, m.n_nonzero - n_nonzero);
}

template <typename eT>
inline typename SpSubview<eT>::const_iterator SpSubview<eT>::cend() const {
  m.sync_csc();

  return const_iterator(*this, 0, n_cols, n_nonzero, m.n_nonzero - n_nonzero);
}

template <typename eT>
inline typename SpSubview<eT>::row_iterator SpSubview<eT>::end_row() {
  m.sync_csc();

  return row_iterator(*this, n_nonzero);
}

template <typename eT>
inline typename SpSubview<eT>::const_row_iterator SpSubview<eT>::end_row() const {
  m.sync_csc();

  return const_row_iterator(*this, n_nonzero);
}

template <typename eT>
inline typename SpSubview<eT>::row_iterator SpSubview<eT>::end_row(const uword row_num) {
  m.sync_csc();

  return row_iterator(*this, row_num + 1, 0);
}

template <typename eT>
inline typename SpSubview<eT>::const_row_iterator SpSubview<eT>::end_row(
    const uword row_num) const {
  m.sync_csc();

  return const_row_iterator(*this, row_num + 1, 0);
}

template <typename eT>
arma_inline bool SpSubview<eT>::is_alias(const SpMat<eT>& X) const {
  return m.is_alias(X);
}

template <typename eT>
inline eT& SpSubview<eT>::insert_element(const uword in_row, const uword in_col,
                                         const eT in_val) {
  arma_debug_sigprint();

  // This may not actually insert an element.
  const uword old_n_nonzero = m.n_nonzero;
  eT& retval = access::rw(m).insert_element(in_row + aux_row1, in_col + aux_col1, in_val);
  // Update n_nonzero (if necessary).
  access::rw(n_nonzero) += (m.n_nonzero - old_n_nonzero);

  return retval;
}

template <typename eT>
inline void SpSubview<eT>::delete_element(const uword in_row, const uword in_col) {
  arma_debug_sigprint();

  // This may not actually delete an element.
  const uword old_n_nonzero = m.n_nonzero;
  access::rw(m).delete_element(in_row + aux_row1, in_col + aux_col1);
  access::rw(n_nonzero) -= (old_n_nonzero - m.n_nonzero);
}

template <typename eT>
inline void SpSubview<eT>::invalidate_cache() const {
  arma_debug_sigprint();

  m.invalidate_cache();
}

//
//
//

template <typename eT>
inline SpSubview_col<eT>::SpSubview_col(const SpMat<eT>& in_m, const uword in_col)
    : SpSubview<eT>(in_m, 0, in_col, in_m.n_rows, 1) {
  arma_debug_sigprint();
}

template <typename eT>
inline SpSubview_col<eT>::SpSubview_col(const SpMat<eT>& in_m, const uword in_col,
                                        const uword in_row1, const uword in_n_rows)
    : SpSubview<eT>(in_m, in_row1, in_col, in_n_rows, 1) {
  arma_debug_sigprint();
}

template <typename eT>
inline void SpSubview_col<eT>::operator=(const SpSubview<eT>& x) {
  arma_debug_sigprint();

  SpSubview<eT>::operator=(x);
}

template <typename eT>
inline void SpSubview_col<eT>::operator=(const SpSubview_col<eT>& x) {
  arma_debug_sigprint();

  SpSubview<eT>::operator=(x);  // interprets 'SpSubview_col' as 'SpSubview'
}

template <typename eT>
template <typename T1>
inline void SpSubview_col<eT>::operator=(const SpBase<eT, T1>& x) {
  arma_debug_sigprint();

  SpSubview<eT>::operator=(x);
}

template <typename eT>
template <typename T1>
inline void SpSubview_col<eT>::operator=(const Base<eT, T1>& x) {
  arma_debug_sigprint();

  SpSubview<eT>::operator=(x);
}

template <typename eT>
inline const SpOp<SpSubview_col<eT>, spop_htrans> SpSubview_col<eT>::t() const {
  return SpOp<SpSubview_col<eT>, spop_htrans>(*this);
}

template <typename eT>
inline const SpOp<SpSubview_col<eT>, spop_htrans> SpSubview_col<eT>::ht() const {
  return SpOp<SpSubview_col<eT>, spop_htrans>(*this);
}

template <typename eT>
inline const SpOp<SpSubview_col<eT>, spop_strans> SpSubview_col<eT>::st() const {
  return SpOp<SpSubview_col<eT>, spop_strans>(*this);
}

template <typename eT>
inline const SpToDOp<SpSubview_col<eT>, op_sp_as_dense> SpSubview_col<eT>::as_dense()
    const {
  return SpToDOp<SpSubview_col<eT>, op_sp_as_dense>(*this);
}

//
//
//

template <typename eT>
inline SpSubview_row<eT>::SpSubview_row(const SpMat<eT>& in_m, const uword in_row)
    : SpSubview<eT>(in_m, in_row, 0, 1, in_m.n_cols) {
  arma_debug_sigprint();
}

template <typename eT>
inline SpSubview_row<eT>::SpSubview_row(const SpMat<eT>& in_m, const uword in_row,
                                        const uword in_col1, const uword in_n_cols)
    : SpSubview<eT>(in_m, in_row, in_col1, 1, in_n_cols) {
  arma_debug_sigprint();
}

template <typename eT>
inline void SpSubview_row<eT>::operator=(const SpSubview<eT>& x) {
  arma_debug_sigprint();

  SpSubview<eT>::operator=(x);
}

template <typename eT>
inline void SpSubview_row<eT>::operator=(const SpSubview_row<eT>& x) {
  arma_debug_sigprint();

  SpSubview<eT>::operator=(x);  // interprets 'SpSubview_row' as 'SpSubview'
}

template <typename eT>
template <typename T1>
inline void SpSubview_row<eT>::operator=(const SpBase<eT, T1>& x) {
  arma_debug_sigprint();

  SpSubview<eT>::operator=(x);
}

template <typename eT>
template <typename T1>
inline void SpSubview_row<eT>::operator=(const Base<eT, T1>& x) {
  arma_debug_sigprint();

  SpSubview<eT>::operator=(x);
}

template <typename eT>
inline const SpOp<SpSubview_row<eT>, spop_htrans> SpSubview_row<eT>::t() const {
  return SpOp<SpSubview_row<eT>, spop_htrans>(*this);
}

template <typename eT>
inline const SpOp<SpSubview_row<eT>, spop_htrans> SpSubview_row<eT>::ht() const {
  return SpOp<SpSubview_row<eT>, spop_htrans>(*this);
}

template <typename eT>
inline const SpOp<SpSubview_row<eT>, spop_strans> SpSubview_row<eT>::st() const {
  return SpOp<SpSubview_row<eT>, spop_strans>(*this);
}

template <typename eT>
inline const SpToDOp<SpSubview_row<eT>, op_sp_as_dense> SpSubview_row<eT>::as_dense()
    const {
  return SpToDOp<SpSubview_row<eT>, op_sp_as_dense>(*this);
}

//! @}
