// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once

namespace rpl {
namespace details {

struct base_mapper {
};

template <typename Type>
constexpr bool is_mapper_v = std::is_base_of_v<
	base_mapper,
	std::decay_t<Type>>;

template <std::size_t Index>
struct argument_mapper : base_mapper {
	template <
		typename Arg,
		typename ...Args,
		typename = std::enable_if_t<(sizeof...(Args) >= Index)>>
	static constexpr decltype(auto) call(Arg &&arg, Args &&...args) {
		return argument_mapper<Index - 1>::call(
			std::forward<Args>(args)...);
	}

	template <
		typename ...Args,
		typename = std::enable_if_t<(sizeof...(Args) > Index)>>
	constexpr auto operator()(Args &&...args) const {
		return call(std::forward<Args>(args)...);
	}

};

template <>
struct argument_mapper<0> : base_mapper {
	template <
		typename Arg,
		typename ...Args>
	static constexpr decltype(auto) call(Arg &&arg, Args &&...args) {
		return std::forward<Arg>(arg);
	}

	template <
		typename Arg,
		typename ...Args>
	constexpr auto operator()(Arg &&arg, Args &&...args) const {
		return std::forward<Arg>(arg);
	}

};

template <typename Type>
class value_mapper : public base_mapper {
public:
	template <typename OtherType>
	constexpr value_mapper(OtherType &&value)
	: _value(std::forward<OtherType>(value)) {
	}

	template <typename ...Args>
	constexpr auto operator()(Args &&...args) const {
		return _value;
	}

private:
	Type _value;

};

template <typename Type>
struct wrap_mapper {
	using type = std::conditional_t<
		is_mapper_v<Type>,
		Type,
		value_mapper<Type>>;
};

template <typename Type>
using wrap_mapper_t = typename wrap_mapper<Type>::type;

template <typename Type, typename Operator>
class unary_operator_mapper : public base_mapper {
	using TypeWrapper = wrap_mapper_t<std::decay_t<Type>>;

public:
	template <typename OtherType>
	constexpr unary_operator_mapper(OtherType &&value)
		: _value(std::forward<OtherType>(value)) {
	}

	template <
		typename ...Args,
		typename Result = decltype((Operator{})(
			std::declval<TypeWrapper>()(std::declval<Args>()...)))>
	constexpr std::decay_t<Result> operator()(Args &&...args) const {
		return (Operator{})(
			_value(std::forward<Args>(args)...));
	}

private:
	TypeWrapper _value;

};

template <typename Left, typename Right, typename Operator>
class binary_operator_mapper : public base_mapper {
	using LeftWrapper = wrap_mapper_t<std::decay_t<Left>>;
	using RightWrapper = wrap_mapper_t<std::decay_t<Right>>;

public:
	template <typename OtherLeft, typename OtherRight>
	constexpr binary_operator_mapper(OtherLeft &&left, OtherRight &&right)
		: _left(std::forward<OtherLeft>(left))
		, _right(std::forward<OtherRight>(right)) {
	}

	template <
		typename ...Args,
		typename Result = decltype((Operator{})(
			std::declval<LeftWrapper>()(std::declval<Args>()...),
			std::declval<RightWrapper>()(std::declval<Args>()...)))>
	constexpr std::decay_t<Result> operator()(Args &&...args) const {
		return (Operator{})(
			_left(std::forward<Args>(args)...),
			_right(std::forward<Args>(args)...));
	}

private:
	LeftWrapper _left;
	RightWrapper _right;

};

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator+(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::plus<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator-(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::minus<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator*(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::multiplies<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator/(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::divides<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator%(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::modulus<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Type,
	typename = std::enable_if_t<
		is_mapper_v<Type>
	>>
inline auto operator-(Type &&value) {
	return unary_operator_mapper<
		Type,
		std::negate<>>(
			std::forward<Type>(value));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator<(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::less<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator<=(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::less_equal<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator>(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::greater<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator>=(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::greater_equal<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator==(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::equal_to<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator!=(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::not_equal_to<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator&&(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::logical_and<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator||(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::logical_or<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Type,
	typename = std::enable_if_t<
		is_mapper_v<Type>
	>>
inline auto operator!(Type &&value) {
	return unary_operator_mapper<
		Type,
		std::logical_not<>>(
			std::forward<Type>(value));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator&(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::bit_and<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator|(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::bit_or<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Left,
	typename Right,
	typename = std::enable_if_t<
		is_mapper_v<Left> || is_mapper_v<Right>
	>>
inline auto operator^(Left &&left, Right &&right) {
	return binary_operator_mapper<
		Left,
		Right,
		std::bit_xor<>>(
			std::forward<Left>(left),
			std::forward<Right>(right));
}

template <
	typename Type,
	typename = std::enable_if_t<
		is_mapper_v<Type>
	>>
inline auto operator~(Type &&value) {
	return unary_operator_mapper<
		Type,
		std::bit_not<>>(
			std::forward<Type>(value));
}

template <typename ...Mappers>
class tuple_mapper {
	template <typename ...Args>
	using tuple_result = std::tuple<decltype(
		std::declval<wrap_mapper_t<std::decay_t<Mappers>>>()(
			std::declval<Args>()...))...>;
public:
	template <typename ...OtherMappers>
	tuple_mapper(OtherMappers &&...mappers) : _mappers(
		std::forward<OtherMappers>(mappers)...) {
	}

	template <typename ...Args>
	constexpr tuple_result<Args...> operator()(
			Args &&...args) const {
		constexpr auto kArity = sizeof...(Mappers);
		return call_helper(
			std::make_index_sequence<kArity>(),
			std::forward<Args>(args)...);
	}

private:
	template <typename ...Args, std::size_t ...I>
	inline tuple_result<Args...> call_helper(
			std::index_sequence<I...>,
			Args &&...args) const {
		return std::make_tuple(
			std::get<I>(_mappers)(std::forward<Args>(args)...)...);
	}

	std::tuple<wrap_mapper_t<std::decay_t<Mappers>>...> _mappers;

};

template <typename ...Args>
tuple_mapper<Args...> tuple(Args &&...args) {
	return tuple_mapper<Args...>(std::forward<Args>(args)...);
}

} // namespace details

namespace mappers {

constexpr const details::argument_mapper<0> _1;
constexpr const details::argument_mapper<1> _2;
constexpr const details::argument_mapper<2> _3;
constexpr const details::argument_mapper<3> _4;
constexpr const details::argument_mapper<4> _5;
constexpr const details::argument_mapper<5> _6;
constexpr const details::argument_mapper<6> _7;
constexpr const details::argument_mapper<7> _8;
constexpr const details::argument_mapper<8> _9;
constexpr const details::argument_mapper<9> _10;

constexpr const auto _1_of_two = ((void)_2, _1);

} // namespace mappers
} // namespace rpl
