#include <filezilla.h>
#include <gnutls/gnutls.h>

bool VerifySetDate(wxDateTime& date, int year, wxDateTime::Month month, int day, int hour /*=0*/, int minute /*=0*/, int second /*=0*/)
{
	if (year < 1900 || year > 3000)
		return false;

	if (month < 0 || month >= 12)
		return false;

	int maxDays = wxDateTime::GetNumberOfDays(month, year);
	if (day > maxDays)
		return false;

	date.Set(day, month, year, hour, minute, second);

	return date.IsValid();
}

wxString GetIPV6LongForm(wxString short_address)
{
	if (short_address[0] == '[')
	{
		if (short_address.Last() != ']')
			return _T("");
		short_address.RemoveLast();
		short_address = short_address.Mid(1);
	}
	short_address.MakeLower();

	wxChar buffer[40] = { '0', '0', '0', '0', ':',
						  '0', '0', '0', '0', ':',
						  '0', '0', '0', '0', ':',
						  '0', '0', '0', '0', ':',
						  '0', '0', '0', '0', ':',
						  '0', '0', '0', '0', ':',
						  '0', '0', '0', '0', ':',
						  '0', '0', '0', '0', 0
						};
	wxChar* out = buffer;

	const unsigned int len = short_address.Len();
	if (len > 39)
		return _T("");

	// First part, before possible ::
	unsigned int i = 0;
	unsigned int grouplength = 0;
	for (i = 0; i < len + 1; i++)
	{
		const wxChar& c = short_address[i];
		if (c == ':' || !c)
		{
			if (!grouplength)
			{
				// Empty group length, not valid
				if (!c || short_address[i + 1] != ':')
					return _T("");
				i++;
				break;
			}

			out += 4 - grouplength;
			for (unsigned int j = grouplength; j > 0; j--)
				*out++ = short_address[i - j];
			// End of string...
			if (!c)
			{
				if (!*out)
					// ...on time
					return buffer;
				else
					// ...premature
					return _T("");
			}
			else if (!*out)
			{
				// Too long
				return _T("");
			}

			out++;

			grouplength = 0;
			if (short_address[i + 1] == ':')
			{
				i++;
				break;
			}
			continue;
		}
		else if ((c < '0' || c > '9') &&
				 (c < 'a' || c > 'f'))
		{
			// Invalid character
			return _T("");
		}
		// Too long group
		if (++grouplength > 4)
			return _T("");
	}

	// Second half after ::

	wxChar* end_first = out;
	out = &buffer[38];
	unsigned int stop = i;
	for (i = len - 1; i > stop; i--)
	{
		if (out < end_first)
		{
			// Too long
			return _T("");
		}

		const wxChar& c = short_address[i];
		if (c == ':')
		{
			if (!grouplength)
			{
				// Empty group length, not valid
				return _T("");
			}

			out -= 5 - grouplength;

			grouplength = 0;
			continue;
		}
		else if ((c < '0' || c > '9') &&
				 (c < 'a' || c > 'f'))
		{
			// Invalid character
			return _T("");
		}
		// Too long group
		if (++grouplength > 4)
			return _T("");
		*out-- = c;
	}
	if (!grouplength)
	{
		// Empty group length, not valid
		return _T("");
	}
	out -= 5 - grouplength;
	out += 2;

	int diff = out - end_first;
	if (diff < 0 || diff % 5)
		return _T("");

	return buffer;
}

int DigitHexToDecNum(wxChar c)
{
	if (c >= 'a')
		return c - 'a' + 10;
	if (c >= 'A')
		return c - 'A' + 10;
	else
		return c - '0';
}

bool IsRoutableAddress(const wxString& address, enum CSocket::address_family family)
{
	if (family == CSocket::ipv6)
	{
		wxString long_address = GetIPV6LongForm(address);
		if (long_address.empty())
			return false;
		if (long_address[0] == '0')
		{
			// ::/128
			if (long_address == _T("0000:0000:0000:0000:0000:0000:0000:0000"))
				return false;
			// ::1/128
			if (long_address == _T("0000:0000:0000:0000:0000:0000:0000:0001"))
				return false;

			if (long_address.Left(30) == _T("0000:0000:0000:0000:0000:ffff:"))
			{
				// IPv4 mapped
				wxString ipv4 = wxString::Format(_T("%d.%d.%d.%d"),
						DigitHexToDecNum(long_address[30]) * 16 + DigitHexToDecNum(long_address[31]),
						DigitHexToDecNum(long_address[32]) * 16 + DigitHexToDecNum(long_address[33]),
						DigitHexToDecNum(long_address[35]) * 16 + DigitHexToDecNum(long_address[36]),
						DigitHexToDecNum(long_address[37]) * 16 + DigitHexToDecNum(long_address[38]));
				return IsRoutableAddress(ipv4, CSocket::ipv4);
			}

			return true;
		}
		if (long_address[0] == 'f')
		{
			if (long_address[1] == 'e')
			{
				// fe80::/10 (link local)
				const wxChar& c = long_address[2];
				int v;
				if (c >= 'a')
					v = c - 'a' + 10;
				else
					v = c - '0';
				if ((v & 0xc) == 0x8)
					return false;

				return true;
			}
			else if (long_address[1] == 'c' || long_address[1] == 'd')
			{
				// fc00::/7 (site local)
				return false;
			}
		}

		return true;
	}
	else
	{
		// Assumes address is already a valid IP address
		if (address.Left(3) == _T("127") ||
			address.Left(3) == _T("10.") ||
			address.Left(7) == _T("192.168") ||
			address.Left(7) == _T("169.254"))
			return false;

		if (address.Left(3) == _T("172"))
		{
			wxString middle = address.Mid(4);
			int pos = address.Find(_T("."));
			wxASSERT(pos != -1);
			long part;
			middle.Left(pos).ToLong(&part);

			if (part >= 16 && part <= 31)
				return false;
		}

		return true;
	}
}

bool IsIpAddress(const wxString& address)
{
	if (GetIPV6LongForm(address) != _T(""))
		return true;

	int segment = 0;
	int dotcount = 0;
	for (size_t i = 0; i < address.Len(); i++)
	{
		const char& c = address[i];
		if (c == '.')
		{
			if (address[i + 1] == '.')
				// Disallow multiple dots in a row
				return false;

			if (segment > 255)
				return false;
			if (!dotcount && !segment)
				return false;
			dotcount++;
			segment = 0;
		}
		else if (c < '0' || c > '9')
			return false;

		segment = segment * 10 + c - '0';
	}
	if (dotcount != 3)
		return false;

	if (segment > 255)
		return false;

	return true;
}

// RAND_MAX is different depending on platform.
// For example on MSW, it is just 0x7fff, not even enough to cover
// a full range of tcp ports.
// On Linux it is identical to INT_MAX.
//
// The following code can generate random numbers using rand() for any
// RAND_MAX.
int GetRandomNumber(int low, int high)
{
	wxASSERT(low <= high);
	if (low == high)
		return low;

	const int range = high - low;
	if (RAND_MAX < range)
	{
		// Calculate number of random bits rand() returns
		int r = RAND_MAX;
		unsigned int random_bits = 0;
		bool zero = false;
		while (r)
		{
			if (!r & 1)
				zero = true;
			r >>= 1;
			random_bits++;
		}
		if (zero)
			random_bits--;

		// Calculate number of bits in the range
		unsigned int maxshift = 0;
		r = range;
		while (r)
		{
			r >>= 1;
			maxshift++;
		}

		unsigned int s = 0;
		do
		{
			unsigned int shift_remaining = maxshift;
			while (shift_remaining)
			{
				int m;
				if (shift_remaining >= random_bits)
				{
					m = (1 << random_bits) - 1;
					s <<= random_bits;
					s += (unsigned int)GetRandomNumber(0, m);
					shift_remaining -= random_bits;
				}
				else
				{
					m = (1 << shift_remaining) - 1;
					s <<= shift_remaining;
					s += (unsigned int)GetRandomNumber(0, m);
					shift_remaining = 0;
				}
			}
		} while (s > (unsigned int)range);

		return s;
	}
	else if (range == RAND_MAX)
		return low + rand();
	else
	{
		const unsigned int max = ((((unsigned int)RAND_MAX) + 1) / (range + 1)) * (range + 1);
		unsigned int r;
		do {
			r = (unsigned int)rand();
		} while (r >= max);
		return (int)((r % (range + 1)) + low);
	}
}

void MakeLowerAscii(wxString& str)
{
	for (size_t i = 0; i < str.Len(); i++)
	{
		wxChar& c = str[i];
		if (c >= 'A' && c <= 'Z')
			c += 32;
	}
}

wxString GetDependencyVersion(enum _dependency dependency)
{
	if (dependency == dependency_gnutls)
	{
		const char* v = gnutls_check_version(0);
		if (!v || !*v)
			return _T("unknown");
	
		return wxString(v, wxConvLibc);	
	}

	return _T("No such dependency");
}
