// 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_col_list
//! @{

template <typename eT, typename T1>
inline SpSubview_col_list<eT, T1>::~SpSubview_col_list() {
  arma_debug_sigprint();
}

template <typename eT, typename T1>
arma_inline SpSubview_col_list<eT, T1>::SpSubview_col_list(const SpMat<eT>& in_m,
                                                           const Base<uword, T1>& in_ci)
    : m(in_m), U_ci(in_ci.get_ref()) {
  arma_debug_sigprint();

  const umat& ci = U_ci.M;
  const uword* ci_mem = ci.memptr();
  const uword ci_n_elem = ci.n_elem;

  arma_conform_check(((ci.is_vec() == false) && (ci.is_empty() == false)),
                     "SpMat::cols(): given object must be a vector");

  for (uword ci_count = 0; ci_count < ci_n_elem; ++ci_count) {
    const uword i = ci_mem[ci_count];

    arma_conform_check_bounds((i >= in_m.n_cols), "SpMat::cols(): index out of bounds");
  }
}

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

  SpMat<eT> tmp(*this);

  tmp.for_each(F);

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

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

  const SpMat<eT> tmp(*this);

  tmp.for_each(F);
}

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

  SpMat<eT> tmp(*this);

  tmp.transform(F);

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

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

  SpMat<eT> tmp(*this);

  tmp.replace(old_val, new_val);

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

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

  SpMat<eT> tmp(*this);

  tmp.clean(threshold);

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

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

  Mat<eT> tmp(m.n_rows, U_ci.M.n_elem, arma_nozeros_indicator());
  tmp.fill(val);

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

template <typename eT, typename T1>
inline void SpSubview_col_list<eT, T1>::zeros() {
  arma_debug_sigprint();

  SpMat<eT>& m_local = const_cast<SpMat<eT>&>(m);

  const umat& ci = U_ci.M;
  const uword* ci_mem = ci.memptr();
  const uword ci_n_elem = ci.n_elem;

  m_local.sync_csc();
  m_local.invalidate_cache();

  for (uword ci_count = 0; ci_count < ci_n_elem; ++ci_count) {
    const uword i = ci_mem[ci_count];

    const uword col_n_nonzero = m_local.col_ptrs[i + 1] - m_local.col_ptrs[i];

    uword offset = m_local.col_ptrs[i];

    for (uword j = 0; j < col_n_nonzero; ++j) {
      access::rw(m_local.values[offset]) = eT(0);

      ++offset;
    }
  }

  m_local.remove_zeros();
}

template <typename eT, typename T1>
inline void SpSubview_col_list<eT, T1>::ones() {
  arma_debug_sigprint();

  const Mat<eT> tmp(m.n_rows, U_ci.M.n_elem, fill::ones);

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

template <typename eT, typename T1>
inline void SpSubview_col_list<eT, T1>::operator+=(const eT val) {
  arma_debug_sigprint();

  const SpMat<eT> tmp1(*this);

  Mat<eT> tmp2(tmp1.n_rows, tmp1.n_cols, arma_nozeros_indicator());
  tmp2.fill(val);

  const Mat<eT> tmp3 = tmp1 + tmp2;

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

template <typename eT, typename T1>
inline void SpSubview_col_list<eT, T1>::operator-=(const eT val) {
  arma_debug_sigprint();

  const SpMat<eT> tmp1(*this);

  Mat<eT> tmp2(tmp1.n_rows, tmp1.n_cols, arma_nozeros_indicator());
  tmp2.fill(val);

  const Mat<eT> tmp3 = tmp1 - tmp2;

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

template <typename eT, typename T1>
inline void SpSubview_col_list<eT, T1>::operator*=(const eT val) {
  arma_debug_sigprint();

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

  SpMat<eT>& m_local = const_cast<SpMat<eT>&>(m);

  const umat& ci = U_ci.M;
  const uword* ci_mem = ci.memptr();
  const uword ci_n_elem = ci.n_elem;

  m_local.sync_csc();
  m_local.invalidate_cache();

  bool has_zero = false;

  for (uword ci_count = 0; ci_count < ci_n_elem; ++ci_count) {
    const uword i = ci_mem[ci_count];

    const uword col_n_nonzero = m_local.col_ptrs[i + 1] - m_local.col_ptrs[i];

    uword offset = m_local.col_ptrs[i];

    for (uword j = 0; j < col_n_nonzero; ++j) {
      eT& m_local_val = access::rw(m_local.values[offset]);

      m_local_val *= val;

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

      ++offset;
    }
  }

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

template <typename eT, typename T1>
inline void SpSubview_col_list<eT, T1>::operator/=(const eT val) {
  arma_debug_sigprint();

  const SpMat<eT> tmp1(*this);

  Mat<eT> tmp2(tmp1.n_rows, tmp1.n_cols, arma_nozeros_indicator());
  tmp2.fill(val);

  const SpMat<eT> tmp3 = tmp1 / tmp2;

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

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

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

  SpMat<eT>& m_local = const_cast<SpMat<eT>&>(m);

  const umat& ci = U_ci.M;
  const uword* ci_mem = ci.memptr();
  const uword ci_n_elem = ci.n_elem;

  arma_conform_assert_same_size(m_local.n_rows, ci_n_elem, X.n_rows, X.n_cols,
                                "SpMat::cols()");

  const uword X_n_elem = X.n_elem;
  const eT* X_mem = X.memptr();

  uword X_n_nonzero = 0;

  for (uword i = 0; i < X_n_elem; ++i) {
    X_n_nonzero += (X_mem[i] != eT(0)) ? uword(1) : uword(0);
  }

  SpMat<eT> Y(arma_reserve_indicator(), X.n_rows, m_local.n_cols, X_n_nonzero);

  uword count = 0;

  for (uword ci_count = 0; ci_count < ci_n_elem; ++ci_count) {
    const uword i = ci_mem[ci_count];

    for (uword row = 0; row < X.n_rows; ++row) {
      const eT X_val = (*X_mem);
      ++X_mem;

      if (X_val != eT(0)) {
        access::rw(Y.row_indices[count]) = row;
        access::rw(Y.values[count]) = X_val;
        ++count;
        ++access::rw(Y.col_ptrs[i + 1]);
      }
    }
  }

  // fix the column pointers
  for (uword i = 0; i < Y.n_cols; ++i) {
    access::rw(Y.col_ptrs[i + 1]) += Y.col_ptrs[i];
  }

  (*this).zeros();

  SpMat<eT> tmp = m_local + Y;

  m_local.steal_mem(tmp);
}

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

  const Mat<eT> tmp = SpMat<eT>(*this) + x.get_ref();

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

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

  const Mat<eT> tmp = SpMat<eT>(*this) - x.get_ref();

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

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

  const SpMat<eT> tmp = SpMat<eT>(*this) % x.get_ref();

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

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

  const SpMat<eT> tmp = SpMat<eT>(*this) / x.get_ref();

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

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

  const SpMat<eT> tmp(x);

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

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

  const SpMat<eT> tmp(x);

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

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

  const unwrap_spmat<expr> U(x.get_ref());
  const SpMat<eT>& X = U.M;

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

    (*this).operator=(tmp);

    return;
  }

  SpMat<eT>& m_local = const_cast<SpMat<eT>&>(m);

  const umat& ci = U_ci.M;
  const uword* ci_mem = ci.memptr();
  const uword ci_n_elem = ci.n_elem;

  arma_conform_assert_same_size(m_local.n_rows, ci_n_elem, X.n_rows, X.n_cols,
                                "SpMat::cols()");

  SpMat<eT> Y(arma_reserve_indicator(), X.n_rows, m_local.n_cols, X.n_nonzero);

  uword count = 0;

  for (uword ci_count = 0; ci_count < ci_n_elem; ++ci_count) {
    const uword i = ci_mem[ci_count];

    typename SpMat<eT>::const_col_iterator X_col_it = X.begin_col(ci_count);
    typename SpMat<eT>::const_col_iterator X_col_it_end = X.end_col(ci_count);

    while (X_col_it != X_col_it_end) {
      access::rw(Y.row_indices[count]) = X_col_it.row();
      access::rw(Y.values[count]) = (*X_col_it);
      ++count;
      ++access::rw(Y.col_ptrs[i + 1]);
      ++X_col_it;
    }
  }

  // fix the column pointers
  for (uword i = 0; i < Y.n_cols; ++i) {
    access::rw(Y.col_ptrs[i + 1]) += Y.col_ptrs[i];
  }

  (*this).zeros();

  SpMat<eT> tmp = m_local + Y;

  m_local.steal_mem(tmp);
}

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

  const SpMat<eT> tmp = SpMat<eT>(*this) + x.get_ref();

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

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

  const SpMat<eT> tmp = SpMat<eT>(*this) - x.get_ref();

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

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

  const SpMat<eT> tmp = SpMat<eT>(*this) % x.get_ref();

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

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

  SpMat<eT> tmp(*this);

  tmp /= x.get_ref();

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

//
//

template <typename eT, typename T1>
inline void SpSubview_col_list<eT, T1>::extract(SpMat<eT>& out,
                                                const SpSubview_col_list<eT, T1>& in) {
  arma_debug_sigprint();

  // NOTE: aliasing is handled by SpMat<eT>::operator=(const SpSubview_col_list<eT,T1>&
  // in)

  const umat& ci = in.U_ci.M;
  const uword* ci_mem = ci.memptr();
  const uword ci_n_elem = ci.n_elem;

  const SpMat<eT>& in_m = in.m;

  in_m.sync_csc();

  uword total_n_nonzero = 0;

  for (uword ci_count = 0; ci_count < ci_n_elem; ++ci_count) {
    const uword i = ci_mem[ci_count];

    const uword col_n_nonzero = in_m.col_ptrs[i + 1] - in_m.col_ptrs[i];

    total_n_nonzero += col_n_nonzero;
  }

  out.reserve(in.m.n_rows, ci_n_elem, total_n_nonzero);

  uword out_n_nonzero = 0;
  uword out_col_count = 0;

  for (uword ci_count = 0; ci_count < ci_n_elem; ++ci_count) {
    const uword i = ci_mem[ci_count];

    const uword col_n_nonzero = in_m.col_ptrs[i + 1] - in_m.col_ptrs[i];

    uword offset = in_m.col_ptrs[i];

    for (uword j = 0; j < col_n_nonzero; ++j) {
      const eT val = in_m.values[offset];
      const uword row = in_m.row_indices[offset];

      ++offset;

      access::rw(out.values[out_n_nonzero]) = val;
      access::rw(out.row_indices[out_n_nonzero]) = row;

      access::rw(out.col_ptrs[out_col_count + 1])++;

      ++out_n_nonzero;
    }

    ++out_col_count;
  }

  // fix the column pointers
  for (uword i = 0; i < out.n_cols; ++i) {
    access::rw(out.col_ptrs[i + 1]) += out.col_ptrs[i];
  }
}

template <typename eT, typename T1>
inline void SpSubview_col_list<eT, T1>::plus_inplace(SpMat<eT>& out,
                                                     const SpSubview_col_list& in) {
  arma_debug_sigprint();

  const SpMat<eT> tmp(in);

  out += tmp;
}

template <typename eT, typename T1>
inline void SpSubview_col_list<eT, T1>::minus_inplace(SpMat<eT>& out,
                                                      const SpSubview_col_list& in) {
  arma_debug_sigprint();

  const SpMat<eT> tmp(in);

  out -= tmp;
}

template <typename eT, typename T1>
inline void SpSubview_col_list<eT, T1>::schur_inplace(SpMat<eT>& out,
                                                      const SpSubview_col_list& in) {
  arma_debug_sigprint();

  const SpMat<eT> tmp(in);

  out %= tmp;
}

template <typename eT, typename T1>
inline void SpSubview_col_list<eT, T1>::div_inplace(SpMat<eT>& out,
                                                    const SpSubview_col_list& in) {
  arma_debug_sigprint();

  const SpMat<eT> tmp(in);

  out /= tmp;
}

//! @}
