// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2021 Erik Schultheis <erik.schultheis@aalto.fi>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ifndef EIGEN_LAPACKE_HELPERS_H
#define EIGEN_LAPACKE_HELPERS_H

#include "./InternalHeaderCheck.h"

#ifdef EIGEN_USE_MKL
#include "mkl_lapacke.h"
#else
#include "lapacke.h"
#endif

namespace Eigen {
namespace internal {
/**
 * \internal
 * \brief Implementation details and helper functions for the lapacke glue code.
 */
namespace lapacke_helpers {

// ---------------------------------------------------------------------------------------------------------------------
//                  Translation from Eigen to Lapacke for types and constants
// ---------------------------------------------------------------------------------------------------------------------

// For complex numbers, the types in Eigen and Lapacke are different, but layout compatible.
template<typename Scalar>
struct translate_type_imp;
template<>
struct translate_type_imp<float> {
    using type = float;
};
template<>
struct translate_type_imp<double> {
    using type = double;
};
template<>
struct translate_type_imp<std::complex<double>> {
    using type = lapack_complex_double;
};
template<>
struct translate_type_imp<std::complex<float>> {
    using type = lapack_complex_float;
};

/// Given an Eigen types, this is defined to be the corresponding, layout-compatible lapack type
template<typename Scalar>
using translated_type = typename translate_type_imp<Scalar>::type;

/// These functions convert their arguments from Eigen to Lapack types
/// This function performs conversion for any of the translations defined above.
template<typename Source, typename Target=translated_type<Source>>
auto to_lapack(Source value) { return static_cast<Target>(value); }

/// This function performs conversions for pointer types corresponding to the translations abovce.
/// This is valid because the translations are between layout-compatible types.
template<typename Source, typename Target=translated_type<Source>>
auto to_lapack(Source *value) { return reinterpret_cast<Target*>(value); }

/// This function converts the Eigen Index to a lapack index, with possible range checks
/// \sa internal::convert_index
lapack_int to_lapack(Index index) {
  return convert_index<lapack_int>(index);
}

/// translates storage order of the given Eigen object to the corresponding lapack constant
template<typename Derived>
EIGEN_CONSTEXPR lapack_int lapack_storage_of(const EigenBase<Derived> &) {
  return Derived::IsRowMajor ? LAPACK_ROW_MAJOR : LAPACK_COL_MAJOR;
}

/// translate UpLo type to the corresponding letter code
template<UpLoType mode> char translate_mode;
template<> EIGEN_CONSTEXPR const char translate_mode<Lower> = 'L';
template<> EIGEN_CONSTEXPR const char translate_mode<Upper> = 'U';


// ---------------------------------------------------------------------------------------------------------------------
//              Automatic generation of low-level wrappers
// ---------------------------------------------------------------------------------------------------------------------

/*!
 * \internal
 * \brief Helper type to facilitate the wrapping of raw LAPACKE functions for different types into a single, overloaded C++ function.
 * This is achieved in combination with \r EIGEN_MAKE_LAPACKE_WRAPPER
 * \details This implementation works by providing an overloaded call function that just forwards its arguments to the
 * underlying lapack function. Each of these overloads is enabled only if the call is actually well formed.
 * Because these lapack functions take pointers to the underlying scalar type as arguments, even though the actual Scalars
 * would be implicitly convertible, the pointers are not and therefore only a single overload can be valid at the same time.
 * Thus, despite all functions taking fully generic `Args&&... args` as arguments, there is never any ambiguity.
 */
template<typename DoubleFn, typename SingleFn, typename DoubleCpxFn, typename SingleCpxFn>
struct WrappingHelper {
  // The naming of double, single, double complex and single complex is purely for readability
  // and doesn't actually affect the workings of this class. In principle, the arguments can
  // be supplied in any permuted order.
  DoubleFn double_; SingleFn single_; DoubleCpxFn double_cpx_; SingleCpxFn single_cpx_;

  template<typename... Args>
  auto call(Args&&... args) -> decltype(double_(std::forward<Args>(args)...)) {
    return double_(std::forward<Args>(args)...);
  }

  template<typename... Args>
  auto call(Args&&... args) -> decltype(single_(std::forward<Args>(args)...)){
    return single_(std::forward<Args>(args)...);
  }

  template<typename... Args>
  auto call(Args&&... args) -> decltype(double_cpx_(std::forward<Args>(args)...)){
    return double_cpx_(std::forward<Args>(args)...);
  }

  template<typename... Args>
  auto call(Args&&... args) -> decltype(single_cpx_(std::forward<Args>(args)...)){
    return single_cpx_(std::forward<Args>(args)...);
  }
};

/** \internal Helper function that generates a `WrappingHelper` object with the given function pointers and
 * invokes its `call` method, thus selecting one of the overloads.
 * \sa EIGEN_MAKE_LAPACKE_WRAPPER
 */
template<typename DoubleFn, typename SingleFn, typename DoubleCpxFn, typename SingleCpxFn, typename... Args>
auto call_wrapper(DoubleFn df, SingleFn sf, DoubleCpxFn dcf, SingleCpxFn scf, Args&&... args) {
  WrappingHelper<DoubleFn, SingleFn, DoubleCpxFn, SingleCpxFn> helper{df, sf, dcf, scf};
  return helper.call(std::forward<Args>(args)...);
}

/**
 * \internal
 * Generates a new function `Function` that dispatches to the corresponding LAPACKE_? prefixed functions.
 * \sa WrappingHelper
 */
#define EIGEN_MAKE_LAPACKE_WRAPPER(FUNCTION) \
template<typename... Args> \
auto FUNCTION(Args&&... args) { return call_wrapper(LAPACKE_d##FUNCTION, LAPACKE_s##FUNCTION, LAPACKE_z##FUNCTION, LAPACKE_c##FUNCTION, std::forward<Args>(args)...); }

// Now with this macro and the helper wrappers, we can generate the dispatch for all the lapacke functions that are
// used in Eigen.
// We define these here instead of in the files where they are used because this allows us to #undef the macro again
// right here
EIGEN_MAKE_LAPACKE_WRAPPER(potrf)
EIGEN_MAKE_LAPACKE_WRAPPER(getrf)
EIGEN_MAKE_LAPACKE_WRAPPER(geqrf)

#undef EIGEN_MAKE_LAPACKE_WRAPPER
}
}
}

#endif // EIGEN_LAPACKE_HELPERS_H