//===-- lib/Evaluate/intrinsics-library-templates.h -------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef FORTRAN_EVALUATE_INTRINSICS_LIBRARY_TEMPLATES_H_
#define FORTRAN_EVALUATE_INTRINSICS_LIBRARY_TEMPLATES_H_

// This header defines the actual implementation of the templatized member
// function of the structures defined in intrinsics-library.h. It should only be
// included if these member functions are used, else intrinsics-library.h is
// sufficient. This is to avoid circular dependencies. The below implementation
// cannot be defined in .cpp file because it would be too cumbersome to decide
// which version should be instantiated in a generic way.

#include "host.h"
#include "flang/Common/template.h"
#include "flang/Evaluate/intrinsics-library.h"
#include "flang/Evaluate/type.h"

#include <tuple>
#include <type_traits>

namespace Fortran::evaluate {

// Define meaningful types for the runtime
using RuntimeTypes = evaluate::AllIntrinsicTypes;

template <typename T, typename... TT> struct IndexInTupleHelper {};
template <typename T, typename... TT>
struct IndexInTupleHelper<T, std::tuple<TT...>> {
  static constexpr TypeCode value{common::TypeIndex<T, TT...>};
};

static_assert(
    std::tuple_size_v<RuntimeTypes> < std::numeric_limits<TypeCode>::max(),
    "TypeCode is too small");
template <typename T>
inline constexpr TypeCode typeCodeOf{
    IndexInTupleHelper<T, RuntimeTypes>::value};

template <TypeCode n>
using RuntimeTypeOf = typename std::tuple_element_t<n, RuntimeTypes>;

template <typename TA, PassBy Pass>
using HostArgType = std::conditional_t<Pass == PassBy::Ref,
    std::add_lvalue_reference_t<std::add_const_t<host::HostType<TA>>>,
    host::HostType<TA>>;

template <typename TR, typename... ArgInfo>
using HostFuncPointer = FuncPointer<host::HostType<TR>,
    HostArgType<typename ArgInfo::Type, ArgInfo::pass>...>;

// Software Subnormal Flushing helper.
template <typename T> struct Flusher {
  // Only flush floating-points. Forward other scalars untouched.
  static constexpr inline const Scalar<T> &FlushSubnormals(const Scalar<T> &x) {
    return x;
  }
};
template <int Kind> struct Flusher<Type<TypeCategory::Real, Kind>> {
  using T = Type<TypeCategory::Real, Kind>;
  static constexpr inline Scalar<T> FlushSubnormals(const Scalar<T> &x) {
    return x.FlushSubnormalToZero();
  }
};
template <int Kind> struct Flusher<Type<TypeCategory::Complex, Kind>> {
  using T = Type<TypeCategory::Complex, Kind>;
  static constexpr inline Scalar<T> FlushSubnormals(const Scalar<T> &x) {
    return x.FlushSubnormalToZero();
  }
};

// Callable factory
template <typename TR, typename... ArgInfo> struct CallableHostWrapper {
  static Scalar<TR> scalarCallable(FoldingContext &context,
      HostFuncPointer<TR, ArgInfo...> func,
      const Scalar<typename ArgInfo::Type> &... x) {
    if constexpr (host::HostTypeExists<TR, typename ArgInfo::Type...>()) {
      host::HostFloatingPointEnvironment hostFPE;
      hostFPE.SetUpHostFloatingPointEnvironment(context);
      host::HostType<TR> hostResult{};
      Scalar<TR> result{};
      if (context.flushSubnormalsToZero() &&
          !hostFPE.hasSubnormalFlushingHardwareControl()) {
        hostResult = func(host::CastFortranToHost<typename ArgInfo::Type>(
            Flusher<typename ArgInfo::Type>::FlushSubnormals(x))...);
        result = Flusher<TR>::FlushSubnormals(
            host::CastHostToFortran<TR>(hostResult));
      } else {
        hostResult =
            func(host::CastFortranToHost<typename ArgInfo::Type>(x)...);
        result = host::CastHostToFortran<TR>(hostResult);
      }
      if (!hostFPE.hardwareFlagsAreReliable()) {
        CheckFloatingPointIssues(hostFPE, result);
      }
      hostFPE.CheckAndRestoreFloatingPointEnvironment(context);
      return result;
    } else {
      common::die("Internal error: Host does not supports this function type."
                  "This should not have been called for folding");
    }
  }
  static constexpr inline auto MakeScalarCallable() { return &scalarCallable; }

  static void CheckFloatingPointIssues(
      host::HostFloatingPointEnvironment &hostFPE, const Scalar<TR> &x) {
    if constexpr (TR::category == TypeCategory::Complex ||
        TR::category == TypeCategory::Real) {
      if (x.IsNotANumber()) {
        hostFPE.SetFlag(RealFlag::InvalidArgument);
      } else if (x.IsInfinite()) {
        hostFPE.SetFlag(RealFlag::Overflow);
      }
    }
  }
};

template <typename TR, typename... TA>
inline GenericFunctionPointer ToGenericFunctionPointer(
    FuncPointer<TR, TA...> f) {
  return reinterpret_cast<GenericFunctionPointer>(f);
}

template <typename TR, typename... TA>
inline FuncPointer<TR, TA...> FromGenericFunctionPointer(
    GenericFunctionPointer g) {
  return reinterpret_cast<FuncPointer<TR, TA...>>(g);
}

template <typename TR, typename... ArgInfo>
IntrinsicProcedureRuntimeDescription::IntrinsicProcedureRuntimeDescription(
    const Signature<TR, ArgInfo...> &signature, bool isElemental)
    : name{signature.name}, returnType{typeCodeOf<TR>},
      argumentsType{typeCodeOf<typename ArgInfo::Type>...},
      argumentsPassedBy{ArgInfo::pass...}, isElemental{isElemental},
      callable{ToGenericFunctionPointer(
          CallableHostWrapper<TR, ArgInfo...>::MakeScalarCallable())} {}

template <typename HostTA> static constexpr inline PassBy PassByMethod() {
  if constexpr (std::is_pointer_v<std::decay_t<HostTA>> ||
      std::is_lvalue_reference_v<HostTA>) {
    return PassBy::Ref;
  }
  return PassBy::Val;
}

template <typename HostTA>
using ArgInfoFromHostType =
    ArgumentInfo<host::FortranType<std::remove_pointer_t<std::decay_t<HostTA>>>,
        PassByMethod<HostTA>()>;

template <typename HostTR, typename... HostTA>
using SignatureFromHostFuncPointer =
    Signature<host::FortranType<HostTR>, ArgInfoFromHostType<HostTA>...>;

template <typename HostTR, typename... HostTA>
HostRuntimeIntrinsicProcedure::HostRuntimeIntrinsicProcedure(
    const std::string &name, FuncPointer<HostTR, HostTA...> func,
    bool isElemental)
    : IntrinsicProcedureRuntimeDescription(
          SignatureFromHostFuncPointer<HostTR, HostTA...>{name}, isElemental),
      handle{ToGenericFunctionPointer(func)} {}

template <template <typename> typename ConstantContainer, typename TR,
    typename... TA>
std::optional<HostProcedureWrapper<ConstantContainer, TR, TA...>>
HostIntrinsicProceduresLibrary::GetHostProcedureWrapper(
    const std::string &name) const {
  if constexpr (host::HostTypeExists<TR, TA...>()) {
    auto rteProcRange{procedures_.equal_range(name)};
    const TypeCode resTypeCode{typeCodeOf<TR>};
    const std::vector<TypeCode> argTypes{typeCodeOf<TA>...};
    const size_t nargs{argTypes.size()};
    for (auto iter{rteProcRange.first}; iter != rteProcRange.second; ++iter) {
      if (nargs == iter->second.argumentsType.size() &&
          resTypeCode == iter->second.returnType &&
          (!std::is_same_v<ConstantContainer<TR>, Scalar<TR>> ||
              iter->second.isElemental)) {
        bool match{true};
        int pos{0};
        for (auto const &type : argTypes) {
          if (type != iter->second.argumentsType[pos++]) {
            match = false;
            break;
          }
        }
        if (match) {
          return {HostProcedureWrapper<ConstantContainer, TR, TA...>{
              [=](FoldingContext &context,
                  const ConstantContainer<TA> &... args) {
                auto callable{FromGenericFunctionPointer<ConstantContainer<TR>,
                    FoldingContext &, GenericFunctionPointer,
                    const ConstantContainer<TA> &...>(iter->second.callable)};
                return callable(context, iter->second.handle, args...);
              }}};
        }
      }
    }
  }
  return std::nullopt;
}

} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_INTRINSICS_LIBRARY_TEMPLATES_H_
