#include <stdio.h>
#include <string.h>
#include "ipv6text.h"
#include "xash3d_types.h"

#ifdef _WIN32
#ifndef snprintf
#define snprintf _snprintf
#endif
#endif

void IPv6IPToString( char *pszOutText, const unsigned char *ip )
{
	// Find the longest run of consecutive zero quads.
	// If there's a tie, we want the leftmost one.
	int idxLongestRunStart = -1;
	int nLongestRun = 1; // It must be at least 2 quads in a row, a single 0 must not be compressed
	int nCurrentRun = 0, idxQuad;
	char *p;
	qboolean bNeedColon;

	for ( idxQuad = 0 ; idxQuad < 8 ; ++idxQuad )
	{
		// Zero
		if ( ip[idxQuad*2] || ip[idxQuad*2 + 1] )
		{
			// Terminate run
			nCurrentRun = 0;
		}
		else
		{
			// Extend (or begin) run
			++nCurrentRun;

			// Longer than previously found run?
			if ( nCurrentRun > nLongestRun )
			{
				nLongestRun = nCurrentRun;
				idxLongestRunStart = idxQuad - nCurrentRun + 1;
			}
		}
	}

	// Print the quads
	p = pszOutText;
	idxQuad = 0;
	bNeedColon = false;
	while ( idxQuad < 8 )
	{
		// Run of compressed zeros?
		if ( idxQuad == idxLongestRunStart )
		{
			*(p++) = ':';
			*(p++) = ':';
			bNeedColon = false;
			idxQuad += nLongestRun;
		}
		else
		{
			// Lowercase hex digits, with leading zeros omitted
			static const char hexdigits[] = "0123456789abcdef";
			unsigned quad;

			// Colon to separate from previous, unless
			// we are first or immediately follow compressed zero "::"
			if ( bNeedColon )
				*(p++) = ':';

			// Next quad should should print a separator
			bNeedColon = true;

			// Assemble 16-bit quad value from the two bytes
			quad = ( (unsigned)ip[idxQuad*2] << 8U ) | ip[idxQuad*2 + 1];

			// Manually do the hex number formatting.
			if ( quad >= 0x0010 )
			{
				if ( quad >= 0x0100 )
				{
					if ( quad >= 0x1000 )
						*(p++) = hexdigits[ quad >> 12U ];
					*(p++) = hexdigits[ ( quad >> 8U ) & 0xf ];
				}
				*(p++) = hexdigits[ ( quad >> 4U ) & 0xf ];
			}

			// Least significant digit, which is always printed
			*(p++) = hexdigits[ quad & 0xf ];

			// On to the next one
			++idxQuad;
		}
	}

	// String terminator
	*p = '\0';
}

void IPv6AddrToString( char *pszOutText, const unsigned char *ip, uint16_t port, uint32_t scope )
{
	char *p = pszOutText;

	// Open bracket
	*(p++) = '[';

	// Print in the IP
	IPv6IPToString( p, ip );

	// Find the end of the string
	while (*p)
		++p;

	if ( scope )
	{
		// And now the scope.  Max 32-digit scope number is 10 digits
		snprintf( p, 12, "%%%d", scope );

		// Find the end of the string
		while (*p)
			++p;
	}

	// And now the rest.  Max 16-digit port number is 6 digits
	snprintf( p, 8, "]:%u", (unsigned int)port );
}

static inline int ParseIPv6Addr_HexDigitVal( char c )
{
	if ( c >= '0' && c <= '9' ) return c - '0';
	if ( c >= 'a' && c <= 'f' ) return c - ('a' - 0xa);
	if ( c >= 'A' && c <= 'F' ) return c - ('A' - 0xa);
	return -1;
}
static inline int ParseIPv6Addr_DecimalDigitVal( char c )
{
	if ( c >= '0' && c <= '9' ) return c - '0';
	return -1;
}
static bool ParseIPv6Addr_IsSpace( char c )
{
	// Newlines don't count, intentionally
	return c == ' ' || c == '\t';
}
bool ParseIPv6Addr( const char *pszText, unsigned char *pOutIP, int *pOutPort, uint32_t *pOutScope )
{
	unsigned char *d, *pZeroFill, *pEndIP;
	const char *s;
	qboolean bQuadMustFollow;
	int nPort;

	while ( ParseIPv6Addr_IsSpace( *pszText ) )
		++pszText;
	s = pszText;

	// Skip opening bracket, if present
	if ( *s == '[' )
	{
		++s;
		while ( ParseIPv6Addr_IsSpace( *s ) )
			++s;
	}

	// Special case for leading "::"
	bQuadMustFollow = true;
	d = pOutIP;
	pZeroFill = NULL;
	pEndIP = pOutIP + 16;
	if ( s[0] == ':' && s[1] == ':' )
	{
		pZeroFill = d;
		s += 2;
		bQuadMustFollow = false;
	}

	// Parse quads until we get to the end
	for (;;)
	{
		// Next thing must be a quad, or end of input.  Is it a quad?
		int quadDigit = ParseIPv6Addr_HexDigitVal( *s );
		const char *pszStartQuad;
		int quad;

		if ( quadDigit < 0 )
		{
			if ( bQuadMustFollow )
				return false;
			break;
		}

		// No room for more quads?
		if ( d >= pEndIP )
			return false;

		pszStartQuad = s;
		++s;
		quad = quadDigit;

		// Now parse up to three additional characters
		quadDigit = ParseIPv6Addr_HexDigitVal( *s );
		if ( quadDigit >= 0 )
		{
			quad = ( quad << 4 ) | quadDigit;
			++s;

			quadDigit = ParseIPv6Addr_HexDigitVal( *s );
			if ( quadDigit >= 0 )
			{
				quad = ( quad << 4 ) | quadDigit;
				++s;

				quadDigit = ParseIPv6Addr_HexDigitVal( *s );
				if ( quadDigit >= 0 )
				{
					quad = ( quad << 4 ) | quadDigit;
					++s;
				}
			}
		}

		// Check if we hit a period, which would happen if we
		// have an IPv4 dotted decimal.  For example, "::ffff:192.168.1.210"
		if ( *s == '.' )
		{
			// Make sure we would have room to store four more bytes.
			unsigned char *pEndDottedDecimal = d+4;
			if ( pEndDottedDecimal > pEndIP )
				return false;

			// Parse 4 octets
			s = pszStartQuad;
			for (;;)
			{

				// Parse 1-3 decimal digits
				int octet = ParseIPv6Addr_DecimalDigitVal( *s );
				int dig;

				if ( octet < 0 )
					return false;
				++s;
				dig = ParseIPv6Addr_DecimalDigitVal( *s );
				if ( dig >= 0 )
				{
					++s;
					octet = octet*10 + dig;
					dig = ParseIPv6Addr_DecimalDigitVal( *s );
					if ( dig >= 0 )
					{
						++s;
						octet = octet*10 + dig;

						// Make sure value is in range.
						if ( octet > 255 )
							return false;
					}
				}
				*(d++) = (unsigned char)octet;

				// All done?
				if ( d >= pEndDottedDecimal )
					break;

				// Next thing must be dot dot separator
				if ( *s != '.' )
					return false;

				// Eat dot
				++s;
			}

			break;
		}

		// Stash it in the next slot, ignoring for now the issue
		// of compressed zeros
		*(d++) = (unsigned char)( quad >> 8 );
		*(d++) = (unsigned char)quad;

		// Only valid character for the IP portion is a colon.
		// Anything else ends the IP portion
		if ( *s != ':' )
			break;

		// Compressed zeros?
		if ( s[1] == ':' )
		{

			// Eat '::'
			s += 2;

			// Can only have one range of compressed zeros
			if ( pZeroFill )
				return false;

			// Remember where to insert the compressed zeros
			pZeroFill = d;

			// An IP can end with '::'
			bQuadMustFollow = false;
		}
		else
		{
			// If they have filed the entire IP with no compressed zeros,
			// then this is unambiguously a port number.  That's not
			// necessarily the best style, but it *is* unambiguous
			// what it should mean, so let's allow it.  If there
			// are compressed zeros, then this is ambiguous, and we will
			// always interpret it as a quad.
			if ( !pZeroFill && d >= pEndIP )
				break; // leave ':' as next character, for below

			// Eat ':'
			++s;

			// A single colon must be followed by another quad
			bQuadMustFollow = true;
		}
	}

	// End of the IP.  Do we have compressed zeros?
	if ( pZeroFill )
	{
		// How many zeros do we need to fill?
		intptr_t nZeros = pEndIP - d;
		if ( nZeros <= 0 )
			return false;

		// Shift the quads after the bytes to the end
		memmove( pZeroFill+nZeros, pZeroFill, d-pZeroFill );

		// And now fill the zeros
		memset( pZeroFill, 0, nZeros );
	}
	else
	{
		// No compressed zeros.  Just make sure we filled the IP exactly
		if ( d != pEndIP )
			return false;
	}

	if ( *s == '%' )
	{
		// Parse scope number
		uint32_t unScope = 0;
		int nScopeDigit;

		++s;

		nScopeDigit = ParseIPv6Addr_DecimalDigitVal( *s );
		if ( nScopeDigit < 0 )
			return false;
		unScope = (uint32_t)nScopeDigit;
		for (;;)
		{
			++s;
			if ( *s == '\0' || *s == ']' || ParseIPv6Addr_IsSpace( *s ) )
				break;
			nScopeDigit = ParseIPv6Addr_DecimalDigitVal( *s );
			if ( nScopeDigit < 0 )
				return false;
			unScope = unScope * 10 + nScopeDigit;
		}

		if ( pOutScope )
			*pOutScope = unScope;
	}
	else
	{
		if ( pOutScope )
			*pOutScope = 0;
	}

	// If we started with a bracket, then the next character MUST be a bracket.
	// (And this is the only circumstance in which a closing bracket would be legal)
	if ( *pszText == '[' )
	{
		while ( ParseIPv6Addr_IsSpace( *s ) )
			++s;
		if ( *s != ']' )
			return false;
		++s;
	}

	// Now we are definitely at the end of the IP.  Do we have a port?
	// We support all of the syntaxes mentioned in RFC5952 section 6 other
	// than the ambiguous case
	if ( *s == ':' || *s == '#' || *s == '.' || *s == 'p' || *s == 'P' )
	{
		++s;
	}
	else
	{
		while ( ParseIPv6Addr_IsSpace( *s ) )
			++s;
		if ( *s == '\0' )
		{
			// Parsed IP without port OK
			if ( pOutPort )
				*pOutPort = -1;
			return true;
		}

		if ( strncmp( s, "port", 4 ) == 0 )
		{
			s += 4;
			while ( ParseIPv6Addr_IsSpace( *s ) )
				++s;
		}
		else
		{
			// Extra stuff after the IP which isn't whitespace or a port
			return false;
		}
	}

	// We have a port.  If they didn't ask for it, that's considered a parse failure.
	if ( !pOutPort )
		return false;

	// Parse port number
	nPort = ParseIPv6Addr_DecimalDigitVal( *s );
	if ( nPort < 0 )
		return false;
	for (;;)
	{
		int portDigit;

		++s;
		if ( *s == '\0' || ParseIPv6Addr_IsSpace( *s ) )
			break;
		portDigit = ParseIPv6Addr_DecimalDigitVal( *s );
		if ( portDigit < 0 )
			return false;
		nPort = nPort * 10 + portDigit;
		if ( nPort > 0xffff )
			return false;
	}

	// Consume trailing whitespace; confirm nothing else in the input
	while ( ParseIPv6Addr_IsSpace( *s ) )
		++s;
	if ( *s != '\0' )
		return false;

	*pOutPort = nPort;
	return true;
}

