// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project

#ifndef KOKKOS_IMPL_PUBLIC_INCLUDE
#define KOKKOS_IMPL_PUBLIC_INCLUDE
#endif

#include <iostream>

#include <OpenMP/Kokkos_OpenMP.hpp>
#include <OpenMP/Kokkos_OpenMP_Instance.hpp>

#include <impl/Kokkos_CheckUsage.hpp>
#include <impl/Kokkos_ExecSpaceManager.hpp>

namespace Kokkos {

OpenMP::~OpenMP() {
  Impl::check_execution_space_destructor_precondition(name());
}

OpenMP::OpenMP()
    : m_space_instance(
          (Impl::check_execution_space_constructor_precondition(name()),
           Impl::HostSharedPtr(&Impl::OpenMPInternal::singleton(),
                               [](Impl::OpenMPInternal *) {}))) {}

OpenMP::OpenMP(int pool_size)
    : m_space_instance(
          (Impl::check_execution_space_constructor_precondition(name()),
           Impl::HostSharedPtr(new Impl::OpenMPInternal(pool_size),
                               [](Impl::OpenMPInternal *ptr) {
                                 ptr->finalize();
                                 delete ptr;
                               }))) {}

int OpenMP::impl_get_current_max_threads() noexcept {
  return Impl::OpenMPInternal::get_current_max_threads();
}

void OpenMP::impl_initialize(InitializationSettings const &settings) {
  Impl::OpenMPInternal::singleton().initialize(
      settings.has_num_threads() ? settings.get_num_threads() : -1);
}

void OpenMP::impl_finalize() { Impl::OpenMPInternal::singleton().finalize(); }

void OpenMP::print_configuration(std::ostream &os, bool /*verbose*/) const {
  os << "Host Parallel Execution Space:\n";
  os << "  KOKKOS_ENABLE_OPENMP: yes\n";

  os << "\nOpenMP Runtime Configuration:\n";

  m_space_instance->print_configuration(os);
}

#ifdef KOKKOS_ENABLE_DEPRECATED_CODE_4
int OpenMP::concurrency(OpenMP const &instance) {
  return instance.impl_thread_pool_size();
}
#else
int OpenMP::concurrency() const { return impl_thread_pool_size(); }
#endif

void OpenMP::impl_static_fence(std::string const &name) {
  Kokkos::Tools::Experimental::Impl::profile_fence_event<Kokkos::OpenMP>(
      name,
      Kokkos::Tools::Experimental::SpecialSynchronizationCases::
          GlobalDeviceSynchronization,
      []() {
        std::lock_guard<std::mutex> lock_all_instances(
            Impl::OpenMPInternal::all_instances_mutex);
        for (auto *instance_ptr : Impl::OpenMPInternal::all_instances) {
          std::lock_guard<std::mutex> lock_instance(
              instance_ptr->m_instance_mutex);
        }
      });
}

void OpenMP::fence(const std::string &name) const {
  Kokkos::Tools::Experimental::Impl::profile_fence_event<Kokkos::OpenMP>(
      name, Kokkos::Tools::Experimental::Impl::DirectFenceIDHandle{1},
      [this]() {
        auto *internal_instance = this->impl_internal_space_instance();
        std::lock_guard<std::mutex> lock(internal_instance->m_instance_mutex);
      });
}

#ifdef KOKKOS_ENABLE_DEPRECATED_CODE_4
KOKKOS_DEPRECATED bool OpenMP::in_parallel(OpenMP const &exec_space) noexcept {
  return exec_space.impl_internal_space_instance()->m_level < omp_get_level();
}
#endif

int OpenMP::impl_thread_pool_size() const noexcept {
  return (impl_internal_space_instance()->get_level() < omp_get_level())
             ? omp_get_num_threads()
             : impl_internal_space_instance()->m_pool_size;
}

int OpenMP::impl_max_hardware_threads() noexcept {
  return Impl::OpenMPInternal::max_hardware_threads();
}

namespace Impl {

int g_openmp_space_factory_initialized =
    initialize_space_factory<OpenMP>("050_OpenMP");

}  // namespace Impl

}  // namespace Kokkos
