#ifndef __KOKKOSBATCHED_TRSM_SERIAL_IMPL_HPP__
#define __KOKKOSBATCHED_TRSM_SERIAL_IMPL_HPP__


/// \author Kyungjoo Kim (kyukim@sandia.gov)

#include "KokkosBatched_Util.hpp"
#include "KokkosBatched_Trsm_Serial_Internal.hpp"

namespace KokkosBatched {

  ///
  /// L/L/NT
  ///
  /// B := inv(tril(A)) (alpha*B)
  /// A(m x m), B(m x n)

#if                                                     \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL__) &&               \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL_BATCHED__) &&       \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL_COMPACT_BATCHED__)    
  template<typename ArgDiag>
  struct SerialTrsm<Side::Left,Uplo::Lower,Trans::NoTranspose,ArgDiag,Algo::Trsm::CompactMKL> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      typedef typename BViewType::value_type vector_type;
      //typedef typename vector_type::value_type value_type;
        
      const int
        m = B.extent(0),
        n = B.extent(1);

      static_assert(is_vector<vector_type>::value, "value type is not vector type");      
      static_assert(vector_type::vector_length == 4 || vector_type::vector_length == 8, 
                    "AVX, AVX2 and AVX512 is supported");
      const MKL_COMPACT_PACK format = vector_type::vector_length == 8 ?  MKL_COMPACT_AVX512 : MKL_COMPACT_AVX;
        
      // no error check
      int r_val = 0;
      if (A.stride_0() == 1 && B.stride_0() == 1) {
        mkl_dtrsm_compact(MKL_COL_MAJOR, 
                          MKL_LEFT, MKL_LOWER, MKL_NOTRANS, 
                          ArgDiag::use_unit_diag ? MKL_UNIT : MKL_NONUNIT,
                          m, n, 
                          alpha, 
                          (const double*)A.data(), A.stride_1(), 
                          (double*)B.data(), B.stride_1(), 
                          format, (MKL_INT)vector_type::vector_length);
      } else if (A.stride_1() == 1 && B.stride_1() == 1) {
        mkl_dtrsm_compact(MKL_ROW_MAJOR, 
                          MKL_LEFT, MKL_LOWER, MKL_NOTRANS, 
                          ArgDiag::use_unit_diag ? MKL_UNIT : MKL_NONUNIT,
                          m, n, 
                          alpha, 
                          (const double*)A.data(), A.stride_0(), 
                          (double*)B.data(), B.stride_0(), 
                          format, (MKL_INT)vector_type::vector_length);
      } else {
        r_val = -1;
      }
      return r_val;
    }
  };
#endif    

  template<typename ArgDiag>
  struct SerialTrsm<Side::Left,Uplo::Lower,Trans::NoTranspose,ArgDiag,Algo::Trsm::Unblocked> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      return SerialTrsmInternalLeftLower<Algo::Trsm::Unblocked>::invoke(ArgDiag::use_unit_diag,
                                                                        B.extent(0), B.extent(1),
                                                                        alpha, 
                                                                        A.data(), A.stride_0(), A.stride_1(),
                                                                        B.data(), B.stride_0(), B.stride_1());
    }
  };

  template<typename ArgDiag>
  struct SerialTrsm<Side::Left,Uplo::Lower,Trans::NoTranspose,ArgDiag,Algo::Trsm::Blocked> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      return SerialTrsmInternalLeftLower<Algo::Trsm::Blocked>::invoke(ArgDiag::use_unit_diag,
                                                                      B.extent(0), B.extent(1),
                                                                      alpha, 
                                                                      A.data(), A.stride_0(), A.stride_1(),
                                                                      B.data(), B.stride_0(), B.stride_1());
    }
  };

  ///
  /// R/U/NT
  ///
  /// B := (alpha*B) inv(triu(A))
  /// A(n x n), B(m x n)
#if                                                     \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL__) &&               \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL_BATCHED__) &&       \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL_COMPACT_BATCHED__)
  template<typename ArgDiag>
  struct SerialTrsm<Side::Right,Uplo::Upper,Trans::NoTranspose,ArgDiag,Algo::Trsm::CompactMKL> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      typedef typename BViewType::value_type vector_type;
      //typedef typename vector_type::value_type value_type;
        
      const int
        m = B.extent(0),
        n = B.extent(1);

      static_assert(is_vector<vector_type>::value, "value type is not vector type");      
      static_assert(vector_type::vector_length == 4 || vector_type::vector_length == 8, 
                    "AVX, AVX2 and AVX512 is supported");
      const MKL_COMPACT_PACK format = vector_type::vector_length == 8 ?  MKL_COMPACT_AVX512 : MKL_COMPACT_AVX;

      // no error check
      int r_val = 0;
      if (A.stride_0() == 1 && B.stride_0() == 1) {
        mkl_dtrsm_compact(MKL_COL_MAJOR, 
                          MKL_RIGHT, MKL_UPPER, MKL_NOTRANS, 
                          ArgDiag::use_unit_diag ? MKL_UNIT : MKL_NONUNIT,
                          m, n, 
                          alpha, 
                          (const double*)A.data(), A.stride_1(), 
                          (double*)B.data(), B.stride_1(), 
                          format, (MKL_INT)vector_type::vector_length);
      } else if (A.stride_1() == 1 && B.stride_1() == 1) {
        mkl_dtrsm_compact(MKL_ROW_MAJOR, 
                          MKL_RIGHT, MKL_UPPER, MKL_NOTRANS, 
                          ArgDiag::use_unit_diag ? MKL_UNIT : MKL_NONUNIT,
                          m, n, 
                          alpha, 
                          (const double*)A.data(), A.stride_0(), 
                          (double*)B.data(), B.stride_0(), 
                          format, (MKL_INT)vector_type::vector_length);
      } else {
        r_val = -1;
      }
      return r_val;
    }
  };
#endif

  template<typename ArgDiag>
  struct SerialTrsm<Side::Right,Uplo::Upper,Trans::NoTranspose,ArgDiag,Algo::Trsm::Unblocked> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      return SerialTrsmInternalLeftLower<Algo::Trsm::Unblocked>::invoke(ArgDiag::use_unit_diag,
                                                                        B.extent(1), B.extent(0),
                                                                        alpha, 
                                                                        A.data(), A.stride_1(), A.stride_0(),
                                                                        B.data(), B.stride_1(), B.stride_0());
    }
  };

  template<typename ArgDiag>
  struct SerialTrsm<Side::Right,Uplo::Upper,Trans::NoTranspose,ArgDiag,Algo::Trsm::Blocked> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      return SerialTrsmInternalLeftLower<Algo::Trsm::Blocked>::invoke(ArgDiag::use_unit_diag,
                                                                      B.extent(1), B.extent(0),
                                                                      alpha, 
                                                                      A.data(), A.stride_1(), A.stride_0(),
                                                                      B.data(), B.stride_1(), B.stride_0());
    }      
  };
    
  ///
  /// L/U/NT
  ///
  /// B := inv(triu(A)) (alpha*B) 
  /// A(m x m), B(m x n)
#if                                                     \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL__) &&               \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL_BATCHED__) &&       \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL_COMPACT_BATCHED__)
  template<typename ArgDiag>
  struct SerialTrsm<Side::Left,Uplo::Upper,Trans::NoTranspose,ArgDiag,Algo::Trsm::CompactMKL> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      typedef typename BViewType::value_type vector_type;
      //typedef typename vector_type::value_type value_type;
        
      const int
        m = B.extent(0),
        n = B.extent(1);

      static_assert(is_vector<vector_type>::value, "value type is not vector type");      
      static_assert(vector_type::vector_length == 4 || vector_type::vector_length == 8, 
                    "AVX, AVX2 and AVX512 is supported");
      const MKL_COMPACT_PACK format = vector_type::vector_length == 8 ?  MKL_COMPACT_AVX512 : MKL_COMPACT_AVX;

      // no error check
      int r_val = 0;
      if (A.stride_0() == 1 && B.stride_0() == 1) {
        mkl_dtrsm_compact(MKL_COL_MAJOR, 
                          MKL_LEFT, MKL_UPPER, MKL_NOTRANS, 
                          ArgDiag::use_unit_diag ? MKL_UNIT : MKL_NONUNIT,
                          m, n, 
                          alpha, 
                          (const double*)A.data(), A.stride_1(), 
                          (double*)B.data(), B.stride_1(), 
                          format, (MKL_INT)vector_type::vector_length);
      } else if (A.stride_1() == 1 && B.stride_1() == 1) {
        mkl_dtrsm_compact(MKL_ROW_MAJOR, 
                          MKL_LEFT, MKL_UPPER, MKL_NOTRANS, 
                          ArgDiag::use_unit_diag ? MKL_UNIT : MKL_NONUNIT,
                          m, n, 
                          alpha, 
                          (const double*)A.data(), A.stride_0(), 
                          (double*)B.data(), B.stride_0(), 
                          format, (MKL_INT)vector_type::vector_length);
      } else {
        r_val = -1;
      }
      return r_val;
    }
  };
#endif

  template<typename ArgDiag>
  struct SerialTrsm<Side::Left,Uplo::Upper,Trans::NoTranspose,ArgDiag,Algo::Trsm::Unblocked> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      return SerialTrsmInternalLeftUpper<Algo::Trsm::Unblocked>::invoke(ArgDiag::use_unit_diag,
                                                                        B.extent(0), B.extent(1),
                                                                        alpha, 
                                                                        A.data(), A.stride_0(), A.stride_1(),
                                                                        B.data(), B.stride_0(), B.stride_1());
    }
  };

  template<typename ArgDiag>
  struct SerialTrsm<Side::Left,Uplo::Upper,Trans::NoTranspose,ArgDiag,Algo::Trsm::Blocked> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      return SerialTrsmInternalLeftUpper<Algo::Trsm::Blocked>::invoke(ArgDiag::use_unit_diag,
                                                                      B.extent(0), B.extent(1),
                                                                      alpha, 
                                                                      A.data(), A.stride_0(), A.stride_1(),
                                                                      B.data(), B.stride_0(), B.stride_1());
    }
  };

  ///
  /// L/L/T
  ///
  /// B := inv(tril(AT)) (alpha*B)
  /// A(m x m), B(m x n)

#if                                                     \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL__) &&               \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL_BATCHED__) &&       \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL_COMPACT_BATCHED__)    
  template<typename ArgDiag>
  struct SerialTrsm<Side::Left,Uplo::Lower,Trans::Transpose,ArgDiag,Algo::Trsm::CompactMKL> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      typedef typename BViewType::value_type vector_type;
      //typedef typename vector_type::value_type value_type;
        
      const int
        m = B.extent(0),
        n = B.extent(1);

      static_assert(is_vector<vector_type>::value, "value type is not vector type");      
      static_assert(vector_type::vector_length == 4 || vector_type::vector_length == 8, 
                    "AVX, AVX2 and AVX512 is supported");
      const MKL_COMPACT_PACK format = vector_type::vector_length == 8 ?  MKL_COMPACT_AVX512 : MKL_COMPACT_AVX;
        
      // no error check
      int r_val = 0;
      if (A.stride_0() == 1 && B.stride_0() == 1) {
        mkl_dtrsm_compact(MKL_COL_MAJOR, 
                          MKL_LEFT, MKL_LOWER, MKL_TRANS, 
                          ArgDiag::use_unit_diag ? MKL_UNIT : MKL_NONUNIT,
                          m, n, 
                          alpha, 
                          (const double*)A.data(), A.stride_1(), 
                          (double*)B.data(), B.stride_1(), 
                          format, (MKL_INT)vector_type::vector_length);
      } else if (A.stride_1() == 1 && B.stride_1() == 1) {
        mkl_dtrsm_compact(MKL_ROW_MAJOR, 
                          MKL_LEFT, MKL_LOWER, MKL_TRANS, 
                          ArgDiag::use_unit_diag ? MKL_UNIT : MKL_NONUNIT,
                          m, n, 
                          alpha, 
                          (const double*)A.data(), A.stride_0(), 
                          (double*)B.data(), B.stride_0(), 
                          format, (MKL_INT)vector_type::vector_length);
      } else {
        r_val = -1;
      }
      return r_val;
    }
  };
#endif    

  template<typename ArgDiag>
  struct SerialTrsm<Side::Left,Uplo::Lower,Trans::Transpose,ArgDiag,Algo::Trsm::Unblocked> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      return SerialTrsmInternalLeftUpper<Algo::Trsm::Unblocked>::invoke(ArgDiag::use_unit_diag,
                                                                        B.extent(0), B.extent(1),
                                                                        alpha, 
                                                                        A.data(), A.stride_1(), A.stride_0(),
                                                                        B.data(), B.stride_0(), B.stride_1());
    }
  };

  template<typename ArgDiag>
  struct SerialTrsm<Side::Left,Uplo::Lower,Trans::Transpose,ArgDiag,Algo::Trsm::Blocked> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      return SerialTrsmInternalLeftUpper<Algo::Trsm::Blocked>::invoke(ArgDiag::use_unit_diag,
                                                                      B.extent(0), B.extent(1),
                                                                      alpha, 
                                                                      A.data(), A.stride_1(), A.stride_0(),
                                                                      B.data(), B.stride_0(), B.stride_1());
    }
  };
  ///
  /// L/U/NT
  ///
  /// B := inv(triu(AT)) (alpha*B) 
  /// A(m x m), B(m x n)
#if                                                     \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL__) &&               \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL_BATCHED__) &&       \
  defined(__KOKKOSBATCHED_ENABLE_INTEL_MKL_COMPACT_BATCHED__)
  template<typename ArgDiag>
  struct SerialTrsm<Side::Left,Uplo::Upper,Trans::Transpose,ArgDiag,Algo::Trsm::CompactMKL> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      typedef typename BViewType::value_type vector_type;
      //typedef typename vector_type::value_type value_type;
        
      const int
        m = B.extent(0),
        n = B.extent(1);

      static_assert(is_vector<vector_type>::value, "value type is not vector type");      
      static_assert(vector_type::vector_length == 4 || vector_type::vector_length == 8, 
                    "AVX, AVX2 and AVX512 is supported");
      const MKL_COMPACT_PACK format = vector_type::vector_length == 8 ?  MKL_COMPACT_AVX512 : MKL_COMPACT_AVX;

      // no error check
      int r_val = 0;
      if (A.stride_0() == 1 && B.stride_0() == 1) {
        mkl_dtrsm_compact(MKL_COL_MAJOR, 
                          MKL_LEFT, MKL_UPPER, MKL_TRANS, 
                          ArgDiag::use_unit_diag ? MKL_UNIT : MKL_NONUNIT,
                          m, n, 
                          alpha, 
                          (const double*)A.data(), A.stride_1(), 
                          (double*)B.data(), B.stride_1(), 
                          format, (MKL_INT)vector_type::vector_length);
      } else if (A.stride_1() == 1 && B.stride_1() == 1) {
        mkl_dtrsm_compact(MKL_ROW_MAJOR, 
                          MKL_LEFT, MKL_UPPER, MKL_TRANS, 
                          ArgDiag::use_unit_diag ? MKL_UNIT : MKL_NONUNIT,
                          m, n, 
                          alpha, 
                          (const double*)A.data(), A.stride_0(), 
                          (double*)B.data(), B.stride_0(), 
                          format, (MKL_INT)vector_type::vector_length);
      } else {
        r_val = -1;
      }
      return r_val;
    }
  };
#endif

  template<typename ArgDiag>
  struct SerialTrsm<Side::Left,Uplo::Upper,Trans::Transpose,ArgDiag,Algo::Trsm::Unblocked> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      return SerialTrsmInternalLeftLower<Algo::Trsm::Unblocked>::invoke(ArgDiag::use_unit_diag,
                                                                        B.extent(0), B.extent(1),
                                                                        alpha, 
                                                                        A.data(), A.stride_1(), A.stride_0(),
                                                                        B.data(), B.stride_0(), B.stride_1());
    }
  };

  template<typename ArgDiag>
  struct SerialTrsm<Side::Left,Uplo::Upper,Trans::Transpose,ArgDiag,Algo::Trsm::Blocked> {
    template<typename ScalarType,
             typename AViewType,
             typename BViewType>
    KOKKOS_INLINE_FUNCTION
    static int
    invoke(const ScalarType alpha,
           const AViewType &A,
           const BViewType &B) {
      return SerialTrsmInternalLeftLower<Algo::Trsm::Blocked>::invoke(ArgDiag::use_unit_diag,
                                                                      B.extent(0), B.extent(1),
                                                                      alpha, 
                                                                      A.data(), A.stride_1(), A.stride_0(),
                                                                      B.data(), B.stride_0(), B.stride_1());
    }
  };

}


#endif
