//@HEADER
// ************************************************************************
//
//                        Kokkos v. 3.0
//       Copyright (2020) National Technology & Engineering
//               Solutions of Sandia, LLC (NTESS).
//
// Under the terms of Contract DE-NA0003525 with NTESS,
// the U.S. Government retains certain rights in this software.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the Corporation nor the names of the
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY NTESS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NTESS OR THE
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Questions? Contact Siva Rajamanickam (srajama@sandia.gov)
//
// ************************************************************************
//@HEADER

#ifndef _KOKKOSSPGEMMMKL2_HPP
#define _KOKKOSSPGEMMMKL2_HPP

//#define KOKKOSKERNELS_ENABLE_TPL_MKL

#ifdef KOKKOSKERNELS_ENABLE_TPL_MKL
#include "mkl.h"
#endif

#include "KokkosKernels_Utils.hpp"
#include <Kokkos_Concepts.hpp>
#include <vector>

namespace KokkosSparse {
namespace Impl {

template <typename KernelHandle, typename in_row_index_view_type,
          typename in_nonzero_index_view_type, typename bin_row_index_view_type,
          typename bin_nonzero_index_view_type,
          typename cin_row_index_view_type>
void mkl2phase_symbolic(
    KernelHandle *handle, typename KernelHandle::nnz_lno_t m,
    typename KernelHandle::nnz_lno_t n, typename KernelHandle::nnz_lno_t k,
    in_row_index_view_type row_mapA, in_nonzero_index_view_type entriesA,

    bool transposeA, bin_row_index_view_type row_mapB,
    bin_nonzero_index_view_type entriesB,

    bool transposeB, cin_row_index_view_type row_mapC, bool verbose = false) {
#ifdef KOKKOSKERNELS_ENABLE_TPL_MKL

  typedef typename KernelHandle::nnz_lno_t idx;

  typedef typename KernelHandle::HandlePersistentMemorySpace
      HandlePersistentMemorySpace;

  typedef typename Kokkos::View<int *, HandlePersistentMemorySpace>
      int_persistent_work_view_t;

  typedef typename KernelHandle::HandleExecSpace MyExecSpace;

  if (std::is_same<idx, int>::value) {
    int_persistent_work_view_t a_xadj_v, b_xadj_v;

    const int max_integer = 2147483647;
    if (entriesB.extent(0) > max_integer || entriesA.extent(0) > max_integer) {
      throw std::runtime_error(
          "MKL requires integer values for size type for SPGEMM. Copying to "
          "integer will cause overflow.\n");
    }

    int *a_adj = (int *)entriesA.data();
    int *b_adj = (int *)entriesB.data();

    int *a_xadj = (int *)row_mapA.data();
    int *b_xadj = (int *)row_mapB.data();
    int *c_xadj = (int *)row_mapC.data();

    if (handle->mkl_convert_to_1base) {
      handle->persistent_a_xadj = int_persistent_work_view_t("tmpa", m + 1);
      handle->persistent_b_xadj = int_persistent_work_view_t("tmpb", n + 1);
      handle->persistent_c_xadj = int_persistent_work_view_t("tmpc", m + 1);
      int_persistent_work_view_t a_plus_one("a_plus_one", entriesA.extent(0));
      int_persistent_work_view_t b_plus_one("b_plus_one", entriesB.extent(0));
      handle->persistent_a_adj = a_plus_one;
      handle->persistent_b_adj = b_plus_one;

      KokkosKernels::Impl::kk_a_times_x_plus_b<int_persistent_work_view_t,
                                               in_row_index_view_type, int, int,
                                               MyExecSpace>(
          m + 1, handle->persistent_a_xadj, row_mapA, 1, 1);
      KokkosKernels::Impl::kk_a_times_x_plus_b<int_persistent_work_view_t,
                                               bin_row_index_view_type, int,
                                               int, MyExecSpace>(
          n + 1, handle->persistent_b_xadj, row_mapB, 1, 1);
      KokkosKernels::Impl::kk_a_times_x_plus_b<int_persistent_work_view_t,
                                               in_nonzero_index_view_type, int,
                                               int, MyExecSpace>(
          entriesA.extent(0), a_plus_one, entriesA, 1, 1);
      KokkosKernels::Impl::kk_a_times_x_plus_b<int_persistent_work_view_t,
                                               bin_nonzero_index_view_type, int,
                                               int, MyExecSpace>(
          entriesB.extent(0), b_plus_one, entriesB, 1, 1);

      a_adj  = (int *)handle->persistent_a_adj.data();
      b_adj  = (int *)handle->persistent_b_adj.data();
      a_xadj = handle->persistent_a_xadj.data();
      b_xadj = handle->persistent_b_xadj.data();
      c_xadj = handle->persistent_c_xadj.data();
    }

#if __INTEL_MKL__ < 2018
    (void)transposeA;
    (void)transposeB;  // supress unused-parameter warning
    char trans      = 'N';
    MKL_INT request = 1;
    MKL_INT sort    = handle->get_mkl_sort_option();
    MKL_INT mklm = m, mkln = n, mklk = k;
    MKL_INT info = 0;

    double *mynullptr = NULL;
    int *mynulladj    = NULL;
    const int nzmax   = 0;

    /*
    KokkosKernels::Impl::print_1Dview(handle->persistent_a_xadj);
    KokkosKernels::Impl::print_1Dview(a_plus_one);
    KokkosKernels::Impl::print_1Dview(handle->persistent_b_xadj);
    KokkosKernels::Impl::print_1Dview(b_plus_one);
    */
    Kokkos::Timer timer1;

    mkl_dcsrmultcsr(&trans, &request, &sort, &mklm, &mkln, &mklk, mynullptr,
                    a_adj, a_xadj, mynullptr, b_adj, b_xadj, mynullptr,
                    mynulladj, c_xadj, &nzmax, &info);

    if (verbose) {
      std::cout << "Sort:" << sort
                << " Actual MKL2 Symbolic Time:" << timer1.seconds()
                << std::endl;
    }

    if (handle->mkl_convert_to_1base) {
      KokkosKernels::Impl::kk_a_times_x_plus_b<cin_row_index_view_type,
                                               int_persistent_work_view_t, int,
                                               int, MyExecSpace>(
          m + 1, row_mapC, handle->persistent_c_xadj, 1, -1);
      handle->set_c_nnz(row_mapC(m));
    } else {
      handle->set_c_nnz(row_mapC(m) - 1);
    }
#endif  // __INTEL_MKL__ < 2018

#if __INTEL_MKL__ == 2018 && __INTEL_MKL_UPDATE__ >= 2
    MKL_INT mklm = m, mkln = n;
    double *mynullptr = NULL;

    sparse_matrix_t A;
    sparse_matrix_t B;
    sparse_matrix_t C;

    // Goal: Set c_xadj (which is the rowptr) from C

    if (handle->mkl_convert_to_1base) {  // a*, b* already converted to 1base
                                         // above...
      if (SPARSE_STATUS_SUCCESS !=
          mkl_sparse_d_create_csr(&A, SPARSE_INDEX_BASE_ONE, mklm, mkln, a_xadj,
                                  a_xadj + 1, a_adj, mynullptr)) {
        throw std::runtime_error(
            "CANNOT CREATE mkl_sparse_s_create_csr A matrix\n");
      }

      if (SPARSE_STATUS_SUCCESS !=
          mkl_sparse_d_create_csr(&B, SPARSE_INDEX_BASE_ONE, n, k, b_xadj,
                                  b_xadj + 1, b_adj, mynullptr)) {
        throw std::runtime_error(
            "CANNOT CREATE mkl_sparse_s_create_csr B matrix\n");
      }
    } else {
      if (SPARSE_STATUS_SUCCESS !=
          mkl_sparse_d_create_csr(&A, SPARSE_INDEX_BASE_ZERO, mklm, mkln,
                                  a_xadj, a_xadj + 1, a_adj, mynullptr)) {
        throw std::runtime_error(
            "CANNOT CREATE mkl_sparse_s_create_csr A matrix\n");
      }

      if (SPARSE_STATUS_SUCCESS !=
          mkl_sparse_d_create_csr(&B, SPARSE_INDEX_BASE_ZERO, n, k, b_xadj,
                                  b_xadj + 1, b_adj, mynullptr)) {
        throw std::runtime_error(
            "CANNOT CREATE mkl_sparse_s_create_csr B matrix\n");
      }
    }

    sparse_operation_t operation;
    if (transposeA && transposeB) {
      operation = SPARSE_OPERATION_TRANSPOSE;
    } else if (!(transposeA || transposeB)) {
      operation = SPARSE_OPERATION_NON_TRANSPOSE;
    } else {
      throw std::runtime_error(
          "MKL either transpose both matrices, or none for SPGEMM\n");
    }

    matrix_descr common_mtx_props;
    common_mtx_props.type = SPARSE_MATRIX_TYPE_GENERAL;
    common_mtx_props.mode = SPARSE_FILL_MODE_FULL;
    common_mtx_props.diag = SPARSE_DIAG_NON_UNIT;

    Kokkos::Timer timer1;
    // options: SPARSE_STAGE_FULL_MULT vs SPARSE_STAGE_NNZ_COUNT then
    // SPARSE_STAGE_FINALIZE_MULT
    bool success = SPARSE_STATUS_SUCCESS !=
                   mkl_sparse_sp2m(operation, common_mtx_props, A, operation,
                                   common_mtx_props, B, SPARSE_STAGE_NNZ_COUNT,
                                   &C);  // success is "true" if mkl_sparse_spmm
                                         // does not return success

    if (verbose) {
      std::cout << "Actual DOUBLE MKL SPMM Time:" << timer1.seconds()
                << std::endl;
    }

    if (success) {
      throw std::runtime_error(
          "ERROR at SPGEMM multiplication in mkl_sparse_spmm\n");
    } else {
      // Copy sparse_matrix_t C results back to input data structure
      sparse_index_base_t c_indexing;
      MKL_INT c_rows, c_cols, *rows_end, *columns;  // use c_xadj as rows_start
      double *values;                               // should return null

      if (SPARSE_STATUS_SUCCESS !=
          // mkl_sparse_s_export_csr (C, &c_indexing, &c_rows, &c_cols,
          // &rows_start, &rows_end, &columns, &values))
          mkl_sparse_d_export_csr(C, &c_indexing, &c_rows, &c_cols, &c_xadj,
                                  &rows_end, &columns, &values)) {
        throw std::runtime_error(
            "ERROR at exporting result matrix in mkl_sparse_spmm\n");
      }

      //      if (SPARSE_INDEX_BASE_ZERO != c_indexing){
      //        throw std::runtime_error ("C is not zero based indexed\n");
      //      }
      if (handle->mkl_convert_to_1base &&
          (c_indexing ==
           SPARSE_INDEX_BASE_ONE)) {  // Need to convert back to base0
        KokkosKernels::Impl::kk_a_times_x_plus_b<cin_row_index_view_type,
                                                 int_persistent_work_view_t,
                                                 int, int, MyExecSpace>(
            m + 1, row_mapC, handle->persistent_c_xadj, 1, -1);
        handle->set_c_nnz(row_mapC(m));
      } else {
        handle->set_c_nnz(row_mapC(m) - 1);
      }

    }  // end else !success

    // Cleanup...
    if (SPARSE_STATUS_SUCCESS != mkl_sparse_destroy(A)) {
      throw std::runtime_error("Error at mkl_sparse_destroy A\n");
    }
    if (SPARSE_STATUS_SUCCESS != mkl_sparse_destroy(B)) {
      throw std::runtime_error("Error at mkl_sparse_destroy B\n");
    }
    if (SPARSE_STATUS_SUCCESS != mkl_sparse_destroy(C)) {
      throw std::runtime_error("Error at mkl_sparse_destroy C\n");
    }
#elif __INTEL_MKL__ == 2018 && __INTEL_MKL_UPDATE__ < 2
    throw std::runtime_error(
        "Intel MKL version 18 must have update 2 - use intel/18.2.xyz\n");
    (void)k;
    (void)transposeA;
    (void)transposeB;
    (void)verbose;
#else
    throw std::runtime_error(
        "Intel MKL versions > 18 are not yet tested/supported\n");
    (void)k;
    (void)transposeA;
    (void)transposeB;
    (void)verbose;
#endif

  } else {
    throw std::runtime_error("MKL requires local ordinals to be integer.\n");
    (void)k;
    (void)transposeA;
    (void)transposeB;
    (void)verbose;
  }
#else   // KOKKOSKERNELS_ENABLE_TPL_MKL
  (void)handle;
  (void)m;
  (void)n;
  (void)k;
  (void)row_mapA;
  (void)row_mapB;
  (void)row_mapC;
  (void)entriesA;
  (void)entriesB;
  (void)transposeA;
  (void)transposeB;
  (void)verbose;
  throw std::runtime_error("MKL IS NOT DEFINED\n");
#endif  // KOKKOSKERNELS_ENABLE_TPL_MKL
}

template <
    typename KernelHandle, typename in_row_index_view_type,
    typename in_nonzero_index_view_type, typename in_nonzero_value_view_type,
    typename bin_row_index_view_type, typename bin_nonzero_index_view_type,
    typename bin_nonzero_value_view_type, typename cin_row_index_view_type,
    typename cin_nonzero_index_view_type, typename cin_nonzero_value_view_type>
void mkl2phase_apply(
    KernelHandle *handle, typename KernelHandle::nnz_lno_t m,
    typename KernelHandle::nnz_lno_t n, typename KernelHandle::nnz_lno_t k,
    in_row_index_view_type row_mapA, in_nonzero_index_view_type entriesA,
    in_nonzero_value_view_type valuesA,

    bool transposeA, bin_row_index_view_type row_mapB,
    bin_nonzero_index_view_type entriesB, bin_nonzero_value_view_type valuesB,
    bool transposeB, cin_row_index_view_type row_mapC,
    cin_nonzero_index_view_type &entriesC, cin_nonzero_value_view_type &valuesC,
    bool verbose = false) {
#ifdef KOKKOSKERNELS_ENABLE_TPL_MKL

  using HandlePersistentMemorySpace =
      typename KernelHandle::HandlePersistentMemorySpace;
  using int_persistent_work_view_t =
      typename Kokkos::View<int *, HandlePersistentMemorySpace>;
  using MyExecSpace = typename KernelHandle::HandleExecSpace;
  using value_type  = typename KernelHandle::nnz_scalar_t;
  using idx         = typename KernelHandle::nnz_lno_t;

  if (std::is_same<idx, int>::value) {
    int *a_xadj = (int *)row_mapA.data();
    int *b_xadj = (int *)row_mapB.data();
    int *c_xadj = (int *)row_mapC.data();

    int *a_adj = (int *)entriesA.data();
    int *b_adj = (int *)entriesB.data();

    if (handle->mkl_convert_to_1base) {
      int_persistent_work_view_t a_xadj_v, b_xadj_v, c_xadj_v;
      a_xadj = (int *)handle->persistent_a_xadj.data();
      b_xadj = (int *)handle->persistent_b_xadj.data();
      c_xadj = (int *)handle->persistent_c_xadj.data();
      int_persistent_work_view_t a_plus_one = handle->persistent_a_adj;
      int_persistent_work_view_t b_plus_one = handle->persistent_b_adj;

      a_adj = (int *)a_plus_one.data();
      b_adj = (int *)b_plus_one.data();
    }

#if __INTEL_MKL__ < 2018
    (void)transposeA;
    (void)transposeB;
    const value_type *a_ew = valuesA.data();
    const value_type *b_ew = valuesB.data();

    char trans      = 'N';
    MKL_INT request = 2;
    MKL_INT sort    = handle->get_mkl_sort_option();
    MKL_INT mklm = m, mkln = n, mklk = k;
    MKL_INT info = 0, nzmax = 2147483647;
    /*
          KokkosKernels::Impl::print_1Dview(handle->persistent_a_xadj);
          KokkosKernels::Impl::print_1Dview(a_plus_one);
          KokkosKernels::Impl::print_1Dview(handle->persistent_b_xadj);
          KokkosKernels::Impl::print_1Dview(b_plus_one);
          KokkosKernels::Impl::print_1Dview(handle->persistent_c_xadj);
          KokkosKernels::Impl::print_1Dview(valuesA);
          KokkosKernels::Impl::print_1Dview(valuesB);


          std::cout << "A" << std::endl;
          KokkosKernels::Impl::print_1Dview(row_mapA);
          KokkosKernels::Impl::print_1Dview(entriesA);
          std::cout << "B" << std::endl;
          KokkosKernels::Impl::print_1Dview(row_mapB);
          KokkosKernels::Impl::print_1Dview(entriesB);
          std::cout << "c:" << "entriesC:" << entriesC.extent(0) << std::endl;
          KokkosKernels::Impl::print_1Dview(row_mapC);
    */
    Kokkos::Timer timer1;

    if (std::is_same<value_type, float>::value) {
      mkl_scsrmultcsr(&trans, &request, &sort, &mklm, &mkln, &mklk,
                      (float *)a_ew, a_adj, a_xadj, (float *)b_ew, b_adj,
                      b_xadj, (float *)valuesC.data(), entriesC.data(), c_xadj,
                      &nzmax, &info);
      mkl_free_buffers();
    } else if (std::is_same<value_type, double>::value) {
      mkl_dcsrmultcsr(&trans, &request, &sort, &mklm, &mkln, &mklk,
                      (double *)a_ew, a_adj, a_xadj, (double *)b_ew, b_adj,
                      b_xadj, (double *)valuesC.data(), entriesC.data(), c_xadj,
                      &nzmax, &info);
      mkl_free_buffers();
    } else {
      throw std::runtime_error(
          "MKL requires float or double values. Complex values are not "
          "implemented yet.\n");
    }
    if (verbose) {
      std::cout << "Sort:" << sort
                << " Actual MKL2 Numeric Time:" << timer1.seconds()
                << std::endl;
    }

    if (handle->mkl_convert_to_1base) {
      KokkosKernels::Impl::kk_a_times_x_plus_b<cin_nonzero_index_view_type,
                                               cin_nonzero_index_view_type, int,
                                               int, MyExecSpace>(
          entriesC.extent(0), entriesC, entriesC, 1, -1);
    }
#endif  // __INTEL_MKL__ < 2018

#if __INTEL_MKL__ == 2018 && __INTEL_MKL_UPDATE__ >= 2
    value_type *a_ew = const_cast<value_type *>(valuesA.data());
    value_type *b_ew = const_cast<value_type *>(valuesB.data());

    char trans      = 'N';
    MKL_INT request = 2;
    MKL_INT sort    = handle->get_mkl_sort_option();
    MKL_INT mklm = m, mkln = n, mklk = k;
    MKL_INT info = 0, nzmax = 2147483647;

    sparse_matrix_t A;
    sparse_matrix_t B;
    sparse_matrix_t C;

    if (handle->mkl_convert_to_1base) {  // a*, b* already converted to 1base
                                         // above...
      if (std::is_same<value_type, double>::value) {
        if (SPARSE_STATUS_SUCCESS !=
            mkl_sparse_d_create_csr(&A, SPARSE_INDEX_BASE_ONE, mklm, mkln,
                                    a_xadj, a_xadj + 1, a_adj,
                                    reinterpret_cast<double *>(a_ew))) {
          throw std::runtime_error(
              "CANNOT CREATE mkl_sparse_s_create_csr A matrix\n");
        }
      } else if (std::is_same<value_type, float>::value) {
        if (SPARSE_STATUS_SUCCESS !=
            mkl_sparse_s_create_csr(&A, SPARSE_INDEX_BASE_ONE, mklm, mkln,
                                    a_xadj, a_xadj + 1, a_adj,
                                    reinterpret_cast<float *>(a_ew))) {
          throw std::runtime_error(
              "CANNOT CREATE mkl_sparse_s_create_csr A matrix\n");
        }
      }

      if (std::is_same<value_type, double>::value) {
        if (SPARSE_STATUS_SUCCESS !=
            mkl_sparse_d_create_csr(&B, SPARSE_INDEX_BASE_ONE, n, k, b_xadj,
                                    b_xadj + 1, b_adj,
                                    reinterpret_cast<double *>(b_ew))) {
          throw std::runtime_error(
              "CANNOT CREATE mkl_sparse_s_create_csr B matrix\n");
        }
      } else if (std::is_same<value_type, float>::value) {
        if (SPARSE_STATUS_SUCCESS !=
            mkl_sparse_s_create_csr(&B, SPARSE_INDEX_BASE_ONE, n, k, b_xadj,
                                    b_xadj + 1, b_adj,
                                    reinterpret_cast<float *>(b_ew))) {
          throw std::runtime_error(
              "CANNOT CREATE mkl_sparse_s_create_csr B matrix\n");
        }
      }
    } else {
      if (std::is_same<value_type, double>::value) {
        if (SPARSE_STATUS_SUCCESS !=
            mkl_sparse_d_create_csr(&A, SPARSE_INDEX_BASE_ZERO, mklm, mkln,
                                    a_xadj, a_xadj + 1, a_adj,
                                    reinterpret_cast<double *>(a_ew))) {
          throw std::runtime_error(
              "CANNOT CREATE mkl_sparse_s_create_csr A matrix\n");
        }
      } else if (std::is_same<value_type, float>::value) {
        if (SPARSE_STATUS_SUCCESS !=
            mkl_sparse_s_create_csr(&A, SPARSE_INDEX_BASE_ZERO, mklm, mkln,
                                    a_xadj, a_xadj + 1, a_adj,
                                    reinterpret_cast<float *>(a_ew))) {
          throw std::runtime_error(
              "CANNOT CREATE mkl_sparse_s_create_csr A matrix\n");
        }
      }

      if (std::is_same<value_type, double>::value) {
        if (SPARSE_STATUS_SUCCESS !=
            mkl_sparse_d_create_csr(&B, SPARSE_INDEX_BASE_ZERO, n, k, b_xadj,
                                    b_xadj + 1, b_adj,
                                    reinterpret_cast<double *>(b_ew))) {
          throw std::runtime_error(
              "CANNOT CREATE mkl_sparse_s_create_csr B matrix\n");
        }
      } else if (std::is_same<value_type, float>::value) {
        if (SPARSE_STATUS_SUCCESS !=
            mkl_sparse_s_create_csr(&B, SPARSE_INDEX_BASE_ZERO, n, k, b_xadj,
                                    b_xadj + 1, b_adj,
                                    reinterpret_cast<float *>(b_ew))) {
          throw std::runtime_error(
              "CANNOT CREATE mkl_sparse_s_create_csr B matrix\n");
        }
      }
    }

    sparse_operation_t operation;
    if (transposeA && transposeB) {
      operation = SPARSE_OPERATION_TRANSPOSE;
    } else if (!(transposeA || transposeB)) {
      operation = SPARSE_OPERATION_NON_TRANSPOSE;
    } else {
      throw std::runtime_error(
          "MKL either transpose both matrices, or none for SPGEMM\n");
    }

    matrix_descr common_mtx_props;
    common_mtx_props.type = SPARSE_MATRIX_TYPE_GENERAL;
    common_mtx_props.mode = SPARSE_FILL_MODE_FULL;
    common_mtx_props.diag = SPARSE_DIAG_NON_UNIT;

    Kokkos::Timer timer1;
    // options: SPARSE_STAGE_FULL_MULT vs SPARSE_STAGE_NNZ_COUNT then
    // SPARSE_STAGE_FINALIZE_MULT
    bool success =
        SPARSE_STATUS_SUCCESS !=
        mkl_sparse_sp2m(operation, common_mtx_props, A, operation,
                        common_mtx_props, B, SPARSE_STAGE_FINALIZE_MULT,
                        &C);  // success is "true" if mkl_sparse_spmm does not
                              // return success

    if (verbose) {
      std::cout << "Actual MKL SPMM Time:" << timer1.seconds() << std::endl;
    }

    if (success) {
      throw std::runtime_error(
          "ERROR at SPGEMM multiplication in mkl_sparse_spmm\n");
    }

    // Copy C components back
    sparse_index_base_t c_indexing;
    MKL_INT c_rows, c_cols, *rows_start, *rows_end, *columns;
    typedef double values_type;
    values_type *values;

    if (SPARSE_STATUS_SUCCESS !=
        // mkl_sparse_s_export_csr (C,
        mkl_sparse_d_export_csr(C, &c_indexing, &c_rows, &c_cols, &rows_start,
                                &rows_end, &columns, &values)) {
      throw std::runtime_error(
          "ERROR at exporting result matrix in mkl_sparse_spmm\n");
    }

    if (SPARSE_INDEX_BASE_ZERO != c_indexing) {
      throw std::runtime_error("C is not zero based indexed\n");
    }

    // KokkosKernels::Impl::copy_vector<MKL_INT *, typename
    // cin_row_index_view_type::non_const_type, MyExecSpace> (m, rows_start,
    // row_mapC); idx nnz = row_mapC(m) =  rows_end[m - 1];
    idx nnz = rows_end[m - 1];
    using non_const_size_type =
        typename cin_row_index_view_type::non_const_value_type;
    auto *tmpPtr = const_cast<non_const_size_type *>(row_mapC.data());
    tmpPtr[m]    = nnz;

    KokkosKernels::Impl::copy_vector<
        MKL_INT *, typename cin_nonzero_index_view_type::non_const_type,
        MyExecSpace>(nnz, columns, entriesC);
    KokkosKernels::Impl::copy_vector<
        values_type *, typename cin_nonzero_value_view_type::non_const_type,
        MyExecSpace>(nnz, values, valuesC);

    if (handle->mkl_convert_to_1base) {
      KokkosKernels::Impl::kk_a_times_x_plus_b<cin_nonzero_index_view_type,
                                               cin_nonzero_index_view_type, int,
                                               int, MyExecSpace>(
          entriesC.extent(0), entriesC, entriesC, 1, -1);
    }

    // Cleanup
    if (SPARSE_STATUS_SUCCESS != mkl_sparse_destroy(A)) {
      throw std::runtime_error("Error at mkl_sparse_destroy A\n");
    }
    if (SPARSE_STATUS_SUCCESS != mkl_sparse_destroy(B)) {
      throw std::runtime_error("Error at mkl_sparse_destroy B\n");
    }
    if (SPARSE_STATUS_SUCCESS != mkl_sparse_destroy(C)) {
      throw std::runtime_error("Error at mkl_sparse_destroy C\n");
    }
#elif __INTEL_MKL__ == 2018 && __INTEL_MKL_UPDATE__ < 2
    throw std::runtime_error(
        "Intel MKL version 18 must have update 2 - use intel/18.2.xyz\n");
    (void)m;
    (void)n;
    (void)k;
    (void)entriesC;
    (void)valuesA;
    (void)valuesB;
    (void)valuesC;
    (void)transposeA;
    (void)transposeB;
    (void)verbose;
#else
    throw std::runtime_error(
        "Intel MKL versions > 18 are not yet tested/supported\n");
    // Supress -Wunused-parameter on intel-18
    (void)m;
    (void)n;
    (void)k;
    (void)entriesC;
    (void)valuesA;
    (void)valuesB;
    (void)valuesC;
    (void)transposeA;
    (void)transposeB;
    (void)verbose;
#endif  // __INTEL_MKL__ == 2018 && __INTEL_MKL_UPDATE__ >= 2
  } else {
    (void)m;
    (void)n;
    (void)k;
    (void)entriesC;
    (void)valuesA;
    (void)valuesB;
    (void)valuesC;
    (void)transposeA;
    (void)transposeB;
    (void)verbose;
    throw std::runtime_error("MKL requires local ordinals to be integer.\n");
  }
#else
  (void)handle;
  (void)m;
  (void)n;
  (void)k;
  (void)row_mapA;
  (void)row_mapB;
  (void)row_mapC;
  (void)entriesA;
  (void)entriesB;
  (void)entriesC;
  (void)valuesA;
  (void)valuesB;
  (void)valuesC;
  (void)transposeA;
  (void)transposeB;
  (void)verbose;
  throw std::runtime_error("MKL IS NOT DEFINED\n");
#endif  // KOKKOSKERNELS_ENABLE_TPL_MKL
}  // end mkl2phase_apply
}  // namespace Impl
}  // namespace KokkosSparse

#endif
