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

inline std::ostream& get_cout_stream() { return (ARMA_COUT_STREAM); }

inline std::ostream& get_cerr_stream() { return (ARMA_CERR_STREAM); }

arma_deprecated inline std::ostream& get_stream_err1() { return get_cerr_stream(); }

arma_deprecated inline std::ostream& get_stream_err2() { return get_cerr_stream(); }

arma_frown(
    "this function does nothing; instead use ARMA_COUT_STREAM or ARMA_WARN_LEVEL; see "
    "documentation") inline void set_cout_stream(const std::ostream&) {}

arma_frown(
    "this function does nothing; instead use ARMA_CERR_STREAM or ARMA_WARN_LEVEL; see "
    "documentation") inline void set_cerr_stream(const std::ostream&) {}

arma_frown(
    "this function does nothing; instead use ARMA_CERR_STREAM or ARMA_WARN_LEVEL; see "
    "documentation") inline void set_stream_err1(const std::ostream&) {}

arma_frown(
    "this function does nothing; instead use ARMA_CERR_STREAM or ARMA_WARN_LEVEL; see "
    "documentation") inline void set_stream_err2(const std::ostream&) {}

template <typename T>
arma_frown(
    "this function does nothing; instead use ARMA_COUT_STREAM or ARMA_WARN_LEVEL; see "
    "documentation") inline std::ostream& arma_cout_stream(std::ostream*) {
  return (ARMA_COUT_STREAM);
}

template <typename T>
arma_frown(
    "this function does nothing; instead use ARMA_CERR_STREAM or ARMA_WARN_LEVEL; see "
    "documentation") inline std::ostream& arma_cerr_stream(std::ostream*) {
  return (ARMA_CERR_STREAM);
}

//! print a message to get_cerr_stream() and throw logic_error exception
template <typename T1>
arma_cold arma_noinline static void arma_stop_logic_error(const T1& x) {
#if defined(ARMA_PRINT_EXCEPTIONS)
  { get_cerr_stream() << "\nerror: " << x << std::endl; }
#endif

  throw std::logic_error(std::string(x));
}

arma_cold arma_noinline static void arma_stop_logic_error(const char* x, const char* y) {
  arma_stop_logic_error(std::string(x) + std::string(y));
}

//! print a message to get_cerr_stream() and throw out_of_range exception
template <typename T1>
arma_cold arma_noinline static void arma_stop_bounds_error(const T1& x) {
#if defined(ARMA_PRINT_EXCEPTIONS)
  { get_cerr_stream() << "\nerror: " << x << std::endl; }
#endif

  throw std::out_of_range(std::string(x));
}

//! print a message to get_cerr_stream() and throw bad_alloc exception
template <typename T1>
arma_cold arma_noinline static void arma_stop_bad_alloc(const T1& x) {
#if defined(ARMA_PRINT_EXCEPTIONS)
  { get_cerr_stream() << "\nerror: " << x << std::endl; }
#else
  { arma_ignore(x); }
#endif

  throw std::bad_alloc();
}

//! print a message to get_cerr_stream() and throw runtime_error exception
template <typename T1>
arma_cold arma_noinline static void arma_stop_runtime_error(const T1& x) {
#if defined(ARMA_PRINT_EXCEPTIONS)
  { get_cerr_stream() << "\nerror: " << x << std::endl; }
#endif

  throw std::runtime_error(std::string(x));
}

//
// arma_print

arma_cold inline void arma_print() { get_cerr_stream() << std::endl; }

template <typename T1>
arma_cold arma_noinline static void arma_print(const T1& x) {
  get_cerr_stream() << x << std::endl;
}

template <typename T1, typename T2>
arma_cold arma_noinline static void arma_print(const T1& x, const T2& y) {
  get_cerr_stream() << x << y << std::endl;
}

template <typename T1, typename T2, typename T3>
arma_cold arma_noinline static void arma_print(const T1& x, const T2& y, const T3& z) {
  get_cerr_stream() << x << y << z << std::endl;
}

//
// arma_sigprint

//! print a message to the log stream with a preceding @ character.
//! by default the log stream is cout.
//! used for printing the signature of a function
//! (see the arma_extra_debug_sigprint macro)
inline void arma_sigprint(const char* x) { get_cerr_stream() << "@ " << x; }

//
// arma_bktprint

inline void arma_bktprint() { get_cerr_stream() << std::endl; }

template <typename T1>
inline void arma_bktprint(const T1& x) {
  get_cerr_stream() << " [" << x << ']' << std::endl;
}

template <typename T1, typename T2>
inline void arma_bktprint(const T1& x, const T2& y) {
  get_cerr_stream() << " [" << x << y << ']' << std::endl;
}

//
// arma_thisprint

inline void arma_thisprint(const void* this_ptr) {
  get_cerr_stream() << " [this = " << this_ptr << ']' << std::endl;
}

//
// arma_warn

//! print a message to the warn stream
template <typename T1>
arma_cold arma_noinline static void arma_warn(const T1& arg1) {
  get_cerr_stream() << "\nwarning: " << arg1 << std::endl;
}

template <typename T1, typename T2>
arma_cold arma_noinline static void arma_warn(const T1& arg1, const T2& arg2) {
  get_cerr_stream() << "\nwarning: " << arg1 << arg2 << std::endl;
}

template <typename T1, typename T2, typename T3>
arma_cold arma_noinline static void arma_warn(const T1& arg1, const T2& arg2,
                                              const T3& arg3) {
  get_cerr_stream() << "\nwarning: " << arg1 << arg2 << arg3 << std::endl;
}

template <typename T1, typename T2, typename T3, typename T4>
arma_cold arma_noinline static void arma_warn(const T1& arg1, const T2& arg2,
                                              const T3& arg3, const T4& arg4) {
  get_cerr_stream() << "\nwarning: " << arg1 << arg2 << arg3 << arg4 << std::endl;
}

//
// arma_warn_level

template <typename T1>
inline void arma_warn_level(const uword level, const T1& arg1) {
  constexpr uword config_level =
      (sword(ARMA_WARN_LEVEL) > 0) ? uword(ARMA_WARN_LEVEL) : uword(0);

  if ((config_level > 0) && (level <= config_level)) {
    arma_warn(arg1);
  }
}

template <typename T1, typename T2>
inline void arma_warn_level(const uword level, const T1& arg1, const T2& arg2) {
  constexpr uword config_level =
      (sword(ARMA_WARN_LEVEL) > 0) ? uword(ARMA_WARN_LEVEL) : uword(0);

  if ((config_level > 0) && (level <= config_level)) {
    arma_warn(arg1, arg2);
  }
}

template <typename T1, typename T2, typename T3>
inline void arma_warn_level(const uword level, const T1& arg1, const T2& arg2,
                            const T3& arg3) {
  constexpr uword config_level =
      (sword(ARMA_WARN_LEVEL) > 0) ? uword(ARMA_WARN_LEVEL) : uword(0);

  if ((config_level > 0) && (level <= config_level)) {
    arma_warn(arg1, arg2, arg3);
  }
}

template <typename T1, typename T2, typename T3, typename T4>
inline void arma_warn_level(const uword level, const T1& arg1, const T2& arg2,
                            const T3& arg3, const T4& arg4) {
  constexpr uword config_level =
      (sword(ARMA_WARN_LEVEL) > 0) ? uword(ARMA_WARN_LEVEL) : uword(0);

  if ((config_level > 0) && (level <= config_level)) {
    arma_warn(arg1, arg2, arg3, arg4);
  }
}

//
// arma_check

//! if state is true, abort program
template <typename T1>
arma_hot inline void arma_check(const bool state, const T1& x) {
  if (state) {
    arma_stop_logic_error(arma_str::str_wrapper(x));
  }
}

template <typename Functor>
arma_hot inline void arma_check(const bool state, const char* x, const Functor& fn) {
  if (state) {
    fn();
    arma_stop_logic_error(x);
  }
}

arma_hot inline void arma_check(const bool state, const char* x, const char* y) {
  if (state) {
    arma_stop_logic_error(x, y);
  }
}

template <typename Functor>
arma_hot inline void arma_check(const bool state, const char* x, const char* y,
                                const Functor& fn) {
  if (state) {
    fn();
    arma_stop_logic_error(x, y);
  }
}

template <typename T1>
arma_hot inline void arma_check_bounds(const bool state, const T1& x) {
  if (state) {
    arma_stop_bounds_error(arma_str::str_wrapper(x));
  }
}

template <typename T1>
arma_hot inline void arma_check_bad_alloc(const bool state, const T1& x) {
  if (state) {
    arma_stop_bad_alloc(x);
  }
}

//
// arma_set_error

arma_hot arma_inline void arma_set_error(bool& err_state, char*& err_msg,
                                         const bool expression, const char* message) {
  if (expression) {
    err_state = true;
    err_msg = const_cast<char*>(message);
  }
}

//
// functions for generating strings indicating size errors

arma_cold arma_noinline static std::string arma_incompat_size_string(const uword A_n_rows,
                                                                     const uword A_n_cols,
                                                                     const uword B_n_rows,
                                                                     const uword B_n_cols,
                                                                     const char* x) {
  std::ostringstream tmp;

  tmp << x << ": incompatible matrix dimensions: " << A_n_rows << 'x' << A_n_cols
      << " and " << B_n_rows << 'x' << B_n_cols;

  return tmp.str();
}

arma_cold arma_noinline static std::string arma_incompat_size_string(
    const uword A_n_rows, const uword A_n_cols, const uword A_n_slices,
    const uword B_n_rows, const uword B_n_cols, const uword B_n_slices, const char* x) {
  std::ostringstream tmp;

  tmp << x << ": incompatible cube dimensions: " << A_n_rows << 'x' << A_n_cols << 'x'
      << A_n_slices << " and " << B_n_rows << 'x' << B_n_cols << 'x' << B_n_slices;

  return tmp.str();
}

template <typename eT>
arma_cold arma_noinline static std::string arma_incompat_size_string(
    const subview_cube<eT>& Q, const Mat<eT>& A, const char* x) {
  std::ostringstream tmp;

  tmp << x << ": interpreting matrix as cube with dimensions: " << A.n_rows << 'x'
      << A.n_cols << 'x' << 1 << " or " << A.n_rows << 'x' << 1 << 'x' << A.n_cols
      << " or " << 1 << 'x' << A.n_rows << 'x' << A.n_cols
      << " is incompatible with cube dimensions: " << Q.n_rows << 'x' << Q.n_cols << 'x'
      << Q.n_slices;

  return tmp.str();
}

//
// functions for checking whether two dense matrices have the same dimensions

arma_hot arma_inline void arma_assert_same_size(const uword A_n_rows,
                                                const uword A_n_cols,
                                                const uword B_n_rows,
                                                const uword B_n_cols, const char* x) {
  if ((A_n_rows != B_n_rows) || (A_n_cols != B_n_cols)) {
    arma_stop_logic_error(
        arma_incompat_size_string(A_n_rows, A_n_cols, B_n_rows, B_n_cols, x));
  }
}

//! stop if given matrices have different sizes
template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const Mat<eT1>& A, const Mat<eT2>& B,
                                           const char* x) {
  const uword A_n_rows = A.n_rows;
  const uword A_n_cols = A.n_cols;

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

  if ((A_n_rows != B_n_rows) || (A_n_cols != B_n_cols)) {
    arma_stop_logic_error(
        arma_incompat_size_string(A_n_rows, A_n_cols, B_n_rows, B_n_cols, x));
  }
}

//! stop if given proxies have different sizes
template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const Proxy<eT1>& A, const Proxy<eT2>& B,
                                           const char* x) {
  const uword A_n_rows = A.get_n_rows();
  const uword A_n_cols = A.get_n_cols();

  const uword B_n_rows = B.get_n_rows();
  const uword B_n_cols = B.get_n_cols();

  if ((A_n_rows != B_n_rows) || (A_n_cols != B_n_cols)) {
    arma_stop_logic_error(
        arma_incompat_size_string(A_n_rows, A_n_cols, B_n_rows, B_n_cols, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const subview<eT1>& A, const subview<eT2>& B,
                                           const char* x) {
  const uword A_n_rows = A.n_rows;
  const uword A_n_cols = A.n_cols;

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

  if ((A_n_rows != B_n_rows) || (A_n_cols != B_n_cols)) {
    arma_stop_logic_error(
        arma_incompat_size_string(A_n_rows, A_n_cols, B_n_rows, B_n_cols, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const Mat<eT1>& A, const subview<eT2>& B,
                                           const char* x) {
  const uword A_n_rows = A.n_rows;
  const uword A_n_cols = A.n_cols;

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

  if ((A_n_rows != B_n_rows) || (A_n_cols != B_n_cols)) {
    arma_stop_logic_error(
        arma_incompat_size_string(A_n_rows, A_n_cols, B_n_rows, B_n_cols, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const subview<eT1>& A, const Mat<eT2>& B,
                                           const char* x) {
  const uword A_n_rows = A.n_rows;
  const uword A_n_cols = A.n_cols;

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

  if ((A_n_rows != B_n_rows) || (A_n_cols != B_n_cols)) {
    arma_stop_logic_error(
        arma_incompat_size_string(A_n_rows, A_n_cols, B_n_rows, B_n_cols, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const Mat<eT1>& A, const Proxy<eT2>& B,
                                           const char* x) {
  const uword A_n_rows = A.n_rows;
  const uword A_n_cols = A.n_cols;

  const uword B_n_rows = B.get_n_rows();
  const uword B_n_cols = B.get_n_cols();

  if ((A_n_rows != B_n_rows) || (A_n_cols != B_n_cols)) {
    arma_stop_logic_error(
        arma_incompat_size_string(A_n_rows, A_n_cols, B_n_rows, B_n_cols, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const Proxy<eT1>& A, const Mat<eT2>& B,
                                           const char* x) {
  const uword A_n_rows = A.get_n_rows();
  const uword A_n_cols = A.get_n_cols();

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

  if ((A_n_rows != B_n_rows) || (A_n_cols != B_n_cols)) {
    arma_stop_logic_error(
        arma_incompat_size_string(A_n_rows, A_n_cols, B_n_rows, B_n_cols, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const Proxy<eT1>& A, const subview<eT2>& B,
                                           const char* x) {
  const uword A_n_rows = A.get_n_rows();
  const uword A_n_cols = A.get_n_cols();

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

  if ((A_n_rows != B_n_rows) || (A_n_cols != B_n_cols)) {
    arma_stop_logic_error(
        arma_incompat_size_string(A_n_rows, A_n_cols, B_n_rows, B_n_cols, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const subview<eT1>& A, const Proxy<eT2>& B,
                                           const char* x) {
  const uword A_n_rows = A.n_rows;
  const uword A_n_cols = A.n_cols;

  const uword B_n_rows = B.get_n_rows();
  const uword B_n_cols = B.get_n_cols();

  if ((A_n_rows != B_n_rows) || (A_n_cols != B_n_cols)) {
    arma_stop_logic_error(
        arma_incompat_size_string(A_n_rows, A_n_cols, B_n_rows, B_n_cols, x));
  }
}

//
// functions for checking whether two sparse matrices have the same dimensions

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const SpMat<eT1>& A, const SpMat<eT2>& B,
                                           const char* x) {
  const uword A_n_rows = A.n_rows;
  const uword A_n_cols = A.n_cols;

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

  if ((A_n_rows != B_n_rows) || (A_n_cols != B_n_cols)) {
    arma_stop_logic_error(
        arma_incompat_size_string(A_n_rows, A_n_cols, B_n_rows, B_n_cols, x));
  }
}

//
// functions for checking whether two cubes have the same dimensions

arma_hot inline void arma_assert_same_size(const uword A_n_rows, const uword A_n_cols,
                                           const uword A_n_slices, const uword B_n_rows,
                                           const uword B_n_cols, const uword B_n_slices,
                                           const char* x) {
  if ((A_n_rows != B_n_rows) || (A_n_cols != B_n_cols) || (A_n_slices != B_n_slices)) {
    arma_stop_logic_error(arma_incompat_size_string(A_n_rows, A_n_cols, A_n_slices,
                                                    B_n_rows, B_n_cols, B_n_slices, x));
  }
}

//! stop if given cubes have different sizes
template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const Cube<eT1>& A, const Cube<eT2>& B,
                                           const char* x) {
  if ((A.n_rows != B.n_rows) || (A.n_cols != B.n_cols) || (A.n_slices != B.n_slices)) {
    arma_stop_logic_error(arma_incompat_size_string(A.n_rows, A.n_cols, A.n_slices,
                                                    B.n_rows, B.n_cols, B.n_slices, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const Cube<eT1>& A, const subview_cube<eT2>& B,
                                           const char* x) {
  if ((A.n_rows != B.n_rows) || (A.n_cols != B.n_cols) || (A.n_slices != B.n_slices)) {
    arma_stop_logic_error(arma_incompat_size_string(A.n_rows, A.n_cols, A.n_slices,
                                                    B.n_rows, B.n_cols, B.n_slices, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const subview_cube<eT1>& A, const Cube<eT2>& B,
                                           const char* x) {
  if ((A.n_rows != B.n_rows) || (A.n_cols != B.n_cols) || (A.n_slices != B.n_slices)) {
    arma_stop_logic_error(arma_incompat_size_string(A.n_rows, A.n_cols, A.n_slices,
                                                    B.n_rows, B.n_cols, B.n_slices, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const subview_cube<eT1>& A,
                                           const subview_cube<eT2>& B, const char* x) {
  if ((A.n_rows != B.n_rows) || (A.n_cols != B.n_cols) || (A.n_slices != B.n_slices)) {
    arma_stop_logic_error(arma_incompat_size_string(A.n_rows, A.n_cols, A.n_slices,
                                                    B.n_rows, B.n_cols, B.n_slices, x));
  }
}

template <typename eT, typename T1>
arma_hot inline void arma_assert_same_size(const subview_cube<eT>& A,
                                           const ProxyCube<T1>& B, const char* x) {
  const uword A_n_rows = A.n_rows;
  const uword A_n_cols = A.n_cols;
  const uword A_n_slices = A.n_slices;

  const uword B_n_rows = B.get_n_rows();
  const uword B_n_cols = B.get_n_cols();
  const uword B_n_slices = B.get_n_slices();

  if ((A_n_rows != B_n_rows) || (A_n_cols != B_n_cols) || (A_n_slices != B_n_slices)) {
    arma_stop_logic_error(arma_incompat_size_string(A_n_rows, A_n_cols, A_n_slices,
                                                    B_n_rows, B_n_cols, B_n_slices, x));
  }
}

//! stop if given cube proxies have different sizes
template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const ProxyCube<eT1>& A,
                                           const ProxyCube<eT2>& B, const char* x) {
  const uword A_n_rows = A.get_n_rows();
  const uword A_n_cols = A.get_n_cols();
  const uword A_n_slices = A.get_n_slices();

  const uword B_n_rows = B.get_n_rows();
  const uword B_n_cols = B.get_n_cols();
  const uword B_n_slices = B.get_n_slices();

  if ((A_n_rows != B_n_rows) || (A_n_cols != B_n_cols) || (A_n_slices != B_n_slices)) {
    arma_stop_logic_error(arma_incompat_size_string(A_n_rows, A_n_cols, A_n_slices,
                                                    B_n_rows, B_n_cols, B_n_slices, x));
  }
}

//
// functions for checking whether a cube or subcube can be interpreted as a matrix (ie.
// single slice)

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const Cube<eT1>& A, const Mat<eT2>& B,
                                           const char* x) {
  if ((A.n_rows != B.n_rows) || (A.n_cols != B.n_cols) || (A.n_slices != 1)) {
    arma_stop_logic_error(arma_incompat_size_string(A.n_rows, A.n_cols, A.n_slices,
                                                    B.n_rows, B.n_cols, 1, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const Mat<eT1>& A, const Cube<eT2>& B,
                                           const char* x) {
  if ((A.n_rows != B.n_rows) || (A.n_cols != B.n_cols) || (1 != B.n_slices)) {
    arma_stop_logic_error(arma_incompat_size_string(A.n_rows, A.n_cols, 1, B.n_rows,
                                                    B.n_cols, B.n_slices, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const subview_cube<eT1>& A, const Mat<eT2>& B,
                                           const char* x) {
  if ((A.n_rows != B.n_rows) || (A.n_cols != B.n_cols) || (A.n_slices != 1)) {
    arma_stop_logic_error(arma_incompat_size_string(A.n_rows, A.n_cols, A.n_slices,
                                                    B.n_rows, B.n_cols, 1, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_same_size(const Mat<eT1>& A, const subview_cube<eT2>& B,
                                           const char* x) {
  if ((A.n_rows != B.n_rows) || (A.n_cols != B.n_cols) || (1 != B.n_slices)) {
    arma_stop_logic_error(arma_incompat_size_string(A.n_rows, A.n_cols, 1, B.n_rows,
                                                    B.n_cols, B.n_slices, x));
  }
}

template <typename eT, typename T1>
inline void arma_assert_cube_as_mat(const Mat<eT>& M, const T1& Q, const char* x,
                                    const bool check_compat_size) {
  const uword Q_n_rows = Q.n_rows;
  const uword Q_n_cols = Q.n_cols;
  const uword Q_n_slices = Q.n_slices;

  const uword M_vec_state = M.vec_state;

  if (M_vec_state == 0) {
    if (((Q_n_rows == 1) || (Q_n_cols == 1) || (Q_n_slices == 1)) == false) {
      std::ostringstream tmp;

      tmp << x << ": can't interpret cube with dimensions " << Q_n_rows << 'x' << Q_n_cols
          << 'x' << Q_n_slices << " as a matrix; one of the dimensions must be 1";

      arma_stop_logic_error(tmp.str());
    }
  } else {
    if (Q_n_slices == 1) {
      if ((M_vec_state == 1) && (Q_n_cols != 1)) {
        std::ostringstream tmp;

        tmp << x << ": can't interpret cube with dimensions " << Q_n_rows << 'x'
            << Q_n_cols << 'x' << Q_n_slices << " as a column vector";

        arma_stop_logic_error(tmp.str());
      }

      if ((M_vec_state == 2) && (Q_n_rows != 1)) {
        std::ostringstream tmp;

        tmp << x << ": can't interpret cube with dimensions " << Q_n_rows << 'x'
            << Q_n_cols << 'x' << Q_n_slices << " as a row vector";

        arma_stop_logic_error(tmp.str());
      }
    } else {
      if ((Q_n_cols != 1) && (Q_n_rows != 1)) {
        std::ostringstream tmp;

        tmp << x << ": can't interpret cube with dimensions " << Q_n_rows << 'x'
            << Q_n_cols << 'x' << Q_n_slices << " as a vector";

        arma_stop_logic_error(tmp.str());
      }
    }
  }

  if (check_compat_size) {
    const uword M_n_rows = M.n_rows;
    const uword M_n_cols = M.n_cols;

    if (M_vec_state == 0) {
      if ((((Q_n_rows == M_n_rows) && (Q_n_cols == M_n_cols)) ||
           ((Q_n_rows == M_n_rows) && (Q_n_slices == M_n_cols)) ||
           ((Q_n_cols == M_n_rows) && (Q_n_slices == M_n_cols))) == false) {
        std::ostringstream tmp;

        tmp << x << ": can't interpret cube with dimensions " << Q_n_rows << 'x'
            << Q_n_cols << 'x' << Q_n_slices << " as a matrix with dimensions "
            << M_n_rows << 'x' << M_n_cols;

        arma_stop_logic_error(tmp.str());
      }
    } else {
      if (Q_n_slices == 1) {
        if ((M_vec_state == 1) && (Q_n_rows != M_n_rows)) {
          std::ostringstream tmp;

          tmp << x << ": can't interpret cube with dimensions " << Q_n_rows << 'x'
              << Q_n_cols << 'x' << Q_n_slices << " as a column vector with dimensions "
              << M_n_rows << 'x' << M_n_cols;

          arma_stop_logic_error(tmp.str());
        }

        if ((M_vec_state == 2) && (Q_n_cols != M_n_cols)) {
          std::ostringstream tmp;

          tmp << x << ": can't interpret cube with dimensions " << Q_n_rows << 'x'
              << Q_n_cols << 'x' << Q_n_slices << " as a row vector with dimensions "
              << M_n_rows << 'x' << M_n_cols;

          arma_stop_logic_error(tmp.str());
        }
      } else {
        if (((M_n_cols == Q_n_slices) || (M_n_rows == Q_n_slices)) == false) {
          std::ostringstream tmp;

          tmp << x << ": can't interpret cube with dimensions " << Q_n_rows << 'x'
              << Q_n_cols << 'x' << Q_n_slices << " as a vector with dimensions "
              << M_n_rows << 'x' << M_n_cols;

          arma_stop_logic_error(tmp.str());
        }
      }
    }
  }
}

//
// functions for checking whether two matrices have dimensions that are compatible with
// the matrix multiply operation

arma_hot inline void arma_assert_mul_size(const uword A_n_rows, const uword A_n_cols,
                                          const uword B_n_rows, const uword B_n_cols,
                                          const char* x) {
  if (A_n_cols != B_n_rows) {
    arma_stop_logic_error(
        arma_incompat_size_string(A_n_rows, A_n_cols, B_n_rows, B_n_cols, x));
  }
}

//! stop if given matrices are incompatible for multiplication
template <typename eT1, typename eT2>
arma_hot inline void arma_assert_mul_size(const Mat<eT1>& A, const Mat<eT2>& B,
                                          const char* x) {
  const uword A_n_cols = A.n_cols;
  const uword B_n_rows = B.n_rows;

  if (A_n_cols != B_n_rows) {
    arma_stop_logic_error(
        arma_incompat_size_string(A.n_rows, A_n_cols, B_n_rows, B.n_cols, x));
  }
}

//! stop if given matrices are incompatible for multiplication
template <typename eT1, typename eT2>
arma_hot inline void arma_assert_mul_size(const Mat<eT1>& A, const Mat<eT2>& B,
                                          const bool do_trans_A, const bool do_trans_B,
                                          const char* x) {
  const uword final_A_n_cols = (do_trans_A == false) ? A.n_cols : A.n_rows;
  const uword final_B_n_rows = (do_trans_B == false) ? B.n_rows : B.n_cols;

  if (final_A_n_cols != final_B_n_rows) {
    const uword final_A_n_rows = (do_trans_A == false) ? A.n_rows : A.n_cols;
    const uword final_B_n_cols = (do_trans_B == false) ? B.n_cols : B.n_rows;

    arma_stop_logic_error(arma_incompat_size_string(final_A_n_rows, final_A_n_cols,
                                                    final_B_n_rows, final_B_n_cols, x));
  }
}

template <const bool do_trans_A, const bool do_trans_B>
arma_hot inline void arma_assert_trans_mul_size(const uword A_n_rows,
                                                const uword A_n_cols,
                                                const uword B_n_rows,
                                                const uword B_n_cols, const char* x) {
  const uword final_A_n_cols = (do_trans_A == false) ? A_n_cols : A_n_rows;
  const uword final_B_n_rows = (do_trans_B == false) ? B_n_rows : B_n_cols;

  if (final_A_n_cols != final_B_n_rows) {
    const uword final_A_n_rows = (do_trans_A == false) ? A_n_rows : A_n_cols;
    const uword final_B_n_cols = (do_trans_B == false) ? B_n_cols : B_n_rows;

    arma_stop_logic_error(arma_incompat_size_string(final_A_n_rows, final_A_n_cols,
                                                    final_B_n_rows, final_B_n_cols, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_mul_size(const Mat<eT1>& A, const subview<eT2>& B,
                                          const char* x) {
  if (A.n_cols != B.n_rows) {
    arma_stop_logic_error(
        arma_incompat_size_string(A.n_rows, A.n_cols, B.n_rows, B.n_cols, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_mul_size(const subview<eT1>& A, const Mat<eT2>& B,
                                          const char* x) {
  if (A.n_cols != B.n_rows) {
    arma_stop_logic_error(
        arma_incompat_size_string(A.n_rows, A.n_cols, B.n_rows, B.n_cols, x));
  }
}

template <typename eT1, typename eT2>
arma_hot inline void arma_assert_mul_size(const subview<eT1>& A, const subview<eT2>& B,
                                          const char* x) {
  if (A.n_cols != B.n_rows) {
    arma_stop_logic_error(
        arma_incompat_size_string(A.n_rows, A.n_cols, B.n_rows, B.n_cols, x));
  }
}

template <typename T1>
arma_hot inline void arma_assert_blas_size(const T1& A) {
  if (sizeof(uword) >= sizeof(blas_int)) {
    bool overflow;

    overflow = (A.n_rows > ARMA_MAX_BLAS_INT);
    overflow = (A.n_cols > ARMA_MAX_BLAS_INT) || overflow;

    if (overflow) {
      arma_stop_runtime_error(
          "integer overflow: matrix dimensions are too large for integer type used by "
          "BLAS and LAPACK");
    }
  }
}

template <typename T1, typename T2>
arma_hot inline void arma_assert_blas_size(const T1& A, const T2& B) {
  if (sizeof(uword) >= sizeof(blas_int)) {
    bool overflow;

    overflow = (A.n_rows > ARMA_MAX_BLAS_INT);
    overflow = (A.n_cols > ARMA_MAX_BLAS_INT) || overflow;
    overflow = (B.n_rows > ARMA_MAX_BLAS_INT) || overflow;
    overflow = (B.n_cols > ARMA_MAX_BLAS_INT) || overflow;

    if (overflow) {
      arma_stop_runtime_error(
          "integer overflow: matrix dimensions are too large for integer type used by "
          "BLAS and LAPACK");
    }
  }
}

// TODO: remove support for ATLAS in next major version
template <typename T1>
arma_hot inline void arma_assert_atlas_size(const T1& A) {
  if (sizeof(uword) >= sizeof(int)) {
    bool overflow;

    overflow = (A.n_rows > INT_MAX);
    overflow = (A.n_cols > INT_MAX) || overflow;

    if (overflow) {
      arma_stop_runtime_error(
          "integer overflow: matrix dimensions are too large for integer type used by "
          "ATLAS");
    }
  }
}

// TODO: remove support for ATLAS in next major version
template <typename T1, typename T2>
arma_hot inline void arma_assert_atlas_size(const T1& A, const T2& B) {
  if (sizeof(uword) >= sizeof(int)) {
    bool overflow;

    overflow = (A.n_rows > INT_MAX);
    overflow = (A.n_cols > INT_MAX) || overflow;
    overflow = (B.n_rows > INT_MAX) || overflow;
    overflow = (B.n_cols > INT_MAX) || overflow;

    if (overflow) {
      arma_stop_runtime_error(
          "integer overflow: matrix dimensions are too large for integer type used by "
          "ATLAS");
    }
  }
}

//
// macros

// #define ARMA_STRING1(x) #x
// #define ARMA_STRING2(x) ARMA_STRING1(x)
// #define ARMA_FILELINE  __FILE__ ": " ARMA_STRING2(__LINE__)

#if defined(ARMA_NO_DEBUG)

#define arma_debug_print true ? (void)0 : arma_print
#define arma_debug_warn true ? (void)0 : arma_warn
#define arma_debug_warn_level true ? (void)0 : arma_warn_level
#define arma_debug_check true ? (void)0 : arma_check
#define arma_debug_check_bounds true ? (void)0 : arma_check_bounds
#define arma_debug_set_error true ? (void)0 : arma_set_error
#define arma_debug_assert_same_size true ? (void)0 : arma_assert_same_size
#define arma_debug_assert_mul_size true ? (void)0 : arma_assert_mul_size
#define arma_debug_assert_trans_mul_size true ? (void)0 : arma_assert_trans_mul_size
#define arma_debug_assert_cube_as_mat true ? (void)0 : arma_assert_cube_as_mat
#define arma_debug_assert_blas_size true ? (void)0 : arma_assert_blas_size
#define arma_debug_assert_atlas_size true ? (void)0 : arma_assert_atlas_size

#else

#define arma_debug_print arma_print
#define arma_debug_warn arma_warn
#define arma_debug_warn_level arma_warn_level
#define arma_debug_check arma_check
#define arma_debug_check_bounds arma_check_bounds
#define arma_debug_set_error arma_set_error
#define arma_debug_assert_same_size arma_assert_same_size
#define arma_debug_assert_mul_size arma_assert_mul_size
#define arma_debug_assert_trans_mul_size arma_assert_trans_mul_size
#define arma_debug_assert_cube_as_mat arma_assert_cube_as_mat
#define arma_debug_assert_blas_size arma_assert_blas_size
#define arma_debug_assert_atlas_size arma_assert_atlas_size

#endif

#if defined(ARMA_EXTRA_DEBUG)

#define arma_extra_debug_sigprint \
  arma_sigprint(ARMA_FNSIG);      \
  arma_bktprint
#define arma_extra_debug_sigprint_this \
  arma_sigprint(ARMA_FNSIG);           \
  arma_thisprint
#define arma_extra_debug_print arma_print

#else

#define arma_extra_debug_sigprint true ? (void)0 : arma_bktprint
#define arma_extra_debug_sigprint_this true ? (void)0 : arma_thisprint
#define arma_extra_debug_print true ? (void)0 : arma_print

#endif

#if defined(ARMA_EXTRA_DEBUG)

namespace junk {
class arma_first_extra_debug_message {
 public:
  inline arma_first_extra_debug_message() {
    union {
      unsigned short a;
      unsigned char b[sizeof(unsigned short)];
    } endian_test;

    endian_test.a = 1;

    const bool little_endian = (endian_test.b[0] == 1);
    const char* nickname = ARMA_VERSION_NAME;

    std::ostream& out = get_cerr_stream();

    out << "@ ---" << '\n';
    out << "@ Armadillo " << arma_version::major << '.' << arma_version::minor << '.'
        << arma_version::patch << " (" << nickname << ")\n";

    out << "@ arma_config::wrapper          = " << arma_config::wrapper << '\n';
    out << "@ arma_config::cxx14            = " << arma_config::cxx14 << '\n';
    out << "@ arma_config::cxx17            = " << arma_config::cxx17 << '\n';
    out << "@ arma_config::cxx20            = " << arma_config::cxx20 << '\n';
    out << "@ arma_config::std_mutex        = " << arma_config::std_mutex << '\n';
    out << "@ arma_config::posix            = " << arma_config::posix << '\n';
    out << "@ arma_config::openmp           = " << arma_config::openmp << '\n';
    out << "@ arma_config::lapack           = " << arma_config::lapack << '\n';
    out << "@ arma_config::blas             = " << arma_config::blas << '\n';
    out << "@ arma_config::newarp           = " << arma_config::newarp << '\n';
    out << "@ arma_config::arpack           = " << arma_config::arpack << '\n';
    out << "@ arma_config::superlu          = " << arma_config::superlu << '\n';
    out << "@ arma_config::atlas            = " << arma_config::atlas << '\n';
    out << "@ arma_config::hdf5             = " << arma_config::hdf5 << '\n';
    out << "@ arma_config::good_comp        = " << arma_config::good_comp << '\n';
    out << "@ arma_config::extra_code       = " << arma_config::extra_code << '\n';
    out << "@ arma_config::hidden_args      = " << arma_config::hidden_args << '\n';
    out << "@ arma_config::mat_prealloc     = " << arma_config::mat_prealloc << '\n';
    out << "@ arma_config::mp_threshold     = " << arma_config::mp_threshold << '\n';
    out << "@ arma_config::mp_threads       = " << arma_config::mp_threads << '\n';
    out << "@ arma_config::optimise_band    = " << arma_config::optimise_band << '\n';
    out << "@ arma_config::optimise_sym     = " << arma_config::optimise_sym << '\n';
    out << "@ arma_config::optimise_invexpr = " << arma_config::optimise_invexpr << '\n';
    out << "@ arma_config::check_nonfinite  = " << arma_config::check_nonfinite << '\n';
    out << "@ arma_config::zero_init        = " << arma_config::zero_init << '\n';
    out << "@ arma_config::fast_math        = " << arma_config::fast_math << '\n';
    out << "@ sizeof(void*)    = " << sizeof(void*) << '\n';
    out << "@ sizeof(int)      = " << sizeof(int) << '\n';
    out << "@ sizeof(long)     = " << sizeof(long) << '\n';
    out << "@ sizeof(uword)    = " << sizeof(uword) << '\n';
    out << "@ sizeof(blas_int) = " << sizeof(blas_int) << '\n';
    out << "@ little_endian    = " << little_endian << '\n';
    out << "@ ---" << std::endl;
  }
};

static arma_first_extra_debug_message arma_first_extra_debug_message_run;
}  // namespace junk

#endif

//! @}
