/*
 *  Flo's Open libRary (floor)
 *  Copyright (C) 2004 - 2026 Florian Ziesche
 *  
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License only.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#pragma once

//! interface and support/query helpers for parallel group operations
//! NOTE: the main intent of this is for backends to be able to provide specialized/optimized work-group/sub-group operations
//!       that will be used instead of the generic work-group/sub-group algorithms in algorithm.hpp
//!       -> that means that these operations *may* be implemented by backends, not that they have to (rather use fl::algorithm)
namespace fl::algorithm::group {

//! group algorithm
enum class ALGORITHM {
	WORK_GROUP_REDUCE,
	WORK_GROUP_INCLUSIVE_SCAN,
	WORK_GROUP_EXCLUSIVE_SCAN,
	
	SUB_GROUP_REDUCE,
	SUB_GROUP_INCLUSIVE_SCAN,
	SUB_GROUP_EXCLUSIVE_SCAN,
};

//! group operation
enum class OP {
	NONE,
	ADD,
	MIN,
	MAX,
	AND,
	OR,
	XOR,
};

//! if the combination of algorithm, operation and data_type is supported, this inherits from std::true_type, otherwise std::false_type
template <ALGORITHM algo, OP op, typename data_type> struct supports : public std::false_type {};

//! if the combination of algorithm, operation and data_type is supported, this is true, otherwise false
template <ALGORITHM algo, OP op, typename data_type> constexpr bool supports_v = supports<algo, op, data_type>::value;

//! specifies the amount of required local memory that is needed for the specified combination of algorithm, operation and data_type
//! NOTE: sub-group level algorithms may not use any local memory
template <ALGORITHM algo, OP op, typename data_type>
requires (algo != ALGORITHM::SUB_GROUP_REDUCE &&
		  algo != ALGORITHM::SUB_GROUP_INCLUSIVE_SCAN &&
		  algo != ALGORITHM::SUB_GROUP_EXCLUSIVE_SCAN)
struct required_local_memory_elements {
	static constexpr const size_t count { 0 };
};

//! stub for reduce with a specific operation and data_type
template <OP op, typename data_type, typename lmem_type>
[[maybe_unused]] static auto work_group_reduce(const data_type input_value, lmem_type& lmem);

//! stub for inclusive scan with a specific operation and data_type
template <OP op, typename data_type, typename lmem_type>
[[maybe_unused]] static auto work_group_inclusive_scan(const data_type input_value, lmem_type& lmem);

//! stub for exclusive scan with a specific operation and data_type
template <OP op, typename data_type, typename lmem_type>
[[maybe_unused]] static auto work_group_exclusive_scan(const data_type input_value, lmem_type& lmem);

//! stub for reduce with a specific operation and data_type
template <OP op, typename data_type>
[[maybe_unused]] static auto sub_group_reduce(const data_type input_value);

//! stub for inclusive scan with a specific operation and data_type
template <OP op, typename data_type>
[[maybe_unused]] static auto sub_group_inclusive_scan(const data_type input_value);

//! stub for exclusive scan with a specific operation and data_type
template <OP op, typename data_type>
[[maybe_unused]] static auto sub_group_exclusive_scan(const data_type input_value);


//! helper functions that work on both scalar and vector types
template <typename data_type> struct min_op;
template <typename data_type> struct max_op;
template <typename data_type> struct and_op;
template <typename data_type> struct or_op;
template <typename data_type> struct xor_op;

//! scalar min(lhs, rhs) function op
template <typename data_type> requires (ext::is_arithmetic_v<data_type>)
struct min_op<data_type> {
	inline constexpr data_type operator()(const data_type lhs, const data_type rhs) const {
		return fl::floor_rt_min(lhs, rhs);
	}
};
//! vector min(lhs, rhs) function op
template <typename data_type> requires (is_floor_vector_v<data_type>)
struct min_op<data_type> {
	inline constexpr data_type operator()(const data_type lhs, const data_type rhs) const {
		return lhs.minned(rhs);
	}
};
//! scalar max(lhs, rhs) function op
template <typename data_type> requires (ext::is_arithmetic_v<data_type>)
struct max_op<data_type> {
	inline constexpr data_type operator()(const data_type lhs, const data_type rhs) const {
		return fl::floor_rt_max(lhs, rhs);
	}
};
//! vector max(lhs, rhs) function op
template <typename data_type> requires (is_floor_vector_v<data_type>)
struct max_op<data_type> {
	inline constexpr data_type operator()(const data_type lhs, const data_type rhs) const {
		return lhs.maxed(rhs);
	}
};
//! scalar/vector and(lhs, rhs) function op
template <typename data_type> requires (ext::is_arithmetic_v<data_type> || is_floor_vector_v<data_type>)
struct and_op<data_type> {
	inline constexpr data_type operator()(const data_type lhs, const data_type rhs) const {
		return lhs & rhs;
	}
};
//! scalar/vector or(lhs, rhs) function op
template <typename data_type> requires (ext::is_arithmetic_v<data_type> || is_floor_vector_v<data_type>)
struct or_op<data_type> {
	inline constexpr data_type operator()(const data_type lhs, const data_type rhs) const {
		return lhs | rhs;
	}
};
//! scalar/vector xor(lhs, rhs) function op
template <typename data_type> requires (ext::is_arithmetic_v<data_type> || is_floor_vector_v<data_type>)
struct xor_op<data_type> {
	inline constexpr data_type operator()(const data_type lhs, const data_type rhs) const {
		return lhs ^ rhs;
	}
};

//! filters integer types that are supported in sub-group operations
template <typename data_type> struct is_sub_group_integer_type : public std::false_type {};
template <> struct is_sub_group_integer_type<int16_t> : public std::true_type {};
template <> struct is_sub_group_integer_type<uint16_t> : public std::true_type {};
template <> struct is_sub_group_integer_type<short2> : public std::true_type {};
template <> struct is_sub_group_integer_type<ushort2> : public std::true_type {};
template <> struct is_sub_group_integer_type<short3> : public std::true_type {};
template <> struct is_sub_group_integer_type<ushort3> : public std::true_type {};
template <> struct is_sub_group_integer_type<short4> : public std::true_type {};
template <> struct is_sub_group_integer_type<ushort4> : public std::true_type {};
template <> struct is_sub_group_integer_type<int32_t> : public std::true_type {};
template <> struct is_sub_group_integer_type<uint32_t> : public std::true_type {};
template <> struct is_sub_group_integer_type<int2> : public std::true_type {};
template <> struct is_sub_group_integer_type<uint2> : public std::true_type {};
template <> struct is_sub_group_integer_type<int3> : public std::true_type {};
template <> struct is_sub_group_integer_type<uint3> : public std::true_type {};
template <> struct is_sub_group_integer_type<int4> : public std::true_type {};
template <> struct is_sub_group_integer_type<uint4> : public std::true_type {};
template <typename T> constexpr bool is_sub_group_integer_type_v = is_sub_group_integer_type<T>::value;

} // namespace fl::algorithm::group
