#pragma once

#include <psemek/util/span.hpp>
#include <psemek/util/range.hpp>

#include <cstdint>
#include <cstddef>
#include <memory>
#include <concepts>

namespace psemek::util
{

	struct big_int
	{
		using digit = std::uint32_t;

		big_int() = default;

		template <std::integral T>
		big_int(T value);

		big_int(big_int const &) = delete;

		big_int(big_int && other);

		static big_int from_digits(std::unique_ptr<digit[]> digits, std::uint32_t size, bool negative = false);

		template <typename Iterator>
		static big_int from_digits(Iterator begin, Iterator end, bool negative = false);

		template <typename Container>
		static big_int from_digits(Container && container, bool negative = false);

		big_int & operator = (big_int const &) = delete;

		big_int & operator = (big_int && other);

		util::span<digit> digits();
		util::span<digit const> digits() const;

		bool negative() const;

	private:
		static constexpr std::uint32_t sign_bit = std::uint32_t(1) << 31;
		static constexpr std::uint32_t digit_size = sizeof(digit);
		static constexpr std::uint32_t digit_bits = digit_size * 8;
		static constexpr std::uint32_t digit_mask = digit(-1);

		std::unique_ptr<digit[]> digits_ = nullptr;
		// Highest bit of size stores the overall big int sign
		std::uint32_t size_ = 0;
		std::uint32_t capacity_ = 0;

		void set_negative(bool negative)
		{
			size_ = size() | (negative ? sign_bit : 0);
		}

		std::uint32_t size() const
		{
			return size_ & (~sign_bit);
		}

		void set_size(std::uint32_t new_size)
		{
			size_ = new_size | (size_ & sign_bit);
		}

		void canonicalize();

		void ensure_capacity(std::uint32_t new_capacity);

		template <std::integral T>
		static T shift_by_digit(T value);

		template <std::integral T>
		static std::uint32_t size_for(T value);

		void add_positive(span<digit const> digits);
	};

	template <std::integral T>
	big_int::big_int(T value)
	{
		auto size = size_for(value);
		ensure_capacity(size);
		set_size(size);
		set_negative(value < 0);
		for (digit & d : digits())
		{
			d = value & digit_mask;
			value = shift_by_digit(value);
		}

		if (negative())
		{
			for (digit & d : digits())
				d = ~d;
			digit const one = 1;
			add_positive(make_singleton_span(one));
		}
	}

	inline big_int::big_int(big_int && other)
		: digits_(std::move(other.digits_))
		, size_(other.size_)
		, capacity_(other.capacity_)
	{
		other.size_ = 0;
		other.capacity_ = 0;
	}

	inline big_int big_int::from_digits(std::unique_ptr<digit[]> digits, std::uint32_t size, bool negative)
	{
		big_int result;
		result.digits_ = std::move(digits);
		result.size_ = size;
		result.capacity_ = size;
		result.set_negative(negative);
		result.canonicalize();
		return result;
	}

	template <typename Iterator>
	big_int big_int::from_digits(Iterator begin, Iterator end, bool negative)
	{
		auto size = std::distance(begin, end);
		auto digits = std::make_unique<digit[]>(size);
		std::copy(begin, end, digits.get());
		return from_digits(std::move(digits), size, negative);
	}

	template <typename Container>
	big_int big_int::from_digits(Container && container, bool negative)
	{
		return from_digits(util::xbegin(container), util::xend(container), negative);
	}

	inline util::span<big_int::digit> big_int::digits()
	{
		return {digits_.get(), size()};
	}

	inline util::span<big_int::digit const> big_int::digits() const
	{
		return {digits_.get(), size()};
	}

	inline bool big_int::negative() const
	{
		return (size_ & sign_bit) == sign_bit;
	}

	template <std::integral T>
	T big_int::shift_by_digit(T value)
	{
		if constexpr (sizeof(value) <= digit_size)
		{
			if (value < 0)
				return -1;
			else
				return 0;
		}
		else
		{
			return value >> digit_bits;
		}
	}

	template <std::integral T>
	std::uint32_t big_int::size_for(T value)
	{
		std::uint32_t result_bits = 0;

		if (value >= 0)
		{
			while (value > 0)
			{
				value = shift_by_digit(value);
				result_bits += digit_bits;
			}
		}
		else
		{
			result_bits = 1;
			while ((value >> (digit_bits - 1)) != T(-1))
			{
				value = shift_by_digit(value);
				result_bits += digit_bits;
			}
		}

		return (result_bits + (digit_bits - 1)) / digit_bits;
	}

}
