//==------ enqueue_functions.hpp ------- SYCL enqueue free functions -------==//
//
// 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
//
//===----------------------------------------------------------------------===//

#pragma once

#include <utility>

#include <sycl/detail/common.hpp>
#include <sycl/event.hpp>
#include <sycl/ext/oneapi/experimental/enqueue_types.hpp>
#include <sycl/ext/oneapi/experimental/free_function_traits.hpp>
#include <sycl/ext/oneapi/experimental/graph.hpp>
#include <sycl/ext/oneapi/properties/properties.hpp>
#include <sycl/handler.hpp>
#include <sycl/nd_range.hpp>
#include <sycl/queue.hpp>
#include <sycl/range.hpp>

namespace sycl {
inline namespace _V1 {
namespace ext::oneapi::experimental {

namespace detail {
// Trait for identifying sycl::range and sycl::nd_range.
template <typename RangeT> struct is_range_or_nd_range : std::false_type {};
template <int Dimensions>
struct is_range_or_nd_range<range<Dimensions>> : std::true_type {};
template <int Dimensions>
struct is_range_or_nd_range<nd_range<Dimensions>> : std::true_type {};

template <typename RangeT>
constexpr bool is_range_or_nd_range_v = is_range_or_nd_range<RangeT>::value;

template <typename LCRangeT, typename LCPropertiesT> struct LaunchConfigAccess;

// Checks that none of the properties in the property list has compile-time
// effects on the kernel.
template <typename T>
struct NoPropertyHasCompileTimeKernelEffect : std::false_type {};
template <typename... Ts>
struct NoPropertyHasCompileTimeKernelEffect<properties_t<Ts...>> {
  static constexpr bool value =
      !(HasCompileTimeEffect<Ts>::value || ... || false);
};
} // namespace detail

// Available only when Range is range or nd_range
template <
    typename RangeT, typename PropertiesT = empty_properties_t,
    typename = std::enable_if_t<
        ext::oneapi::experimental::detail::is_range_or_nd_range_v<RangeT>>>
class launch_config {
  static_assert(ext::oneapi::experimental::detail::
                    NoPropertyHasCompileTimeKernelEffect<PropertiesT>::value,
                "launch_config does not allow properties with compile-time "
                "kernel effects.");

public:
  launch_config(RangeT Range, PropertiesT Properties = {})
      : MRange{Range}, MProperties{Properties} {}

private:
  RangeT MRange;
  PropertiesT MProperties;

  const RangeT &getRange() const noexcept { return MRange; }

  const PropertiesT &getProperties() const noexcept { return MProperties; }

  template <typename LCRangeT, typename LCPropertiesT>
  friend struct detail::LaunchConfigAccess;
};

#ifdef __cpp_deduction_guides
// CTAD work-around to avoid warning from GCC when using default deduction
// guidance.
launch_config(detail::AllowCTADTag)
    -> launch_config<void, empty_properties_t, void>;
#endif // __cpp_deduction_guides

namespace detail {
// Helper for accessing the members of launch_config.
template <typename LCRangeT, typename LCPropertiesT> struct LaunchConfigAccess {
  LaunchConfigAccess(const launch_config<LCRangeT, LCPropertiesT> &LaunchConfig)
      : MLaunchConfig{LaunchConfig} {}

  const launch_config<LCRangeT, LCPropertiesT> &MLaunchConfig;

  const LCRangeT &getRange() const noexcept { return MLaunchConfig.getRange(); }

  const LCPropertiesT &getProperties() const noexcept {
    return MLaunchConfig.getProperties();
  }
};

template <typename CommandGroupFunc, typename PropertiesT>
void submit_impl(const queue &Q, PropertiesT Props, CommandGroupFunc &&CGF,
                 const sycl::detail::code_location &CodeLoc) {
  Q.submit_without_event(Props, detail::type_erased_cgfo_ty{CGF}, CodeLoc);
}

template <typename CommandGroupFunc, typename PropertiesT>
event submit_with_event_impl(const queue &Q, PropertiesT Props,
                             CommandGroupFunc &&CGF,
                             const sycl::detail::code_location &CodeLoc) {
  return Q.submit_with_event(Props, detail::type_erased_cgfo_ty{CGF}, CodeLoc);
}
} // namespace detail

template <typename CommandGroupFunc, typename PropertiesT>
void submit(const queue &Q, PropertiesT Props, CommandGroupFunc &&CGF,
            const sycl::detail::code_location &CodeLoc =
                sycl::detail::code_location::current()) {
  sycl::ext::oneapi::experimental::detail::submit_impl(
      Q, Props, std::forward<CommandGroupFunc>(CGF), CodeLoc);
}

template <typename CommandGroupFunc>
void submit(const queue &Q, CommandGroupFunc &&CGF,
            const sycl::detail::code_location &CodeLoc =
                sycl::detail::code_location::current()) {
  submit(Q, empty_properties_t{}, std::forward<CommandGroupFunc>(CGF), CodeLoc);
}

template <typename CommandGroupFunc, typename PropertiesT>
event submit_with_event(const queue &Q, PropertiesT Props,
                        CommandGroupFunc &&CGF,
                        const sycl::detail::code_location &CodeLoc =
                            sycl::detail::code_location::current()) {
  return sycl::ext::oneapi::experimental::detail::submit_with_event_impl(
      Q, Props, std::forward<CommandGroupFunc>(CGF), CodeLoc);
}

template <typename CommandGroupFunc>
event submit_with_event(const queue &Q, CommandGroupFunc &&CGF,
                        const sycl::detail::code_location &CodeLoc =
                            sycl::detail::code_location::current()) {
  return submit_with_event(Q, empty_properties_t{},
                           std::forward<CommandGroupFunc>(CGF), CodeLoc);
}

template <typename KernelName = sycl::detail::auto_name, typename KernelType>
void single_task(handler &CGH, const KernelType &KernelObj) {
  CGH.single_task<KernelName>(KernelObj);
}

template <typename KernelName = sycl::detail::auto_name, typename KernelType>
void single_task(queue Q, const KernelType &KernelObj,
                 const sycl::detail::code_location &CodeLoc =
                     sycl::detail::code_location::current()) {
  // TODO The handler-less path does not support kernel functions with the
  // kernel_handler type argument yet.
  if constexpr (!(detail::KernelLambdaHasKernelHandlerArgT<KernelType,
                                                           void>::value)) {
    detail::submit_kernel_direct_single_task<KernelName>(
        std::move(Q), KernelObj, {}, empty_properties_t{}, CodeLoc);
  } else {
    submit(
        std::move(Q),
        [&](handler &CGH) { single_task<KernelName>(CGH, KernelObj); },
        CodeLoc);
  }
}

template <typename... ArgsT>
void single_task(handler &CGH, const kernel &KernelObj, ArgsT &&...Args) {
  CGH.set_args<ArgsT...>(std::forward<ArgsT>(Args)...);
  CGH.single_task(KernelObj);
}

template <typename... ArgsT>
void single_task(queue Q, const kernel &KernelObj, ArgsT &&...Args) {
  submit(std::move(Q), [&](handler &CGH) {
    single_task(CGH, KernelObj, std::forward<ArgsT>(Args)...);
  });
}

// Free function kernel single_task enqueue functions
template <auto *Func, typename... ArgsT>
void single_task(queue Q, [[maybe_unused]] kernel_function_s<Func> KernelFunc,
                 ArgsT &&...Args) {
  detail::submit_kernel_direct_single_task(std::move(Q),
                                           [Args...]() { Func(Args...); });
}

template <auto *Func, typename... ArgsT>
void single_task(handler &CGH,
                 [[maybe_unused]] kernel_function_s<Func> KernelFunc,
                 ArgsT &&...Args) {
  CGH.single_task([Args...]() { Func(Args...); });
}

// TODO: Make overloads for scalar arguments for range.
template <typename KernelName = sycl::detail::auto_name, int Dimensions,
          typename KernelType, typename... ReductionsT>
void parallel_for(handler &CGH, range<Dimensions> Range,
                  const KernelType &KernelObj, ReductionsT &&...Reductions) {
  CGH.parallel_for<KernelName>(Range, std::forward<ReductionsT>(Reductions)...,
                               KernelObj);
}

template <typename KernelName = sycl::detail::auto_name, int Dimensions,
          typename KernelType, typename... ReductionsT>
void parallel_for(queue Q, range<Dimensions> Range, const KernelType &KernelObj,
                  ReductionsT &&...Reductions) {
  using LambdaArgType =
      sycl::detail::lambda_arg_type<KernelType, item<Dimensions>>;
  using TransformedArgType = std::conditional_t<
      std::is_integral<LambdaArgType>::value && Dimensions == 1,
      item<Dimensions>,
      typename detail::TransformUserItemType<Dimensions, LambdaArgType>::type>;

  // TODO The handler-less path does not support reductions, and
  // kernel functions with the kernel_handler type argument yet.
  if constexpr (sizeof...(ReductionsT) == 0 &&
                !(detail::KernelLambdaHasKernelHandlerArgT<
                    KernelType, TransformedArgType>::value)) {
    detail::submit_kernel_direct_parallel_for<KernelName>(std::move(Q), Range,
                                                          KernelObj);
  } else {
    submit(std::move(Q), [&](handler &CGH) {
      parallel_for<KernelName>(CGH, Range, KernelObj,
                               std::forward<ReductionsT>(Reductions)...);
    });
  }
}

template <typename KernelName = sycl::detail::auto_name, int Dimensions,
          typename Properties, typename KernelType, typename... ReductionsT>
void parallel_for(handler &CGH,
                  launch_config<range<Dimensions>, Properties> Config,
                  const KernelType &KernelObj, ReductionsT &&...Reductions) {
  ext::oneapi::experimental::detail::LaunchConfigAccess<range<Dimensions>,
                                                        Properties>
      ConfigAccess(Config);
  CGH.parallel_for<KernelName>(
      ConfigAccess.getRange(), ConfigAccess.getProperties(),
      std::forward<ReductionsT>(Reductions)..., KernelObj);
}

template <typename KernelName = sycl::detail::auto_name, int Dimensions,
          typename Properties, typename KernelType, typename... ReductionsT>
void parallel_for(queue Q, launch_config<range<Dimensions>, Properties> Config,
                  const KernelType &KernelObj, ReductionsT &&...Reductions) {
  using LambdaArgType =
      sycl::detail::lambda_arg_type<KernelType, item<Dimensions>>;
  using TransformedArgType = std::conditional_t<
      std::is_integral<LambdaArgType>::value && Dimensions == 1,
      item<Dimensions>,
      typename detail::TransformUserItemType<Dimensions, LambdaArgType>::type>;

  // TODO The handler-less path does not support reductions, and
  // kernel functions with the kernel_handler type argument yet.
  if constexpr (sizeof...(ReductionsT) == 0 &&
                !(detail::KernelLambdaHasKernelHandlerArgT<
                    KernelType, TransformedArgType>::value)) {
    ext::oneapi::experimental::detail::LaunchConfigAccess<range<Dimensions>,
                                                          Properties>
        LaunchConfigAccess(Config);

    detail::submit_kernel_direct_parallel_for<KernelName>(
        std::move(Q), LaunchConfigAccess.getRange(), KernelObj, {},
        LaunchConfigAccess.getProperties());
  } else {
    submit(std::move(Q), [&](handler &CGH) {
      parallel_for<KernelName>(CGH, Config, KernelObj,
                               std::forward<ReductionsT>(Reductions)...);
    });
  }
}

template <int Dimensions, typename... ArgsT>
void parallel_for(handler &CGH, range<Dimensions> Range,
                  const kernel &KernelObj, ArgsT &&...Args) {
  CGH.set_args<ArgsT...>(std::forward<ArgsT>(Args)...);
  CGH.parallel_for(Range, KernelObj);
}

template <int Dimensions, typename... ArgsT>
void parallel_for(queue Q, range<Dimensions> Range, const kernel &KernelObj,
                  ArgsT &&...Args) {
  submit(std::move(Q), [&](handler &CGH) {
    parallel_for(CGH, Range, KernelObj, std::forward<ArgsT>(Args)...);
  });
}

template <int Dimensions, typename Properties, typename... ArgsT>
void parallel_for(handler &CGH,
                  launch_config<range<Dimensions>, Properties> Config,
                  const kernel &KernelObj, ArgsT &&...Args) {
  ext::oneapi::experimental::detail::LaunchConfigAccess<range<Dimensions>,
                                                        Properties>
      ConfigAccess(Config);
  CGH.set_args<ArgsT...>(std::forward<ArgsT>(Args)...);
  sycl::detail::HandlerAccess::parallelForImpl(
      CGH, ConfigAccess.getRange(), ConfigAccess.getProperties(), KernelObj);
}

template <int Dimensions, typename Properties, typename... ArgsT>
void parallel_for(queue Q, launch_config<range<Dimensions>, Properties> Config,
                  const kernel &KernelObj, ArgsT &&...Args) {
  submit(std::move(Q), [&](handler &CGH) {
    parallel_for(CGH, Config, KernelObj, std::forward<ArgsT>(Args)...);
  });
}

template <typename KernelName = sycl::detail::auto_name, int Dimensions,
          typename KernelType, typename... ReductionsT>
void nd_launch(handler &CGH, nd_range<Dimensions> Range,
               const KernelType &KernelObj, ReductionsT &&...Reductions) {
  CGH.parallel_for<KernelName>(Range, std::forward<ReductionsT>(Reductions)...,
                               KernelObj);
}

template <typename KernelName = sycl::detail::auto_name, int Dimensions,
          typename KernelType, typename... ReductionsT>
void nd_launch(queue Q, nd_range<Dimensions> Range, const KernelType &KernelObj,
               ReductionsT &&...Reductions) {
  // TODO The handler-less path does not support reductions, and
  // kernel functions with the kernel_handler type argument yet.
  if constexpr (sizeof...(ReductionsT) == 0 &&
                !(detail::KernelLambdaHasKernelHandlerArgT<
                    KernelType, sycl::nd_item<Dimensions>>::value)) {
    detail::submit_kernel_direct_parallel_for<KernelName>(std::move(Q), Range,
                                                          KernelObj);
  } else {
    submit(std::move(Q), [&](handler &CGH) {
      nd_launch<KernelName>(CGH, Range, KernelObj,
                            std::forward<ReductionsT>(Reductions)...);
    });
  }
}

template <typename KernelName = sycl::detail::auto_name, int Dimensions,
          typename Properties, typename KernelType, typename... ReductionsT>
void nd_launch(handler &CGH,
               launch_config<nd_range<Dimensions>, Properties> Config,
               const KernelType &KernelObj, ReductionsT &&...Reductions) {

  ext::oneapi::experimental::detail::LaunchConfigAccess<nd_range<Dimensions>,
                                                        Properties>
      ConfigAccess(Config);
  CGH.parallel_for<KernelName>(
      ConfigAccess.getRange(), ConfigAccess.getProperties(),
      std::forward<ReductionsT>(Reductions)..., KernelObj);
}

template <typename KernelName = sycl::detail::auto_name, int Dimensions,
          typename Properties, typename KernelType, typename... ReductionsT>
void nd_launch(queue Q, launch_config<nd_range<Dimensions>, Properties> Config,
               const KernelType &KernelObj, ReductionsT &&...Reductions) {
  // TODO The handler-less path does not support reductions, and
  // kernel functions with the kernel_handler type argument yet.
  if constexpr (sizeof...(ReductionsT) == 0 &&
                !(detail::KernelLambdaHasKernelHandlerArgT<
                    KernelType, sycl::nd_item<Dimensions>>::value)) {

    ext::oneapi::experimental::detail::LaunchConfigAccess<nd_range<Dimensions>,
                                                          Properties>
        LaunchConfigAccess(Config);

    detail::submit_kernel_direct_parallel_for<KernelName>(
        std::move(Q), LaunchConfigAccess.getRange(), KernelObj, {},
        LaunchConfigAccess.getProperties());
  } else {
    submit(std::move(Q), [&](handler &CGH) {
      nd_launch<KernelName>(CGH, Config, KernelObj,
                            std::forward<ReductionsT>(Reductions)...);
    });
  }
}

template <int Dimensions, typename... ArgsT>
void nd_launch(handler &CGH, nd_range<Dimensions> Range,
               const kernel &KernelObj, ArgsT &&...Args) {
  CGH.set_args<ArgsT...>(std::forward<ArgsT>(Args)...);
  CGH.parallel_for(Range, KernelObj);
}

template <int Dimensions, typename... ArgsT>
void nd_launch(queue Q, nd_range<Dimensions> Range, const kernel &KernelObj,
               ArgsT &&...Args) {
  submit(std::move(Q), [&](handler &CGH) {
    nd_launch(CGH, Range, KernelObj, std::forward<ArgsT>(Args)...);
  });
}

template <int Dimensions, typename Properties, typename... ArgsT>
void nd_launch(handler &CGH,
               launch_config<nd_range<Dimensions>, Properties> Config,
               const kernel &KernelObj, ArgsT &&...Args) {
  ext::oneapi::experimental::detail::LaunchConfigAccess<nd_range<Dimensions>,
                                                        Properties>
      ConfigAccess(Config);
  CGH.set_args<ArgsT...>(std::forward<ArgsT>(Args)...);
  sycl::detail::HandlerAccess::parallelForImpl(
      CGH, ConfigAccess.getRange(), ConfigAccess.getProperties(), KernelObj);
}

template <int Dimensions, typename Properties, typename... ArgsT>
void nd_launch(queue Q, launch_config<nd_range<Dimensions>, Properties> Config,
               const kernel &KernelObj, ArgsT &&...Args) {
  submit(std::move(Q), [&](handler &CGH) {
    nd_launch(CGH, Config, KernelObj, std::forward<ArgsT>(Args)...);
  });
}

// Free function kernel nd_launch enqueue functions
template <auto *Func, int Dimensions, typename... ArgsT>
void nd_launch(queue Q, nd_range<Dimensions> Range,
               [[maybe_unused]] kernel_function_s<Func> KernelFunc,
               ArgsT &&...Args) {
  detail::submit_kernel_direct_parallel_for(
      std::move(Q), Range,
      [Args...](sycl::nd_item<Dimensions>) { Func(Args...); });
}

template <auto *Func, int Dimensions, typename... ArgsT>
void nd_launch(handler &CGH, nd_range<Dimensions> Range,
               [[maybe_unused]] kernel_function_s<Func> KernelFunc,
               ArgsT &&...Args) {
  CGH.parallel_for(Range,
                   [Args...](sycl::nd_item<Dimensions>) { Func(Args...); });
}

template <auto *Func, int Dimensions, typename Properties, typename... ArgsT>
void nd_launch(queue Q, launch_config<nd_range<Dimensions>, Properties> Config,
               [[maybe_unused]] kernel_function_s<Func> KernelFunc,
               ArgsT &&...Args) {

  ext::oneapi::experimental::detail::LaunchConfigAccess<nd_range<Dimensions>,
                                                        Properties>
      ConfigAccess(Config);
  detail::submit_kernel_direct_parallel_for(
      std::move(Q), ConfigAccess.getRange(),
      [Args...](sycl::nd_item<Dimensions>) { Func(Args...); }, {},
      ConfigAccess.getProperties());
}

template <auto *Func, int Dimensions, typename Properties, typename... ArgsT>
void nd_launch(handler &CGH,
               launch_config<nd_range<Dimensions>, Properties> Config,
               [[maybe_unused]] kernel_function_s<Func> KernelFunc,
               ArgsT &&...Args) {
  ext::oneapi::experimental::detail::LaunchConfigAccess<nd_range<Dimensions>,
                                                        Properties>
      ConfigAccess(Config);
  CGH.parallel_for(ConfigAccess.getRange(), ConfigAccess.getProperties(),
                   [Args...](sycl::nd_item<Dimensions>) { Func(Args...); });
}

inline void memcpy(handler &CGH, void *Dest, const void *Src, size_t NumBytes) {
  CGH.memcpy(Dest, Src, NumBytes);
}

__SYCL_EXPORT void memcpy(queue Q, void *Dest, const void *Src, size_t NumBytes,
                          const sycl::detail::code_location &CodeLoc =
                              sycl::detail::code_location::current());

template <typename T>
void copy(handler &CGH, const T *Src, T *Dest, size_t Count) {
  CGH.copy<T>(Src, Dest, Count);
}

template <typename T>
void copy(queue Q, const T *Src, T *Dest, size_t Count,
          const sycl::detail::code_location &CodeLoc =
              sycl::detail::code_location::current()) {
  submit(
      std::move(Q), [&](handler &CGH) { copy<T>(CGH, Src, Dest, Count); },
      CodeLoc);
}

inline void memset(handler &CGH, void *Ptr, int Value, size_t NumBytes) {
  CGH.memset(Ptr, Value, NumBytes);
}

__SYCL_EXPORT void memset(queue Q, void *Ptr, int Value, size_t NumBytes,
                          const sycl::detail::code_location &CodeLoc =
                              sycl::detail::code_location::current());

template <typename T>
void fill(sycl::handler &CGH, T *Ptr, const T &Pattern, size_t Count) {
  CGH.fill(Ptr, Pattern, Count);
}

template <typename T>
void fill(sycl::queue Q, T *Ptr, const T &Pattern, size_t Count,
          const sycl::detail::code_location &CodeLoc =
              sycl::detail::code_location::current()) {
  submit(
      std::move(Q), [&](handler &CGH) { fill<T>(CGH, Ptr, Pattern, Count); },
      CodeLoc);
}

inline void prefetch(handler &CGH, void *Ptr, size_t NumBytes,
                     prefetch_type Type = prefetch_type::device) {
  CGH.prefetch(Ptr, NumBytes, Type);
}

inline void prefetch(queue Q, void *Ptr, size_t NumBytes,
                     prefetch_type Type = prefetch_type::device,
                     const sycl::detail::code_location &CodeLoc =
                         sycl::detail::code_location::current()) {
  submit(
      std::move(Q), [&](handler &CGH) { prefetch(CGH, Ptr, NumBytes, Type); },
      CodeLoc);
}

inline void mem_advise(handler &CGH, void *Ptr, size_t NumBytes, int Advice) {
  CGH.mem_advise(Ptr, NumBytes, Advice);
}

__SYCL_EXPORT void mem_advise(queue Q, void *Ptr, size_t NumBytes, int Advice,
                              const sycl::detail::code_location &CodeLoc =
                                  sycl::detail::code_location::current());

inline void barrier(handler &CGH) { CGH.ext_oneapi_barrier(); }

inline void barrier(queue Q, const sycl::detail::code_location &CodeLoc =
                                 sycl::detail::code_location::current()) {
  submit(std::move(Q), [&](handler &CGH) { barrier(CGH); }, CodeLoc);
}

inline void partial_barrier(handler &CGH, const std::vector<event> &Events) {
  CGH.ext_oneapi_barrier(Events);
}

inline void partial_barrier(queue Q, const std::vector<event> &Events,
                            const sycl::detail::code_location &CodeLoc =
                                sycl::detail::code_location::current()) {
  submit(
      std::move(Q), [&](handler &CGH) { partial_barrier(CGH, Events); },
      CodeLoc);
}

inline void execute_graph(handler &CGH,
                          command_graph<graph_state::executable> &G) {
  CGH.ext_oneapi_graph(G);
}

inline void execute_graph(queue Q, command_graph<graph_state::executable> &G,
                          const sycl::detail::code_location &CodeLoc =
                              sycl::detail::code_location::current()) {
  submit_graph_direct_without_event_impl(std::move(Q), G, /*DepEvents*/ {},
                                         CodeLoc);
}

} // namespace ext::oneapi::experimental
} // namespace _V1
} // namespace sycl
