#include "NativeFeatureIncludes.h"
#if _RAKNET_SUPPORT_DynDNS==1 && _RAKNET_SUPPORT_TCPInterface==1

#include "TCPInterface.h"
#include "SocketLayer.h"
#include "DynDNS.h"
#include "GetTime.h"

using namespace RakNet;

struct DynDnsResult
{
	const char *description;
	const char *code;
	DynDnsResultCode resultCode;
};

DynDnsResult resultTable[13] =
{
	// See http://www.dyndns.com/developers/specs/flow.pdf
	{"DNS update success.\nPlease wait up to 60 seconds for the change to take effect.\n", "good", RC_SUCCESS}, // Even with success, it takes time for the cache to update!
	{"No change", "nochg", RC_NO_CHANGE},
	{"Host has been blocked. You will need to contact DynDNS to reenable.", "abuse", RC_ABUSE},
	{"Useragent is blocked", "badagent", RC_BAD_AGENT},
	{"Username/password pair bad", "badauth", RC_BAD_AUTH},
	{"Bad system parameter", "badsys", RC_BAD_SYS},
	{"DNS inconsistency", "dnserr", RC_DNS_ERROR},
	{"Paid account feature", "!donator", RC_NOT_DONATOR},
	{"No such host in system", "nohost", RC_NO_HOST},
	{"Invalid hostname format", "notfqdn", RC_NOT_FQDN},
	{"Serious error", "numhost", RC_NUM_HOST},
	{"This host exists, but does not belong to you", "!yours", RC_NOT_YOURS},
	{"911", "911", RC_911},
};
DynDNS::DynDNS()
{
	connectPhase=CP_IDLE;
	tcp=0;
}
DynDNS::~DynDNS()
{
	if (tcp)
		RakNet::OP_DELETE(tcp, _FILE_AND_LINE_);
}
void DynDNS::Stop(void)
{
	tcp->Stop();
	connectPhase = CP_IDLE;
	RakNet::OP_DELETE(tcp, _FILE_AND_LINE_);
	tcp=0;
}


// newIPAddress is optional - if left out, DynDNS will use whatever it receives
void DynDNS::UpdateHostIP(const char *dnsHost, const char *newIPAddress, const char *usernameAndPassword )
{
	myIPStr[0]=0;

	if (tcp==0)
		tcp = RakNet::OP_NEW<TCPInterface>(_FILE_AND_LINE_);
	connectPhase = CP_IDLE;
	host = dnsHost;

	if (tcp->Start(0, 1)==false)
	{
		SetCompleted(RC_TCP_FAILED_TO_START, "TCP failed to start");
		return;
	}

	connectPhase = CP_CONNECTING_TO_CHECKIP;
	tcp->Connect("checkip.dyndns.org", 80, false);

	// See https://www.dyndns.com/developers/specs/syntax.html
	getString="GET /nic/update?hostname=";
	getString+=dnsHost;
	if (newIPAddress)
	{
		getString+="&myip=";
		getString+=newIPAddress;
	}
	getString+="&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG HTTP/1.0\n";
	getString+="Host: members.dyndns.org\n";
	getString+="Authorization: Basic ";
	char outputData[512];
	TCPInterface::Base64Encoding(usernameAndPassword, (int) strlen(usernameAndPassword), outputData);
	getString+=outputData;
	getString+="User-Agent: Jenkins Software LLC - PC - 1.0\n\n";
}
void DynDNS::Update(void)
{
	if (connectPhase==CP_IDLE)
		return;

	serverAddress=tcp->HasFailedConnectionAttempt();
	if (serverAddress!=UNASSIGNED_SYSTEM_ADDRESS)
	{
		SetCompleted(RC_TCP_DID_NOT_CONNECT, "Could not connect to DynDNS");
		return;
	}

	serverAddress=tcp->HasCompletedConnectionAttempt();
	if (serverAddress!=UNASSIGNED_SYSTEM_ADDRESS)
	{
		if (connectPhase == CP_CONNECTING_TO_CHECKIP)
		{
			checkIpAddress=serverAddress;
			connectPhase = CP_WAITING_FOR_CHECKIP_RESPONSE;
			tcp->Send("GET\n\n", (unsigned int) strlen("GET\n\n"), serverAddress, false); // Needs 2 newlines! This is not documented and wasted a lot of my time
		}
		else
		{
			connectPhase = CP_WAITING_FOR_DYNDNS_RESPONSE;
			tcp->Send(getString.C_String(), (unsigned int) getString.GetLength(), serverAddress, false);
		}
		phaseTimeout=RakNet::GetTime()+1000;
	}

	if (connectPhase==CP_WAITING_FOR_CHECKIP_RESPONSE && RakNet::GetTime()>phaseTimeout)
	{
		connectPhase = CP_CONNECTING_TO_DYNDNS;
		tcp->CloseConnection(checkIpAddress);
		tcp->Connect("members.dyndns.org", 80, false);
	}
	else if (connectPhase==CP_WAITING_FOR_DYNDNS_RESPONSE && RakNet::GetTime()>phaseTimeout)
	{
		SetCompleted(RC_DYNDNS_TIMEOUT, "DynDNS did not respond");
		return;
	}

	Packet *packet = tcp->Receive();
	if (packet)
	{
		if (connectPhase==CP_WAITING_FOR_DYNDNS_RESPONSE)
		{
			unsigned int i;

			char *result;
			result=strstr((char*) packet->data, "Connection: close");
			if (result!=0)
			{
				result+=strlen("Connection: close");
				while (*result && (*result=='\r') || (*result=='\n') || (*result==' ') )
					result++;
				for (i=0; i < 13; i++)
				{
					if (strncmp(resultTable[i].code, result, strlen(resultTable[i].code))==0)
					{
						if (resultTable[i].resultCode==RC_SUCCESS)
						{
							// Read my external IP into myIPStr
							// Advance until we hit a number
							while (*result && ((*result<'0') || (*result>'9')) )
								result++;
							if (*result)
							{
								SystemAddress parser;
								parser.FromString(result);
								parser.ToString(false, myIPStr);
							}
						}
						tcp->DeallocatePacket(packet);
						SetCompleted(resultTable[i].resultCode, resultTable[i].description);
						break;
					}
				}
				if (i==13)
				{
					tcp->DeallocatePacket(packet);
					SetCompleted(RC_UNKNOWN_RESULT, "DynDNS returned unknown result");
				}
			}
			else
			{
				tcp->DeallocatePacket(packet);
				SetCompleted(RC_PARSING_FAILURE, "Parsing failure on returned string from DynDNS");
			}

			return;
		}
		else
		{
			/*
			HTTP/1.1 200 OK
			Content-Type: text/html
			Server: DynDNS-CheckIP/1.0
			Connection: close
			Cache-Control: no-cache
			Pragma: no-cache
			Content-Length: 105

			<html><head><title>Current IP Check</title></head><body>Current IP Address: 98.1
			89.219.22</body></html>


			Connection to host lost.
			*/

			char *result;
			result=strstr((char*) packet->data, "Current IP Address: ");
			if (result!=0)
			{
				result+=strlen("Current IP Address: ");
				SystemAddress myIp;
				myIp.FromString(result);
				myIp.ToString(false, myIPStr);

				// Resolve DNS we are setting. If equal to current then abort
				const char *existingHost = ( char* ) SocketLayer::DomainNameToIP( host.C_String() );
				if (existingHost && strcmp(existingHost, myIPStr)==0)
				{
					// DynDNS considers setting the IP to what it is already set abuse
					tcp->DeallocatePacket(packet);
					SetCompleted(RC_DNS_ALREADY_SET, "No action needed");
					return;
				}
			}

			tcp->DeallocatePacket(packet);
			tcp->CloseConnection(packet->systemAddress);

			connectPhase = CP_CONNECTING_TO_DYNDNS;
			tcp->Connect("members.dyndns.org", 80, false);
		}
	}

	if (tcp->HasLostConnection()!=UNASSIGNED_SYSTEM_ADDRESS)
	{
		if (connectPhase==CP_WAITING_FOR_DYNDNS_RESPONSE)
		{
			SetCompleted(RC_CONNECTION_LOST_WITHOUT_RESPONSE, "Connection lost to DynDNS during GET operation");
		}
	}
}


#endif // _RAKNET_SUPPORT_DynDNS
