///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Lewis Baker
// Licenced under MIT license. See LICENSE.txt for details.
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCORO_NET_IP_ADDRESS_HPP_INCLUDED
#define CPPCORO_NET_IP_ADDRESS_HPP_INCLUDED

#include <cppcoro/net/ipv4_address.hpp>
#include <cppcoro/net/ipv6_address.hpp>

#include <cassert>
#include <optional>
#include <string>

namespace cppcoro
{
	namespace net
	{
		class ip_address
		{
		public:

			// Constructs to IPv4 address 0.0.0.0
			ip_address() noexcept;

			ip_address(ipv4_address address) noexcept;
			ip_address(ipv6_address address) noexcept;

			bool is_ipv4() const noexcept { return m_family == family::ipv4; }
			bool is_ipv6() const noexcept { return m_family == family::ipv6; }

			const ipv4_address& to_ipv4() const;
			const ipv6_address& to_ipv6() const;

			const std::uint8_t* bytes() const noexcept;

			std::string to_string() const;

			static std::optional<ip_address> from_string(std::string_view string) noexcept;

			bool operator==(const ip_address& rhs) const noexcept;
			bool operator!=(const ip_address& rhs) const noexcept;

			//  ipv4_address sorts less than ipv6_address
			bool operator<(const ip_address& rhs) const noexcept;
			bool operator>(const ip_address& rhs) const noexcept;
			bool operator<=(const ip_address& rhs) const noexcept;
			bool operator>=(const ip_address& rhs) const noexcept;

		private:

			enum class family
			{
				ipv4,
				ipv6
			};

			family m_family;

			union
			{
				ipv4_address m_ipv4;
				ipv6_address m_ipv6;
			};

		};

		inline ip_address::ip_address() noexcept
			: m_family(family::ipv4)
			, m_ipv4()
		{}

		inline ip_address::ip_address(ipv4_address address) noexcept
			: m_family(family::ipv4)
			, m_ipv4(address)
		{}

		inline ip_address::ip_address(ipv6_address address) noexcept
			: m_family(family::ipv6)
			, m_ipv6(address)
		{
		}

		inline const ipv4_address& ip_address::to_ipv4() const
		{
			assert(is_ipv4());
			return m_ipv4;
		}

		inline const ipv6_address& ip_address::to_ipv6() const
		{
			assert(is_ipv6());
			return m_ipv6;
		}

		inline const std::uint8_t* ip_address::bytes() const noexcept
		{
			return is_ipv4() ? m_ipv4.bytes() : m_ipv6.bytes();
		}

		inline bool ip_address::operator==(const ip_address& rhs) const noexcept
		{
			if (is_ipv4())
			{
				return rhs.is_ipv4() && m_ipv4 == rhs.m_ipv4;
			}
			else
			{
				return rhs.is_ipv6() && m_ipv6 == rhs.m_ipv6;
			}
		}

		inline bool ip_address::operator!=(const ip_address& rhs) const noexcept
		{
			return !(*this == rhs);
		}

		inline bool ip_address::operator<(const ip_address& rhs) const noexcept
		{
			if (is_ipv4())
			{
				return !rhs.is_ipv4() || m_ipv4 < rhs.m_ipv4;
			}
			else
			{
				return rhs.is_ipv6() && m_ipv6 < rhs.m_ipv6;
			}
		}

		inline bool ip_address::operator>(const ip_address& rhs) const noexcept
		{
			return rhs < *this;
		}

		inline bool ip_address::operator<=(const ip_address& rhs) const noexcept
		{
			return !(rhs < *this);
		}

		inline bool ip_address::operator>=(const ip_address& rhs) const noexcept
		{
			return !(*this < rhs);
		}
	}
}

#endif
