//========= Copyright Valve Corporation ============//
#include "strtools.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef __linux__
#define stricmp strcasecmp
#define strnicmp strncasecmp
#endif

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool StringHasPrefix(const std::string &sString, const std::string &sPrefix)
{
	return 0 == strnicmp(sString.c_str(), sPrefix.c_str(), sPrefix.length());
}

bool StringHasPrefixCaseSensitive(const std::string &sString, const std::string &sPrefix)
{
	return 0 == strncmp(sString.c_str(), sPrefix.c_str(), sPrefix.length());
}

bool StringHasSuffix(const std::string &sString, const std::string &sSuffix)
{
	size_t cStrLen = sString.length();
	size_t cSuffixLen = sSuffix.length();

	if (cSuffixLen > cStrLen)
		return false;

	std::string sStringSuffix = sString.substr(cStrLen - cSuffixLen, cSuffixLen);

	return 0 == stricmp(sStringSuffix.c_str(), sSuffix.c_str());
}

bool StringHasSuffixCaseSensitive(const std::string &sString, const std::string &sSuffix)
{
	size_t cStrLen = sString.length();
	size_t cSuffixLen = sSuffix.length();

	if (cSuffixLen > cStrLen)
		return false;

	std::string sStringSuffix = sString.substr(cStrLen - cSuffixLen, cSuffixLen);

	return 0 == strncmp(sStringSuffix.c_str(), sSuffix.c_str(), cSuffixLen);
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
std::string UTF16to8(const wchar_t *in)
{
	std::string out;
	unsigned int codepoint = 0;
	for (; in && *in != 0; ++in)
	{
		if (*in >= 0xd800 && *in <= 0xdbff)
			codepoint = ((*in - 0xd800) << 10) + 0x10000;
		else
		{
			if (*in >= 0xdc00 && *in <= 0xdfff)
				codepoint |= *in - 0xdc00;
			else
				codepoint = *in;

			if (codepoint <= 0x7f)
				out.append(1, static_cast<char>(codepoint));
			else if (codepoint <= 0x7ff)
			{
				out.append(1, static_cast<char>(0xc0 | ((codepoint >> 6) & 0x1f)));
				out.append(1, static_cast<char>(0x80 | (codepoint & 0x3f)));
			}
			else if (codepoint <= 0xffff)
			{
				out.append(1, static_cast<char>(0xe0 | ((codepoint >> 12) & 0x0f)));
				out.append(1, static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f)));
				out.append(1, static_cast<char>(0x80 | (codepoint & 0x3f)));
			}
			else
			{
				out.append(1, static_cast<char>(0xf0 | ((codepoint >> 18) & 0x07)));
				out.append(1, static_cast<char>(0x80 | ((codepoint >> 12) & 0x3f)));
				out.append(1, static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f)));
				out.append(1, static_cast<char>(0x80 | (codepoint & 0x3f)));
			}
			codepoint = 0;
		}
	}
	return out;
}

std::wstring UTF8to16(const char *in)
{
	std::wstring out;
	unsigned int codepoint = 0;
	int following = 0;
	for (; in && *in != 0; ++in)
	{
		unsigned char ch = *in;
		if (ch <= 0x7f)
		{
			codepoint = ch;
			following = 0;
		}
		else if (ch <= 0xbf)
		{
			if (following > 0)
			{
				codepoint = (codepoint << 6) | (ch & 0x3f);
				--following;
			}
		}
		else if (ch <= 0xdf)
		{
			codepoint = ch & 0x1f;
			following = 1;
		}
		else if (ch <= 0xef)
		{
			codepoint = ch & 0x0f;
			following = 2;
		}
		else
		{
			codepoint = ch & 0x07;
			following = 3;
		}
		if (following == 0)
		{
			if (codepoint > 0xffff)
			{
				out.append(1, static_cast<wchar_t>(0xd800 + (codepoint >> 10)));
				out.append(1, static_cast<wchar_t>(0xdc00 + (codepoint & 0x03ff)));
			}
			else
				out.append(1, static_cast<wchar_t>(codepoint));
			codepoint = 0;
		}
	}
	return out;
}

void strcpy_safe(char *pchBuffer, size_t unBufferSizeBytes, const char *pchSource)
{
	pchBuffer[unBufferSizeBytes - 1] = '\0';
	strncpy(pchBuffer, pchSource, unBufferSizeBytes - 1);
}

// --------------------------------------------------------------------
// Purpose: converts a string to upper case
// --------------------------------------------------------------------
std::string StringToUpper(const std::string &sString)
{
	std::string sOut;
	sOut.reserve(sString.size() + 1);
	for (std::string::const_iterator i = sString.begin(); i != sString.end(); i++)
	{
		sOut.push_back((char)toupper(*i));
	}

	return sOut;
}

// --------------------------------------------------------------------
// Purpose: converts a string to lower case
// --------------------------------------------------------------------
std::string StringToLower(const std::string &sString)
{
	std::string sOut;
	sOut.reserve(sString.size() + 1);
	for (std::string::const_iterator i = sString.begin(); i != sString.end(); i++)
	{
		sOut.push_back((char)tolower(*i));
	}

	return sOut;
}

uint32_t ReturnStdString(const std::string &sValue, char *pchBuffer, uint32_t unBufferLen)
{
	uint32_t unLen = (uint32_t)sValue.length() + 1;
	if (!pchBuffer || !unBufferLen)
		return unLen;

	if (unBufferLen < unLen)
	{
		pchBuffer[0] = '\0';
	}
	else
	{
		memcpy(pchBuffer, sValue.c_str(), unLen);
	}

	return unLen;
}

void BufferToStdString(std::string &sDest, const char *pchBuffer, uint32_t unBufferLen)
{
	sDest.resize(unBufferLen + 1);
	memcpy(const_cast<char *>(sDest.c_str()), pchBuffer, unBufferLen);
	const_cast<char *>(sDest.c_str())[unBufferLen] = '\0';
}

/** Returns a std::string from a uint64_t */
std::string Uint64ToString(uint64_t ulValue)
{
	char buf[22];
#if defined(_WIN32)
	sprintf_s(buf, "%llu", ulValue);
#else
	snprintf(buf, sizeof(buf), "%llu", (long long unsigned int)ulValue);
#endif
	return buf;
}

/** returns a uint64_t from a string */
uint64_t StringToUint64(const std::string &sValue)
{
	return strtoull(sValue.c_str(), NULL, 0);
}

//-----------------------------------------------------------------------------
// Purpose: Helper for converting a numeric value to a hex digit, value should be 0-15.
//-----------------------------------------------------------------------------
char cIntToHexDigit(int nValue)
{
	//Assert( nValue >= 0 && nValue <= 15 );
	return "0123456789ABCDEF"[nValue & 15];
}

//-----------------------------------------------------------------------------
// Purpose: Helper for converting a hex char value to numeric, return -1 if the char
//          is not a valid hex digit.
//-----------------------------------------------------------------------------
int iHexCharToInt(char cValue)
{
	int32_t iValue = cValue;
	if ((uint32_t)(iValue - '0') < 10)
		return iValue - '0';

	iValue |= 0x20;
	if ((uint32_t)(iValue - 'a') < 6)
		return iValue - 'a' + 10;

	return -1;
}

//-----------------------------------------------------------------------------
// Purpose: Internal implementation of encode, works in the strict RFC manner, or
//          with spaces turned to + like HTML form encoding.
//-----------------------------------------------------------------------------
void V_URLEncodeInternal(char *pchDest, int nDestLen, const char *pchSource, int nSourceLen, bool bUsePlusForSpace)
{
	//AssertMsg( nDestLen > 3*nSourceLen, "Target buffer for V_URLEncode should be 3x source length, plus one for terminating null\n" );

	int iDestPos = 0;
	for (int i = 0; i < nSourceLen; ++i)
	{
		// worst case we need 3 additional chars
		if ((iDestPos + 3) > nDestLen)
		{
			pchDest[0] = '\0';
			//			AssertMsg( false, "Target buffer too short\n" );
			return;
		}

		// We allow only a-z, A-Z, 0-9, period, underscore, and hyphen to pass through unescaped.
		// These are the characters allowed by both the original RFC 1738 and the latest RFC 3986.
		// Current specs also allow '~', but that is forbidden under original RFC 1738.
		if (!(pchSource[i] >= 'a' && pchSource[i] <= 'z') && !(pchSource[i] >= 'A' && pchSource[i] <= 'Z') && !(pchSource[i] >= '0' && pchSource[i] <= '9') && pchSource[i] != '-' && pchSource[i] != '_' && pchSource[i] != '.')
		{
			if (bUsePlusForSpace && pchSource[i] == ' ')
			{
				pchDest[iDestPos++] = '+';
			}
			else
			{
				pchDest[iDestPos++] = '%';
				uint8_t iValue = pchSource[i];
				if (iValue == 0)
				{
					pchDest[iDestPos++] = '0';
					pchDest[iDestPos++] = '0';
				}
				else
				{
					char cHexDigit1 = cIntToHexDigit(iValue % 16);
					iValue /= 16;
					char cHexDigit2 = cIntToHexDigit(iValue);
					pchDest[iDestPos++] = cHexDigit2;
					pchDest[iDestPos++] = cHexDigit1;
				}
			}
		}
		else
		{
			pchDest[iDestPos++] = pchSource[i];
		}
	}

	if ((iDestPos + 1) > nDestLen)
	{
		pchDest[0] = '\0';
		//AssertMsg( false, "Target buffer too short to terminate\n" );
		return;
	}

	// Null terminate
	pchDest[iDestPos++] = 0;
}

//-----------------------------------------------------------------------------
// Purpose: Internal implementation of decode, works in the strict RFC manner, or
//          with spaces turned to + like HTML form encoding.
//
//			Returns the amount of space used in the output buffer.
//-----------------------------------------------------------------------------
size_t V_URLDecodeInternal(char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen, bool bUsePlusForSpace)
{
	if (nDecodeDestLen < nEncodedSourceLen)
	{
		//AssertMsg( false, "V_URLDecode needs a dest buffer at least as large as the source" );
		return 0;
	}

	int iDestPos = 0;
	for (int i = 0; i < nEncodedSourceLen; ++i)
	{
		if (bUsePlusForSpace && pchEncodedSource[i] == '+')
		{
			pchDecodeDest[iDestPos++] = ' ';
		}
		else if (pchEncodedSource[i] == '%')
		{
			// Percent signifies an encoded value, look ahead for the hex code, convert to numeric, and use that

			// First make sure we have 2 more chars
			if (i < nEncodedSourceLen - 2)
			{
				char cHexDigit1 = pchEncodedSource[i + 1];
				char cHexDigit2 = pchEncodedSource[i + 2];

				// Turn the chars into a hex value, if they are not valid, then we'll
				// just place the % and the following two chars direct into the string,
				// even though this really shouldn't happen, who knows what bad clients
				// may do with encoding.
				bool bValid = false;
				int iValue = iHexCharToInt(cHexDigit1);
				if (iValue != -1)
				{
					iValue *= 16;
					int iValue2 = iHexCharToInt(cHexDigit2);
					if (iValue2 != -1)
					{
						iValue += iValue2;
						pchDecodeDest[iDestPos++] = (char)iValue;
						bValid = true;
					}
				}

				if (!bValid)
				{
					pchDecodeDest[iDestPos++] = '%';
					pchDecodeDest[iDestPos++] = cHexDigit1;
					pchDecodeDest[iDestPos++] = cHexDigit2;
				}
			}

			// Skip ahead
			i += 2;
		}
		else
		{
			pchDecodeDest[iDestPos++] = pchEncodedSource[i];
		}
	}

	// We may not have extra room to NULL terminate, since this can be used on raw data, but if we do
	// go ahead and do it as this can avoid bugs.
	if (iDestPos < nDecodeDestLen)
	{
		pchDecodeDest[iDestPos] = 0;
	}

	return (size_t)iDestPos;
}

//-----------------------------------------------------------------------------
// Purpose: Encodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2.
//          This version of the call isn't a strict RFC implementation, but uses + for space as is
//          the standard in HTML form encoding, despite it not being part of the RFC.
//
//          Dest buffer should be at least as large as source buffer to guarantee room for decode.
//-----------------------------------------------------------------------------
void V_URLEncode(char *pchDest, int nDestLen, const char *pchSource, int nSourceLen)
{
	return V_URLEncodeInternal(pchDest, nDestLen, pchSource, nSourceLen, true);
}

//-----------------------------------------------------------------------------
// Purpose: Decodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2.
//          This version of the call isn't a strict RFC implementation, but uses + for space as is
//          the standard in HTML form encoding, despite it not being part of the RFC.
//
//          Dest buffer should be at least as large as source buffer to guarantee room for decode.
//			Dest buffer being the same as the source buffer (decode in-place) is explicitly allowed.
//-----------------------------------------------------------------------------
size_t V_URLDecode(char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen)
{
	return V_URLDecodeInternal(pchDecodeDest, nDecodeDestLen, pchEncodedSource, nEncodedSourceLen, true);
}

//-----------------------------------------------------------------------------
void V_StripExtension(std::string &in)
{
	// Find the last dot. If it's followed by a dot or a slash, then it's part of a
	// directory specifier like ../../somedir/./blah.
	std::string::size_type test = in.rfind('.');
	if (test != std::string::npos)
	{
		// This handles things like ".\blah" or "c:\my@email.com\abc\def\geh"
		// Which would otherwise wind up with "" and "c:\my@email", respectively.
		if (in.rfind('\\') < test && in.rfind('/') < test)
		{
			in.resize(test);
		}
	}
}
