#pragma once

#include "yacppdic/detail/internal/misc_concepts.hpp"
#include "yacppdic/detail/internal/tuple_indices.hpp"

#include <concepts>
#include <tuple>
#include <type_traits>
#include <utility>

namespace yacppdic::internal
{

// Tag used to default initialize one or both of the pair's elements.
class DefaultInitTag
{
};

class ValueInitTag
{
};

template <typename Value>
concept ValueCanBeEmptyBase = std::is_empty_v<Value> && !std::is_final_v<Value>;

template <typename Value, int Idx>
class CompressedPairElement
{
public:
	using ValueReference = Value &;
	using ConstValueReference = const Value &;

	constexpr explicit CompressedPairElement(DefaultInitTag /*default_init_tag*/
	) noexcept {};

	constexpr explicit CompressedPairElement(ValueInitTag /*value_init_tag*/) noexcept
		: _value()
	{
	}

	template <typename ElementValue>
	requires std::same_as<CompressedPairElement, std::decay_t<ElementValue>>
	// NOLINTNEXTLINE(bugprone-forwarding-reference-overload)
	constexpr explicit CompressedPairElement(ElementValue &&value) noexcept
		: _value(std::forward<ElementValue>(value))
	{
	}

	template <typename... Args, std::size_t... Indices>
	constexpr CompressedPairElement(
		std::piecewise_construct_t /*tag_type*/,
		std::tuple<Args...> args,
		TupleIndices<Indices...> /*tuple_indices*/
	) noexcept
		: _value(std::forward<Args>(std::get<Indices>(args))...)
	{
	}

	auto get() noexcept -> ValueReference
	{
		return _value;
	}

	[[nodiscard]] auto get() const noexcept -> ConstValueReference
	{
		return _value;
	}

private:
	Value _value;
};

template <typename Value, int Idx>
requires ValueCanBeEmptyBase<Value>
class CompressedPairElement<Value, Idx> : private Value
{
public:
	using ValueReference = Value &;
	using ConstValueReference = const Value &;

	constexpr CompressedPairElement() noexcept = default;

	constexpr explicit CompressedPairElement(DefaultInitTag /*unused*/) noexcept {}

	constexpr explicit CompressedPairElement(ValueInitTag /*unused*/) noexcept : Value()
	{
	}

	template <typename ElementValue>
	requires std::same_as<CompressedPairElement, typename std::decay<ElementValue>::type>
	// NOLINTNEXTLINE(bugprone-forwarding-reference-overload)
	constexpr explicit CompressedPairElement(ElementValue &&value) noexcept
		: Value(std::forward<ElementValue>(value))
	{
	}

	template <typename... Args, std::size_t... Indices>
	constexpr CompressedPairElement(
		std::piecewise_construct_t /*unused*/,
		std::tuple<Args...> args,
		TupleIndices<Indices...> /*unused*/
	) noexcept
		: Value(std::forward<Args>(std::get<Indices>(args))...)
	{
	}

	auto get() noexcept -> ValueReference
	{
		return *this;
	}

	[[nodiscard]] auto get() const noexcept -> ConstValueReference
	{
		return *this;
	}
};

template <typename ValueOne, typename ValueTwo>
requires NotSameAs<ValueOne, ValueTwo>
class CompressedPair : private CompressedPairElement<ValueOne, 0>,
					   private CompressedPairElement<ValueTwo, 1>
{
public:
	using BaseOne = CompressedPairElement<ValueOne, 0>;
	using BaseTwo = CompressedPairElement<ValueTwo, 1>;

	template <typename>
	requires std::default_initializable<ValueOne> && std::default_initializable<ValueTwo>
	constexpr CompressedPair() noexcept;

	template <typename FirstValue, typename SecondValue>
	constexpr CompressedPair(
		FirstValue &&first_value,
		SecondValue &&second_value
	) noexcept;

	template <typename... ArgsOne, typename... ArgsTwo>
	constexpr CompressedPair(
		std::piecewise_construct_t piecewise_construct,
		std::tuple<ArgsOne...> first_args,
		std::tuple<ArgsTwo...> second_args
	) noexcept;

	auto first() noexcept -> typename BaseOne::ValueReference;

	[[nodiscard]] auto first() const noexcept -> typename BaseOne::ConstValueReference;

	auto second() noexcept -> typename BaseTwo::ValueReference;

	[[nodiscard]] auto second() const noexcept -> typename BaseTwo::ConstValueReference;

	constexpr static auto get_first_base(CompressedPair *pair) noexcept -> BaseOne *;

	constexpr static auto get_second_base(CompressedPair *pair) noexcept -> BaseTwo *;
};

} // namespace yacppdic::internal

#include "compressed_pair-impl.hpp"