#ifndef IP_ADDRESS_H_
#define IP_ADDRESS_H_

#include <arpa/inet.h>

#include <array>
#include <string>
#include <cstring>

#include "config.h"
#include "tools.h"

struct IpAddress
{
	// IPv4-mapped IPv6 prefix: first 12 bytes (::ffff:)
	constexpr static uint8_t Ipv4MappedPrefix[12] = {
	    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	    0x00, 0x00, 0x00, 0x00, 0xff, 0xff
	};

	// Default construction creates IPv6 i.e. in6addr_any. [::]
	IpAddress() = default;

	IpAddress(uint32_t ipv4Address)
	{
		std::copy(Ipv4MappedPrefix, Ipv4MappedPrefix + sizeof(Ipv4MappedPrefix), address.begin());
		set4(address.data(), sizeof(Ipv4MappedPrefix), ipv4Address);
	}

	IpAddress(const std::string& ip)
		: IpAddress(ip.c_str())
	{
	}

	IpAddress(const char* ip)
	{
		//Attempt to parse it as ipv4 address
		if (inet_pton(AF_INET, ip, address.data() + sizeof(Ipv4MappedPrefix)) == 1)
			// Prepend prefix
			std::copy(Ipv4MappedPrefix, Ipv4MappedPrefix + sizeof(Ipv4MappedPrefix), address.begin());
		else
			// Try as ipv6
			if (inet_pton(AF_INET6, ip, address.data()) != 1)
				//Couldn't parse it
				throw std::invalid_argument("Invalid IP address");
	}

	IpAddress(const std::array<uint8_t, 16>& address) : address(address)
	{
	}

	IpAddress(const in6_addr& sin6_addr)
	{
		std::copy(sin6_addr.s6_addr, sin6_addr.s6_addr + 16, address.begin());
	}

	bool operator==(const IpAddress& other) const
	{
		return address == other.address;
	}

	bool operator!=(const IpAddress& other) const
	{
		return !(*this == other);
	}

	bool IsAny() const
	{
		return std::memcmp(address.data(), &in6addr_any, sizeof(in6addr_any)) == 0;
	}

	bool IsV4() const
	{
		// Compare the first 12 bytes with the prefix
		return std::memcmp(address.data(), Ipv4MappedPrefix, sizeof(Ipv4MappedPrefix)) == 0;
	}

	const uint8_t* GetIpV4Address() const
	{
		return address.data() + sizeof(Ipv4MappedPrefix);
	}

	const uint8_t* GetIpV6Address() const
	{
		return address.data();
	}

	std::string ToString() const
	{
		char str[INET6_ADDRSTRLEN];
		if (!IsV4())
		{
			if (inet_ntop(AF_INET6, GetIpV6Address(), str, sizeof(str)) == nullptr)
				throw std::runtime_error("Failed to convert IPv6 address to string");
		}
		else
		{
			if (inet_ntop(AF_INET, GetIpV4Address(), str, sizeof(str)) == nullptr)
				throw std::runtime_error("Failed to convert IPv4 address to string");
		}
		return std::string(str);
	}

	void CopyTo(in6_addr& sin6_addr) const
	{
		memcpy(&sin6_addr.s6_addr, address.data(), address.size());
	}

	std::array<uint8_t, 16> address = {};
};

#endif //IP_ADDRESS_H_