#define LOG_MODULE PacketLogModuleDnsLayer

#include "DnsResourceData.h"
#include "Logger.h"
#include "GeneralUtils.h"
#include <sstream>
#include <utility>
#include <string.h>
#include "EndianPortable.h"

namespace pcpp
{

size_t IDnsResourceData::decodeName(const char* encodedName, char* result, IDnsResource* dnsResource) const
{
	if (dnsResource == nullptr)
	{
		PCPP_LOG_ERROR("Cannot decode name, DNS resource object is NULL");
		return 0;
	}

	return dnsResource->decodeName(encodedName, result);
}

void IDnsResourceData::encodeName(const std::string& decodedName, char* result, size_t& resultLen, IDnsResource* dnsResource) const
{
	if (dnsResource == nullptr)
	{
		PCPP_LOG_ERROR("Cannot encode name, DNS resource object is NULL");
		return;
	}

	dnsResource->encodeName(decodedName, result, resultLen);
}


StringDnsResourceData::StringDnsResourceData(const uint8_t* dataPtr, size_t dataLen, IDnsResource* dnsResource)
{
	if (dataPtr && dataLen > 0)
	{
		char tempResult[4096];
		decodeName((const char*)dataPtr, tempResult, dnsResource);
		m_Data = tempResult;
	}
	else
		PCPP_LOG_ERROR("Cannot decode name, dataPtr is NULL or length is 0");
}

bool StringDnsResourceData::toByteArr(uint8_t* arr, size_t& arrLength, IDnsResource* dnsResource) const
{
	encodeName(m_Data, (char*)arr, arrLength, dnsResource);
	return true;
}

IPv4DnsResourceData::IPv4DnsResourceData(const uint8_t* dataPtr, size_t dataLen)
{
	if (dataLen != 4)
	{
		PCPP_LOG_ERROR("DNS type is A but resource length is not 4 - malformed data");
		return;
	}

	uint32_t addrAsInt = *(uint32_t*)dataPtr;
	m_Data = IPv4Address(addrAsInt);
}

bool IPv4DnsResourceData::toByteArr(uint8_t* arr, size_t& arrLength, IDnsResource*) const
{
	if (!m_Data.isValid())
	{
		PCPP_LOG_ERROR("Cannot convert IPv4 address to byte array because address is not valid");
		return false;
	}

	arrLength = sizeof(uint32_t);
	memcpy(arr, m_Data.toBytes(), sizeof(uint32_t));
	return true;
}

IPv6DnsResourceData::IPv6DnsResourceData(const uint8_t* dataPtr, size_t dataLen)
{
	if (dataLen != 16)
	{
		PCPP_LOG_ERROR("DNS type is AAAA but resource length is not 16 - malformed data");
		return;
	}

	m_Data = IPv6Address((uint8_t*)dataPtr);
}

bool IPv6DnsResourceData::toByteArr(uint8_t* arr, size_t& arrLength, IDnsResource*) const
{
	if (!m_Data.isValid())
	{
		PCPP_LOG_ERROR("Cannot convert IPv6 address to byte array because address is not valid");
		return false;
	}

	arrLength = 16;
	m_Data.copyTo(arr);
	return true;
}

MxDnsResourceData::MxDnsResourceData(uint8_t* dataPtr, size_t dataLen, IDnsResource* dnsResource)
{
	if (dataPtr && dataLen > 0)
	{
		uint16_t preference = be16toh(*(uint16_t*)dataPtr);
		char tempMX[4096];
		decodeName((const char*)(dataPtr + sizeof(preference)), tempMX, dnsResource);
		m_Data.preference = preference;
		m_Data.mailExchange = tempMX;
	}
	else
		PCPP_LOG_ERROR("Cannot decode name, dataPtr is NULL or length is 0");
}

MxDnsResourceData::MxDnsResourceData(const uint16_t& preference, const std::string& mailExchange)
{
	m_Data.preference = preference;
	m_Data.mailExchange = mailExchange;
}

bool MxDnsResourceData::operator==(const MxDnsResourceData& other) const
{
	return (m_Data.preference == other.m_Data.preference) &&
			(m_Data.mailExchange == other.m_Data.mailExchange);
}

void MxDnsResourceData::setMxData(uint16_t preference, std::string mailExchange)
{
	m_Data.preference = preference;
	m_Data.mailExchange = std::move(mailExchange);
}

std::string MxDnsResourceData::toString() const
{
	std::stringstream result;
	result << "pref: " << m_Data.preference << "; mx: " << m_Data.mailExchange;
	return result.str();
}

bool MxDnsResourceData::toByteArr(uint8_t* arr, size_t& arrLength, IDnsResource* dnsResource) const
{
	uint16_t netOrderPreference = htobe16(m_Data.preference);
	memcpy(arr, &netOrderPreference, sizeof(uint16_t));
	encodeName(m_Data.mailExchange, (char*)(arr + sizeof(uint16_t)), arrLength, dnsResource);
	arrLength += sizeof(uint16_t);

	return true;
}

GenericDnsResourceData::GenericDnsResourceData(const uint8_t* dataPtr, size_t dataLen)
{
	m_Data = nullptr;
	m_DataLen = 0;
	if (dataLen > 0 && dataPtr != nullptr)
	{
		m_DataLen = dataLen;
		m_Data = new uint8_t[dataLen];
		memcpy(m_Data, dataPtr, dataLen);
	}
}

GenericDnsResourceData::GenericDnsResourceData(const std::string& dataAsHexString)
{
	m_Data = nullptr;
	uint8_t tempDataArr[2048];
	m_DataLen = hexStringToByteArray(dataAsHexString, tempDataArr, 2048);
	if (m_DataLen != 0)
	{
		m_Data = new uint8_t[m_DataLen];
		memcpy(m_Data, tempDataArr, m_DataLen);
	}
}

GenericDnsResourceData::GenericDnsResourceData(const GenericDnsResourceData& other) : IDnsResourceData()
{
	m_DataLen = other.m_DataLen;

	if (m_DataLen > 0 && other.m_Data != nullptr)
	{
		m_Data = new uint8_t[m_DataLen];
		memcpy(m_Data, other.m_Data, m_DataLen);
	}
}

GenericDnsResourceData& GenericDnsResourceData::operator=(const GenericDnsResourceData& other)
{
	if (m_Data != nullptr)
		delete [] m_Data;

	m_Data = nullptr;
	m_DataLen = other.m_DataLen;
	if (m_DataLen > 0 && other.m_Data != nullptr)
	{
		m_Data = new uint8_t[m_DataLen];
		memcpy(m_Data, other.m_Data, m_DataLen);
	}

	return (*this);
}

bool GenericDnsResourceData::operator==(const GenericDnsResourceData& other) const
{
	if (m_DataLen != other.m_DataLen)
		return false;

	return (memcmp(m_Data, other.m_Data, m_DataLen) == 0);
}

std::string GenericDnsResourceData::toString() const
{
	return byteArrayToHexString(m_Data, m_DataLen);
}

bool GenericDnsResourceData::toByteArr(uint8_t* arr, size_t& arrLength, IDnsResource*) const
{
	if (m_DataLen == 0 || m_Data == nullptr)
	{
		PCPP_LOG_ERROR("Input data is null or illegal" << "|m_DataLen:" << m_DataLen);
		return false;
	}

	arrLength = m_DataLen;
	memcpy(arr, m_Data, m_DataLen);
	return true;
}

}
