#include "td/network/IPAddress.h"

#include <regex>
#include <stdexcept>
#include <sstream>

namespace {

const std::regex IPRegex(R":(^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$):");
const std::wregex IPRegexW(LR":(^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$):");

} // ns

namespace td {
namespace network {

/* Create an invalid address */
IPAddress::IPAddress() noexcept
	: m_Address(0), m_Valid(false) {

}

/* Initialize by string IP */
IPAddress::IPAddress(const std::string& ip)
	: m_Address(0), m_Valid(false) {

	std::sregex_iterator begin(ip.begin(), ip.end(), IPRegex);
	std::sregex_iterator end;

	if (begin == end) return; // m_Valid = false

	std::smatch match = *begin;

	std::uint8_t octet1 = std::stoul(std::string(match[1]));
	std::uint8_t octet2 = std::stoul(std::string(match[2]));
	std::uint8_t octet3 = std::stoul(std::string(match[3]));
	std::uint8_t octet4 = std::stoul(std::string(match[4]));

	m_Address = static_cast<std::uint32_t>((octet1 << 24) | (octet2 << 16) | (octet3 << 8) | octet4);
	m_Valid = true;
}

IPAddress::IPAddress(const std::wstring& ip)
	: m_Address(0), m_Valid(false) {

	std::wsregex_iterator begin(ip.begin(), ip.end(), IPRegexW);
	std::wsregex_iterator end;

	if (begin == end) return; // m_Valid = false

	std::wsmatch match = *begin;

	std::uint8_t octet1 = std::stoul(match[1]);
	std::uint8_t octet2 = std::stoul(match[2]);
	std::uint8_t octet3 = std::stoul(match[3]);
	std::uint8_t octet4 = std::stoul(match[4]);

	m_Address = static_cast<std::uint32_t>((octet1 << 24) | (octet2 << 16) | (octet3 << 8) | octet4);
	m_Valid = true;
}

/* Initialize by octets */
IPAddress::IPAddress(uint8_t octet1, uint8_t octet2, uint8_t octet3, uint8_t octet4) noexcept
	: m_Valid(true) {
	m_Address = static_cast<std::uint32_t>((octet1 << 24) | (octet2 << 16) | (octet3 << 8) | octet4);
}

/* Get the specific octet */
uint8_t IPAddress::GetOctet(uint8_t num) const {
	if (num == 0 || num > 4) throw std::invalid_argument("Invalid argument in IPAddress:GetOctet.");

	return (m_Address >> (8 * (4 - num))) & 0xFF;
}

/* Set the specific octet. 1-4 */
void IPAddress::SetOctet(uint8_t num, uint8_t value) {
	if (num == 0 || num > 4) throw std::invalid_argument("Invalid argument in IPAddress:GetOctet.");
	uint8_t octets[4];

	for (int i = 0; i < 4; ++i)
		octets[i] = (m_Address >> ((3 - i) * 8)) & 0xFF;

	octets[num - 1] = value;

	m_Address = static_cast<std::uint32_t>((octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]);
}

IPAddress IPAddress::LocalAddress() {
	return IPAddress(127, 0, 0, 1);
}

std::string IPAddress::ToString() const {
	std::stringstream ss;

	for (int i = 0; i < 4; ++i) {
		if (i != 0) ss << ".";
		ss << static_cast<unsigned int>(GetOctet(i + 1));
	}

	return ss.str();
}

bool IPAddress::operator==(const IPAddress& right) {
	return m_Address == right.m_Address;
}

bool IPAddress::operator!=(const IPAddress& right) {
	return !(*this == right);
}

bool IPAddress::operator==(bool b) {
	return IsValid() == b;
}

std::ostream& operator<<(std::ostream& os, const IPAddress& addr) {
	return os << addr.ToString();
}

std::wostream& operator<<(std::wostream& os, const IPAddress& addr) {
	std::string str = addr.ToString();
	return os << std::wstring(str.begin(), str.end());
}

} // ns network
} // ns mc
