/************************************************************************************
 *
 * D++, A Lightweight C++ library for Discord
 *
 * Copyright 2022 Craig Edwards and D++ contributors
 * (https://github.com/brainboxdotcc/DPP/graphs/contributors)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ************************************************************************************/

#pragma once

#include <dpp/export.h>

#ifndef DPP_NO_CORO

#if (defined(_LIBCPP_VERSION) and !defined(__cpp_impl_coroutine)) // if libc++ experimental implementation (LLVM < 14)
#  define STDCORO_EXPERIMENTAL_HEADER
#  define STDCORO_EXPERIMENTAL_NAMESPACE
#endif

#ifdef STDCORO_GLIBCXX_COMPAT
#  define __cpp_impl_coroutine 1
namespace std {
	namespace experimental {
		using namespace std;
	}
}
#endif

#ifdef STDCORO_EXPERIMENTAL_HEADER
#  include <experimental/coroutine>
#else
#  include <coroutine>
#endif

namespace dpp {

/**
 * @brief Implementation details for internal use only.
 *
 * @attention This is only meant to be used by D++ internally. Support will not be given regarding the facilities in this namespace.
 */
namespace detail {
#ifdef _DOXYGEN_
/**
 * @brief Alias for either std or std::experimental depending on compiler and library. Used by coroutine implementation.
 *
 * @todo Remove and use std when all supported libraries have coroutines in it
 */
namespace std_coroutine {}
#else
#  ifdef STDCORO_EXPERIMENTAL_NAMESPACE
namespace std_coroutine = std::experimental;
#  else
namespace std_coroutine = std;
#  endif
#endif

#ifndef _DOXYGEN_
/**
 * @brief Concept to check if a type has a useable `operator co_await()` member
 */
template <typename T>
concept has_co_await_member = requires (T expr) { expr.operator co_await(); };

/**
 * @brief Concept to check if a type has a useable overload of the free function `operator co_await(expr)`
 */
template <typename T>
concept has_free_co_await = requires (T expr) { operator co_await(expr); };

/**
 * @brief Concept to check if a type has useable `await_ready()`, `await_suspend()` and `await_resume()` member functions.
 */
template <typename T>
concept has_await_members = requires (T expr) { expr.await_ready(); expr.await_suspend(); expr.await_resume(); };

/**
 * @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr`
 */
template <typename T>
requires (has_co_await_member<T>)
decltype(auto) co_await_resolve(T&& expr) noexcept(noexcept(expr.operator co_await())) {
	decltype(auto) awaiter = expr.operator co_await();
	return awaiter;
}

/**
 * @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr`
 */
template <typename T>
requires (!has_co_await_member<T> && has_free_co_await<T>)
decltype(auto) co_await_resolve(T&& expr) noexcept(noexcept(operator co_await(expr))) {
	decltype(auto) awaiter = operator co_await(static_cast<T&&>(expr));
	return awaiter;
}

/**
 * @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr`
 */
template <typename T>
requires (!has_co_await_member<T> && !has_free_co_await<T>)
decltype(auto) co_await_resolve(T&& expr) noexcept {
	return static_cast<T&&>(expr);
}

#else

/**
 * @brief Concept to check if a type has a useable `operator co_await()` member
 *
 * @note This is actually a C++20 concept but Doxygen doesn't do well with them
 */
template <typename T>
inline constexpr bool has_co_await_member;

/**
 * @brief Concept to check if a type has a useable overload of the free function `operator co_await(expr)`
 *
 * @note This is actually a C++20 concept but Doxygen doesn't do well with them
 */
template <typename T>
inline constexpr bool has_free_co_await;

/**
 * @brief Concept to check if a type has useable `await_ready()`, `await_suspend()` and `await_resume()` member functions.
 *
 * @note This is actually a C++20 concept but Doxygen doesn't do well with them
 */
template <typename T>
inline constexpr bool has_await_members;

/**
 * @brief Concept to check if a type can be used with co_await
 * 
 * @note This is actually a C++20 concept but Doxygen doesn't do well with them
 */
template <typename T>
inline constexpr bool awaitable_type;

/**
 * @brief Mimics the compiler's behavior of using co_await. That is, it returns whichever works first, in order : `expr.operator co_await();` > `operator co_await(expr)` > `expr`
 *
 * This function is conditionally noexcept, if the returned expression also is.
 */
decltype(auto) co_await_resolve(auto&& expr) {}

#endif

/**
 * @brief Convenience alias for the result of a certain awaitable's await_resume.
 */
template <typename T>
using awaitable_result = decltype(co_await_resolve(std::declval<T>()).await_resume());

} // namespace detail

/**
 * @brief Concept to check if a type can be used with co_await
 */
template <typename T>
concept awaitable_type = requires (T expr) { detail::co_await_resolve(expr).await_ready(); };

struct confirmation_callback_t;

template <typename R = confirmation_callback_t>
class async;

template <typename R = void>
#ifndef _DOXYGEN_
requires (!std::is_reference_v<R>)
#endif
class task;

template <typename R = void>
class coroutine;

struct job;

#ifdef DPP_CORO_TEST
/**
 * @brief Allocation count of a certain type, for testing purposes.
 *
 * @todo Remove when coro is stable
 */
template <typename T>
inline int coro_alloc_count = 0;
#endif

} // namespace dpp

#endif /* DPP_NO_CORO */

