#include "enum_codets.h"
#include "gen_classes.h"
#include "Regex.h"
#include "common_debug_log.h"
#include <iostream>
#include <fstream>
#include <math.h>


//compiler switches:
//#define ENUM_ONLY 1
#define DEBUG_LOG_FILE "C:\\Temp\\LibEnum.log"

namespace t3devlib { 

#ifdef ENUM_ONLY

void PAInit()
{
}

void SAInit()
{
}

void CDInit()
{
}

void PAReset()
{
}

void SAReset()
{
}
#endif

	namespace gen {

/*type union NAPTRmessage
{
    DnsMessage	dnsMessage,
    EnumMessage	enumMessage
} */
void NAPTRmessage::PreDecode (Buffer& buffer) throw (DecodeError)
{
	debug_log_PreDecode(DEBUG_LOG_FILE, "NAPTRmessage");

	Unsigned transaction(16);
	Unsigned flags(16);
	Unsigned qcount(16);

	//store message start offset
	int nPos = buffer.GetPosition();

	//predecode header
	transaction.Decode(buffer);
	flags.Decode(buffer);
	qcount.Decode(buffer);

	//set the decoded union field
	unsigned int nFlags = flags.GetValue();
	unsigned int nQuestions = qcount.GetValue();

	int nResponse = nFlags & 0x00008000;

	int nSelectedFieldId = id_dnsMessage;
	if(nQuestions)
	{
		Unsigned lenByte(8);
		Unsigned typeVal(16);
		buffer.SetPosition(buffer.GetPosition() + 6 * 8);//3xCount
		
		//skip DomainName
		lenByte.Decode(buffer);
		while(lenByte.GetValue())
		{
			if(lenByte.GetValue() == 0xC0)
				break;
			else
				buffer.SetPosition(buffer.GetPosition() + lenByte.GetValue() * 8);
			lenByte.Decode(buffer);
		}
		typeVal.Decode(buffer);
		if(typeVal.GetValue() == 35)
			nSelectedFieldId = id_enumMessage;
	}

	//rewind buffer position
	buffer.SetPosition(nPos);
	SetHypChosenId(nSelectedFieldId);
}

/*type union DnsMessage
{
    DnsQuery	query,
    DnsResponse response	
}*/
void DnsMessage::PreDecode (Buffer& buffer) throw (DecodeError)
{
	debug_log_PreDecode(DEBUG_LOG_FILE, "DnsMessage");
	
	Unsigned transaction(16);
	Unsigned flags(16);

	//store message start offset
	int nPos = buffer.GetPosition();

	//predecode header
	transaction.Decode(buffer);
	flags.Decode(buffer);

	//set the decoded union field
	unsigned int nFlags = flags.GetValue();

	int nResponse = nFlags & 0x00008000;

	//rewind buffer position
	buffer.SetPosition(nPos);
	SetHypChosenId(nResponse ? id_response : id_query );
}

/*type union EnumMessage {
	EnumQuery		query,
	EnumResponse	response	
}*/
void EnumMessage::PreDecode(Buffer& buffer) throw (DecodeError)
{
	debug_log_PreDecode(DEBUG_LOG_FILE, "EnumMessage");

	Unsigned transaction(16);
	Unsigned flags(16);

	//store message start offset
	int nPos = buffer.GetPosition();

	//predecode header
	transaction.Decode(buffer);
	flags.Decode(buffer);

	//set the decoded union field
	unsigned int nFlags = flags.GetValue();

	int nResponse = nFlags & 0x00008000;

	//rewind buffer position
	buffer.SetPosition(nPos);
	SetHypChosenId(nResponse ? id_response : id_query );
}

/*type record DnsQuery
{
	DnsHeader header,
	DnsQuestionList question_list,
	DnsAnswerList answer_list,
	DnsAuthorityList authority_list,
	DnsAdditionalList additional_list
}*/
void DnsQuery::PostDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PostDecodeField(DEBUG_LOG_FILE, "DnsQuery", field_id);

	if(field_id == id_header)
	{
		DnsHeader &header = Get_header();
		
		unsigned int nLen = header.Get_qDCOUNT().GetValue();
		if(nLen)
		{
			SetHypFieldIsPresent(id_question_list, 1);
			Get_question_list().SetHypSize(nLen);
		}
		else
			SetHypFieldIsPresent(id_question_list, 0);

		nLen = header.Get_aNCOUNT().GetValue();
		if(nLen)
		{
			SetHypFieldIsPresent(id_answer_list, 1);
			Get_answer_list().SetHypSize(nLen);
		}
		else
			SetHypFieldIsPresent(id_answer_list, 0);

		nLen = header.Get_nSCOUNT().GetValue();
		if(nLen)
		{
			SetHypFieldIsPresent(id_authority_list, 1);
			Get_authority_list().SetHypSize(nLen);
		}
		else
			SetHypFieldIsPresent(id_authority_list, 0);
		
		nLen = header.Get_aRCOUNT().GetValue();
		if(nLen)
		{
			SetHypFieldIsPresent(id_additional_list, 1);
			Get_additional_list().SetHypSize(nLen);
		}
		else
			SetHypFieldIsPresent(id_additional_list, 0);
	}
}

/*type record DnsResponse
{
	DnsHeader header,
	DnsQuestionList question_list,
	DnsAnswerList answer_list,
	DnsAuthorityList authority_list,
	DnsAdditionalList additional_list
}*/
void DnsResponse::PostDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PostDecodeField(DEBUG_LOG_FILE, "DnsResponse", field_id);

	if(field_id == id_header)
	{
		DnsHeader &header = Get_header();
		Get_question_list().SetHypSize(header.Get_qDCOUNT().GetValue());
		Get_answer_list().SetHypSize(header.Get_aNCOUNT().GetValue());
		Get_authority_list().SetHypSize(header.Get_nSCOUNT().GetValue());
		Get_additional_list().SetHypSize(header.Get_aRCOUNT().GetValue());
	}
}

/*type record DnsHeader
{
	UInt16 id,
	Bit1 qR_flag,
	UInt4 opcode,
	Bit1 aA_flag,
	Bit1 tC_flag,
	Bit1 rD_flag,
	Bit1 rA_flag,
	Bit3 z_field,
	UInt4 rCODE,
	UInt16 qDCOUNT,
	UInt16 aNCOUNT,
	UInt16 nSCOUNT,
	UInt16 aRCOUNT
}*/
void DnsHeader::PreDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PreDecodeField(DEBUG_LOG_FILE, "DnsHeader", field_id);
}

//this will not dereference pointers!!!
unsigned int getDomainNameLength(Buffer& buffer)
{
	Unsigned lenByte(8);
	Unsigned typeVal(16);
	
	unsigned int nByteLen = 1;
	unsigned int nStartPos = buffer.GetPosition();
	

	lenByte.Decode(buffer);
	while(lenByte.GetValue())
	{
		if(lenByte.GetValue() == 0xC0)
		{
			nByteLen++;
			break;
		}
		else
		{
			nByteLen += lenByte.GetValue();
			buffer.SetPosition(buffer.GetPosition() + lenByte.GetValue() * 8);
		}
		nByteLen++;
		lenByte.Decode(buffer);
	}

	buffer.SetPosition(nStartPos);
	return nByteLen;
}

//caller sets position
void process_resolve_pointer(Buffer& buffer, std::string & sRes)
{
	Unsigned charByte(8);
	
	unsigned char ch = 0;
	charByte.Decode(buffer);
	
	while((ch = (unsigned char)charByte.GetValue()) != 0)
	{
		if(ch == 0xC0)//another pointer???
		{
			charByte.Decode(buffer);
			//TODO: we assume that the message starts at pos 0
			buffer.SetPosition(charByte.GetValue() * 8);
			process_resolve_pointer(buffer, sRes);
			return;
		}
		else
		{
			if(sRes.length())
				sRes.append(".", 1);
			unsigned int nSegmentLen = ch;
			while(nSegmentLen--)
			{
				charByte.Decode(buffer);
				ch = (unsigned char)charByte.GetValue();
				sRes.append((char *)&ch, 1);
			}

#ifdef DEBUG_LOG
		std::ofstream logfile(DEBUG_LOG_FILE, ios::app);
		logfile <<  "process_resolve_pointer: " << sRes << std::endl;
		logfile.close();
#endif
		}
		charByte.Decode(buffer);
	}
}

void process_DNS_pointers(Buffer& buffer, Charstring& strVal)
{
	unsigned int nStartPos = buffer.GetPosition();
	const char * pSrc = strVal.GetValue();
	unsigned char pLenOct = 0;
	std::string sRes;

	while((pLenOct = (unsigned char)*pSrc) != 0x00)
	{
		pSrc++;
		if(pLenOct == 0xC0)//is a pointer
		{
			buffer.SetPosition((*pSrc) * 8);
			process_resolve_pointer(buffer, sRes);
			break;
		}
		else
		{
			if(sRes.length())
				sRes.append(".", 1);

			sRes.append(pSrc, pLenOct);
			pSrc += pLenOct;

#ifdef DEBUG_LOG
		std::ofstream logfile(DEBUG_LOG_FILE, ios::app);
		logfile <<  "process_DNS_pointers: " << sRes << std::endl;
		logfile.close();
#endif
		}
	}
	strVal.SetValue(sRes.c_str());
	buffer.SetPosition(nStartPos);
}

/*type record DnsQuestion
{
	DnsDomainName qNAME,
	UInt16 qTYPE,
	UInt16 qCLASS
}*/
void DnsQuestion::PreDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PreDecodeField(DEBUG_LOG_FILE, "DnsQuestion", field_id);

	if(field_id == id_qNAME)
	{
		unsigned int nStrLen = getDomainNameLength(buffer);
		SetHypFieldLength(field_id, nStrLen * 8);
	}
}
void DnsQuestion::PostDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PostDecodeField(DEBUG_LOG_FILE, "DnsQuestion", field_id);

	if(field_id == id_qNAME)
		process_DNS_pointers(buffer, Get_qNAME());
}

/*type record DnsResourceRecord
{
	DnsDomainName domainName,
	Int16 type_,
	Int16 class_,
	UInt32 ttl,
	UInt16 rd_length,
	DnsRDATA r_data
}*/
void DnsResourceRecord::PreDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PreDecodeField(DEBUG_LOG_FILE, "DnsResourceRecord", field_id);

	if(field_id == id_domainName)
	{
		unsigned int nStrLen = getDomainNameLength(buffer);
		SetHypFieldLength(field_id, nStrLen * 8);
	}
	else if(field_id == id_r_data)
		SetHypFieldLength(id_r_data, Get_rd_length().GetValue() * 8);
}

void DnsResourceRecord::PostDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PostDecodeField(DEBUG_LOG_FILE, "DnsResourceRecord", field_id);

	if(field_id == id_domainName)
		process_DNS_pointers(buffer, Get_domainName());
}

/*type record EnumQuery {
	DnsHeader header,
	EnumQuestionList enumQuestion_list,
	EnumAnswerList enumResponse_list,
	DnsAuthorityList authority_list,
	DnsAdditionalList additional_list
} with { encode "NAPTRCodec"}*/
void EnumQuery::PostDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PostDecodeField(DEBUG_LOG_FILE, "EnumQuery", field_id);

	if(field_id == id_header)
	{
		DnsHeader &header = Get_header();

		unsigned int nLen = header.Get_qDCOUNT().GetValue();
		if(nLen)
		{
			SetHypFieldIsPresent(id_enumQuestion_list, 1);
			Get_enumQuestion_list().SetHypSize(nLen);
		}
		else
			SetHypFieldIsPresent(id_enumQuestion_list, 0);

		nLen = header.Get_aNCOUNT().GetValue();
		if(nLen)
		{
			SetHypFieldIsPresent(id_enumResponse_list, 1);
			Get_enumResponse_list().SetHypSize(nLen);
		}
		else
			SetHypFieldIsPresent(id_enumResponse_list, 0);

		nLen = header.Get_nSCOUNT().GetValue();
		if(nLen)
		{
			SetHypFieldIsPresent(id_authority_list, 1);
			Get_authority_list().SetHypSize(nLen);
		}
		else
			SetHypFieldIsPresent(id_authority_list, 0);
		
		nLen = header.Get_aRCOUNT().GetValue();
		if(nLen)
		{
			SetHypFieldIsPresent(id_additional_list, 1);
			Get_additional_list().SetHypSize(nLen);
		}
		else
			SetHypFieldIsPresent(id_additional_list, 0);
	}
}

/*type record EnumResponse
{
	DnsHeader header,
	EnumQuestionList enumQuestion_list,
	EnumAnswerList enumAnswer_list,
	DnsAuthorityList authority_list,
	DnsAdditionalList additional_list
}*/
void EnumResponse::PostDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PostDecodeField(DEBUG_LOG_FILE, "EnumResponse", field_id);

	if(field_id == id_header)
	{
		DnsHeader &header = Get_header();
		unsigned int nLen = header.Get_qDCOUNT().GetValue();
		if(nLen)
		{
			SetHypFieldIsPresent(id_enumQuestion_list, 1);
			Get_enumQuestion_list().SetHypSize(nLen);
		}
		else
			SetHypFieldIsPresent(id_enumQuestion_list, 0);

		nLen = header.Get_aNCOUNT().GetValue();
		if(nLen)
		{
			SetHypFieldIsPresent(id_enumAnswer_list, 1);
			Get_enumAnswer_list().SetHypSize(nLen);
		}
		else
			SetHypFieldIsPresent(id_enumAnswer_list, 0);

		nLen = header.Get_nSCOUNT().GetValue();
		if(nLen)
		{
			SetHypFieldIsPresent(id_authority_list, 1);
			Get_authority_list().SetHypSize(nLen);
		}
		else
			SetHypFieldIsPresent(id_authority_list, 0);
		
		nLen = header.Get_aRCOUNT().GetValue();
		if(nLen)
		{
			SetHypFieldIsPresent(id_additional_list, 1);
			Get_additional_list().SetHypSize(nLen);
		}
		else
			SetHypFieldIsPresent(id_additional_list, 0);
	}
}

/*type record EnumQuestion
{
	TN	e164TelNumber,
	UInt16 qTYPE, //35
	UInt16 qCLASS //
}*/

void EnumQuestion::PreDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PreDecodeField(DEBUG_LOG_FILE, "EnumQuestion", field_id);

	if(field_id == id_e164TelNumber)
	{
		unsigned int nStrLen = getDomainNameLength(buffer);
		SetHypFieldLength(field_id, nStrLen * 8);
	}
}
void EnumQuestion::PostDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PostDecodeField(DEBUG_LOG_FILE, "EnumQuestion", field_id);

	if(field_id == id_e164TelNumber)
		process_DNS_pointers(buffer, Get_e164TelNumber());
}


/*type record EnumAnswer
{
	TN		e164TelNumber,			
	Int16 	naptrType,
	Int16 	class_,
	UInt32 	ttl,
	UInt16 	rd_length,
	DnsNaptrRRData naptrRR
}*/
void EnumAnswer::PreDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PreDecodeField(DEBUG_LOG_FILE, "EnumAnswer", field_id);

	if(field_id == id_e164TelNumber)
	{
		unsigned int nStrLen = getDomainNameLength(buffer);
		SetHypFieldLength(field_id, nStrLen * 8);
	}
}

void EnumAnswer::PostDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PostDecodeField(DEBUG_LOG_FILE, "EnumAnswer", field_id);

	if(field_id == id_e164TelNumber)
		process_DNS_pointers(buffer, Get_e164TelNumber());
}

/*type record DnsNaptrRRData
{
	UInt16 	naptrOrder,
	UInt16 	naptrPreference,
	UInt8 	flags_length,
	NaptrCharString naptrFlags,
	UInt8 	service_length,
	NaptrCharString naptrService,
	UInt8 	regex_length,
	NaptrCharString naptrRegex,
	UInt8 	replacementLength,
	DnsDomainName naptrReplacement
}*/
void DnsNaptrRRData::PreDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PreDecodeField(DEBUG_LOG_FILE, "DnsNaptrRRData", field_id);

	int nFieldLength = 0;
	if(field_id == id_naptrFlags)
	{
		nFieldLength = Get_flags_length().GetValue();
		SetHypFieldLength(field_id, nFieldLength * 8);
	}
	else if(field_id == id_naptrService)
	{
		nFieldLength = Get_service_length().GetValue();
		SetHypFieldLength(field_id, nFieldLength * 8);
	}
	else if(field_id == id_naptrRegex)
	{
		nFieldLength = Get_regex_length().GetValue();
		SetHypFieldLength(field_id, nFieldLength * 8);
	}
	else if(field_id == id_naptrReplacement)
	{
		nFieldLength = Get_replacementLength().GetValue();
		SetHypFieldLength(field_id, nFieldLength * 8);
	}
}

void DnsNaptrRRData::PostDecodeField(int field_id, Buffer& buffer) throw (DecodeError)
{
	debug_log_PostDecodeField(DEBUG_LOG_FILE, "DnsNaptrRRData", field_id);

	if(field_id == id_naptrReplacement)
		process_DNS_pointers(buffer, Get_naptrReplacement());
}

}} // namespaces
