/***********************************************************************
THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY
DEVELOPER: Zihan Chen(vczh)
***********************************************************************/
#include "VlppOS.Windows.h"

/***********************************************************************
.\FILESYSTEM.WINDOWS.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/

#define _WINSOCKAPI_
#include <Shlwapi.h>

#ifndef VCZH_MSVC
static_assert(false, "Do not build this file for non-Windows applications.");
#endif

#pragma comment(lib, "Shlwapi.lib")

namespace vl
{
	namespace stream
	{
		extern Ptr<IFileStreamImpl>		CreateOSFileStreamImpl(const WString& fileName, FileStream::AccessRight accessRight);
	}

	namespace filesystem
	{
		using namespace collections;
		using namespace stream;

/***********************************************************************
WindowsFileSystemImpl
***********************************************************************/

		class WindowsFileSystemImpl : public feature_injection::FeatureImpl<IFileSystemImpl>
		{
		public:
			wchar_t GetPathDelimiter() const override
			{
				return L'\\';
			}

			const wchar_t* GetCompatibleDelimiters() const override
			{
				return L"/";
			}

			WString ConcatPath(const WString& fullPath, const WString& relativePath) const override
			{
				if (IsRoot(fullPath))
				{
					return relativePath;
				}

				return fullPath + WString::FromChar(GetPathDelimiter()) + relativePath;
			}

			void Initialize(WString& fullPath) const override
			{
				{
					Array<wchar_t> buffer(fullPath.Length() + 1);
					wcscpy_s(&buffer[0], fullPath.Length() + 1, fullPath.Buffer());
					FilePath::NormalizeDelimiters(buffer);
					fullPath = &buffer[0];
				}

				if (fullPath != L"")
				{
					if (fullPath.Length() < 2 || fullPath[1] != L':')
					{
						wchar_t buffer[MAX_PATH + 1] = { 0 };
						auto result = GetCurrentDirectory(sizeof(buffer) / sizeof(*buffer), buffer);
						if (result > MAX_PATH + 1 || result == 0)
						{
							throw ArgumentException(L"Failed to call GetCurrentDirectory.", L"vl::filesystem::FilePath::Initialize", L"");
						}
						fullPath = WString(buffer) + L"\\" + fullPath;
					}
					{
						wchar_t buffer[MAX_PATH + 1] = { 0 };
						if (fullPath.Length() == 2 && fullPath[1] == L':')
						{
							fullPath += L"\\";
						}
						auto result = GetFullPathName(fullPath.Buffer(), sizeof(buffer) / sizeof(*buffer), buffer, NULL);
						if (result > MAX_PATH + 1 || result == 0)
						{
							throw ArgumentException(L"The path is illegal.", L"vl::filesystem::FilePath::FilePath", L"_filePath");
						}

						{
							wchar_t shortPath[MAX_PATH + 1];
							wchar_t longPath[MAX_PATH + 1];
							if (GetShortPathName(buffer, shortPath, MAX_PATH) > 0)
							{
								if (GetLongPathName(shortPath, longPath, MAX_PATH) > 0)
								{
									memcpy(buffer, longPath, sizeof(buffer));
								}
							}
						}
						fullPath = buffer;
					}
				}

				FilePath::TrimLastDelimiter(fullPath);
			}

			bool IsFile(const WString& fullPath) const override
			{
				WIN32_FILE_ATTRIBUTE_DATA info;
				BOOL result = GetFileAttributesEx(fullPath.Buffer(), GetFileExInfoStandard, &info);
				if (!result) return false;
				return (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
			}

			bool IsFolder(const WString& fullPath) const override
			{
				WIN32_FILE_ATTRIBUTE_DATA info;
				BOOL result = GetFileAttributesEx(fullPath.Buffer(), GetFileExInfoStandard, &info);
				if (!result) return false;
				return (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
			}

			bool IsRoot(const WString& fullPath) const override
			{
				return fullPath == L"";
			}

			WString GetRelativePathFor(const WString& fromPath, const WString& toPath) const override
			{
				if (fromPath.Length() == 0 || toPath.Length() == 0 || fromPath[0] != toPath[0])
				{
					return toPath;
				}

				wchar_t buffer[MAX_PATH + 1] = { 0 };
				PathRelativePathTo(
					buffer,
					fromPath.Buffer(),
					(IsFolder(fromPath) ? FILE_ATTRIBUTE_DIRECTORY : 0),
					toPath.Buffer(),
					(IsFolder(toPath) ? FILE_ATTRIBUTE_DIRECTORY : 0)
				);
				return buffer;
			}

			bool FileDelete(const FilePath& filePath) const override
			{
				return DeleteFile(filePath.GetFullPath().Buffer()) != 0;
			}

			bool FileRename(const FilePath& filePath, const WString& newName) const override
			{
				WString oldFileName = filePath.GetFullPath();
				WString newFileName = (filePath.GetFolder() / newName).GetFullPath();
				return MoveFile(oldFileName.Buffer(), newFileName.Buffer()) != 0;
			}

			bool GetFolders(const FilePath& folderPath, collections::List<Folder>& folders) const override
			{
				if (folderPath.IsRoot())
				{
					auto bufferSize = GetLogicalDriveStrings(0, nullptr);
					if (bufferSize > 0)
					{
						Array<wchar_t> buffer(bufferSize);
						if (GetLogicalDriveStrings((DWORD)buffer.Count(), &buffer[0]) > 0)
						{
							auto begin = &buffer[0];
							auto end = begin + buffer.Count();
							while (begin < end && *begin)
							{
								WString driveString = begin;
								begin += driveString.Length() + 1;
								folders.Add(Folder(FilePath(driveString)));
							}
							return true;
						}
					}
					return false;
				}
				else
				{
					if (!IsFolder(folderPath.GetFullPath())) return false;
					WIN32_FIND_DATA findData;
					HANDLE findHandle = INVALID_HANDLE_VALUE;

					while (true)
					{
						if (findHandle == INVALID_HANDLE_VALUE)
						{
							WString searchPath = (folderPath / L"*").GetFullPath();
							findHandle = FindFirstFile(searchPath.Buffer(), &findData);
							if (findHandle == INVALID_HANDLE_VALUE)
							{
								break;
							}
						}
						else
						{
							BOOL result = FindNextFile(findHandle, &findData);
							if (result == 0)
							{
								FindClose(findHandle);
								break;
							}
						}

						if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
						{
							if (wcscmp(findData.cFileName, L".") != 0 && wcscmp(findData.cFileName, L"..") != 0)
							{
								folders.Add(Folder(folderPath / findData.cFileName));
							}
						}
					}
					return true;
				}
			}

			bool GetFiles(const FilePath& folderPath, collections::List<File>& files) const override
			{
				if (IsRoot(folderPath.GetFullPath()))
				{
					return true;
				}
				if (!IsFolder(folderPath.GetFullPath())) return false;
				WIN32_FIND_DATA findData;
				HANDLE findHandle = INVALID_HANDLE_VALUE;

				while (true)
				{
					if (findHandle == INVALID_HANDLE_VALUE)
					{
						WString searchPath = (folderPath / L"*").GetFullPath();
						findHandle = FindFirstFile(searchPath.Buffer(), &findData);
						if (findHandle == INVALID_HANDLE_VALUE)
						{
							break;
						}
					}
					else
					{
						BOOL result = FindNextFile(findHandle, &findData);
						if (result == 0)
						{
							FindClose(findHandle);
							break;
						}
					}

					if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
					{
						files.Add(File(folderPath / findData.cFileName));
					}
				}
				return true;
			}

			bool CreateFolder(const FilePath& folderPath) const override
			{
				return CreateDirectory(folderPath.GetFullPath().Buffer(), NULL) != 0;
			}

			bool DeleteFolder(const FilePath& folderPath) const override
			{
				return RemoveDirectory(folderPath.GetFullPath().Buffer()) != 0;
			}

			bool FolderRename(const FilePath& folderPath, const WString& newName) const override
			{
				WString oldFileName = folderPath.GetFullPath();
				WString newFileName = (folderPath.GetFolder() / newName).GetFullPath();
				return MoveFile(oldFileName.Buffer(), newFileName.Buffer()) != 0;
			}

			Ptr<stream::IFileStreamImpl> GetFileStreamImpl(const WString& fileName, stream::FileStream::AccessRight accessRight) const override
			{
				return stream::CreateOSFileStreamImpl(fileName, accessRight);
			}
		};

		IFileSystemImpl* GetOSFileSystemImpl()
		{
			static WindowsFileSystemImpl osFileSystemImpl;
			return &osFileSystemImpl;
		}
	}
}


/***********************************************************************
.\LOCALE.WINDOWS.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/

#define _WINSOCKAPI_

#ifndef VCZH_MSVC
static_assert(false, "Do not build this file for non-Windows applications.");
#endif

namespace vl
{
	using namespace collections;

/***********************************************************************
Locale Helper Functions
***********************************************************************/

	SYSTEMTIME DateTimeToSystemTime(const DateTime& dateTime)
	{
		SYSTEMTIME systemTime;
		systemTime.wYear = (WORD)dateTime.year;
		systemTime.wMonth = (WORD)dateTime.month;
		systemTime.wDayOfWeek = (WORD)dateTime.dayOfWeek;
		systemTime.wDay = (WORD)dateTime.day;
		systemTime.wHour = (WORD)dateTime.hour;
		systemTime.wMinute = (WORD)dateTime.minute;
		systemTime.wSecond = (WORD)dateTime.second;
		systemTime.wMilliseconds = (WORD)dateTime.milliseconds;
		return systemTime;
	}

	BOOL CALLBACK Locale_EnumLocalesProcEx(
		_In_  LPWSTR lpLocaleString,
		_In_  DWORD dwFlags,
		_In_  LPARAM lParam
	)
	{
		((List<Locale>*)lParam)->Add(Locale(lpLocaleString));
		return TRUE;
	}

	BOOL CALLBACK Locale_EnumDateFormatsProcExEx(
		_In_  LPWSTR lpDateFormatString,
		_In_  CALID CalendarID,
		_In_  LPARAM lParam
	)
	{
		((List<WString>*)lParam)->Add(lpDateFormatString);
		return TRUE;
	}

	BOOL CALLBACK EnumTimeFormatsProcEx(
		_In_  LPWSTR lpTimeFormatString,
		_In_  LPARAM lParam
	)
	{
		((List<WString>*)lParam)->Add(lpTimeFormatString);
		return TRUE;
	}

	WString Transform(const WString& localeName, const WString& input, DWORD flag)
	{
		int length = LCMapStringEx(localeName.Buffer(), flag, input.Buffer(), (int)input.Length() + 1, NULL, 0, NULL, NULL, NULL);
		Array<wchar_t> buffer(length);
		LCMapStringEx(localeName.Buffer(), flag, input.Buffer(), (int)input.Length() + 1, &buffer[0], (int)buffer.Count(), NULL, NULL, NULL);
		return &buffer[0];
	}

	DWORD TranslateNormalization(Locale::Normalization normalization)
	{
		DWORD result = 0;
		if (normalization & Locale::IgnoreCase) result |= NORM_IGNORECASE;
		if (normalization & Locale::IgnoreCaseLinguistic) result |= NORM_IGNORECASE | NORM_LINGUISTIC_CASING;
		if (normalization & Locale::IgnoreKanaType) result |= NORM_IGNOREKANATYPE;
		if (normalization & Locale::IgnoreNonSpace) result |= NORM_IGNORENONSPACE;
		if (normalization & Locale::IgnoreSymbol) result |= NORM_IGNORESYMBOLS;
		if (normalization & Locale::IgnoreWidth) result |= NORM_IGNOREWIDTH;
		if (normalization & Locale::DigitsAsNumbers) result |= SORT_DIGITSASNUMBERS;
		if (normalization & Locale::StringSoft) result |= SORT_STRINGSORT;
		return result;
	}

/***********************************************************************
WindowsLocaleImpl
***********************************************************************/

	class WindowsLocaleImpl : public feature_injection::FeatureImpl<ILocaleImpl>
	{
	public:
		Locale Invariant() const override
		{
			return Locale(LOCALE_NAME_INVARIANT);
		}

		Locale SystemDefault() const override
		{
			wchar_t buffer[LOCALE_NAME_MAX_LENGTH + 1] = { 0 };
			GetSystemDefaultLocaleName(buffer, LOCALE_NAME_MAX_LENGTH);
			return Locale(buffer);
		}

		Locale UserDefault() const override
		{
			wchar_t buffer[LOCALE_NAME_MAX_LENGTH + 1] = { 0 };
			GetUserDefaultLocaleName(buffer, LOCALE_NAME_MAX_LENGTH);
			return Locale(buffer);
		}

		void Enumerate(collections::List<Locale>& locales) const override
		{
			EnumSystemLocalesEx(&Locale_EnumLocalesProcEx, LOCALE_ALL, (LPARAM)&locales, NULL);
		}

		void GetShortDateFormats(const WString& localeName, collections::List<WString>& formats) const override
		{
			EnumDateFormatsExEx(&Locale_EnumDateFormatsProcExEx, localeName.Buffer(), DATE_SHORTDATE, (LPARAM)&formats);
		}

		void GetLongDateFormats(const WString& localeName, collections::List<WString>& formats) const override
		{
			EnumDateFormatsExEx(&Locale_EnumDateFormatsProcExEx, localeName.Buffer(), DATE_LONGDATE, (LPARAM)&formats);
		}

		void GetYearMonthDateFormats(const WString& localeName, collections::List<WString>& formats) const override
		{
			EnumDateFormatsExEx(&Locale_EnumDateFormatsProcExEx, localeName.Buffer(), DATE_YEARMONTH, (LPARAM)&formats);
		}

		void GetLongTimeFormats(const WString& localeName, collections::List<WString>& formats) const override
		{
			EnumTimeFormatsEx(&EnumTimeFormatsProcEx, localeName.Buffer(), 0, (LPARAM)&formats);
		}

		void GetShortTimeFormats(const WString& localeName, collections::List<WString>& formats) const override
		{
			EnumTimeFormatsEx(&EnumTimeFormatsProcEx, localeName.Buffer(), TIME_NOSECONDS, (LPARAM)&formats);
		}

		WString FormatDate(const WString& localeName, const WString& format, DateTime date) const override
		{
			SYSTEMTIME st = DateTimeToSystemTime(date);
			int length = GetDateFormatEx(localeName.Buffer(), 0, &st, format.Buffer(), NULL, 0, NULL);
			if (length == 0) return L"";
			Array<wchar_t> buffer(length);
			GetDateFormatEx(localeName.Buffer(), 0, &st, format.Buffer(), &buffer[0], (int)buffer.Count(), NULL);
			return &buffer[0];
		}

		WString FormatTime(const WString& localeName, const WString& format, DateTime time) const override
		{
			SYSTEMTIME st = DateTimeToSystemTime(time);
			int length = GetTimeFormatEx(localeName.Buffer(), 0, &st, format.Buffer(), NULL, 0);
			if (length == 0) return L"";
			Array<wchar_t> buffer(length);
			GetTimeFormatEx(localeName.Buffer(), 0, &st, format.Buffer(), &buffer[0], (int)buffer.Count());
			return &buffer[0];
		}

		WString FormatNumber(const WString& localeName, const WString& number) const override
		{
			int length = GetNumberFormatEx(localeName.Buffer(), 0, number.Buffer(), NULL, NULL, 0);
			if (length == 0) return L"";
			Array<wchar_t> buffer(length);
			GetNumberFormatEx(localeName.Buffer(), 0, number.Buffer(), NULL, &buffer[0], (int)buffer.Count());
			return &buffer[0];
		}

		WString FormatCurrency(const WString& localeName, const WString& currency) const override
		{
			int length = GetCurrencyFormatEx(localeName.Buffer(), 0, currency.Buffer(), NULL, NULL, 0);
			if (length == 0) return L"";
			Array<wchar_t> buffer(length);
			GetCurrencyFormatEx(localeName.Buffer(), 0, currency.Buffer(), NULL, &buffer[0], (int)buffer.Count());
			return &buffer[0];
		}

		WString GetShortDayOfWeekName(const WString& localeName, vint dayOfWeek) const override
		{
			return FormatDate(localeName, L"ddd", DateTime::FromDateTime(2000, 1, 2 + dayOfWeek));
		}

		WString GetLongDayOfWeekName(const WString& localeName, vint dayOfWeek) const override
		{
			return FormatDate(localeName, L"dddd", DateTime::FromDateTime(2000, 1, 2 + dayOfWeek));
		}

		WString GetShortMonthName(const WString& localeName, vint month) const override
		{
			return FormatDate(localeName, L"MMM", DateTime::FromDateTime(2000, month, 1));
		}

		WString GetLongMonthName(const WString& localeName, vint month) const override
		{
			return FormatDate(localeName, L"MMMM", DateTime::FromDateTime(2000, month, 1));
		}

		WString ToLower(const WString& localeName, const WString& str) const override
		{
			return Transform(localeName, str, LCMAP_LOWERCASE);
		}

		WString ToUpper(const WString& localeName, const WString& str) const override
		{
			return Transform(localeName, str, LCMAP_UPPERCASE);
		}

		WString ToLinguisticLower(const WString& localeName, const WString& str) const override
		{
			return Transform(localeName, str, LCMAP_LOWERCASE | LCMAP_LINGUISTIC_CASING);
		}

		WString ToLinguisticUpper(const WString& localeName, const WString& str) const override
		{
			return Transform(localeName, str, LCMAP_UPPERCASE | LCMAP_LINGUISTIC_CASING);
		}

		vint Compare(const WString& localeName, const WString& s1, const WString& s2, Locale::Normalization normalization) const override
		{
			switch (CompareStringEx(localeName.Buffer(), TranslateNormalization(normalization), s1.Buffer(), (int)s1.Length(), s2.Buffer(), (int)s2.Length(), NULL, NULL, NULL))
			{
			case CSTR_LESS_THAN: return -1;
			case CSTR_GREATER_THAN: return 1;
			default: return 0;
			}
		}

		vint CompareOrdinal(const WString& s1, const WString& s2) const override
		{
			switch (CompareStringOrdinal(s1.Buffer(), (int)s1.Length(), s2.Buffer(), (int)s2.Length(), FALSE))
			{
			case CSTR_LESS_THAN: return -1;
			case CSTR_GREATER_THAN: return 1;
			default: return 0;
			}
		}

		vint CompareOrdinalIgnoreCase(const WString& s1, const WString& s2) const override
		{
			switch (CompareStringOrdinal(s1.Buffer(), (int)s1.Length(), s2.Buffer(), (int)s2.Length(), TRUE))
			{
			case CSTR_LESS_THAN: return -1;
			case CSTR_GREATER_THAN: return 1;
			default: return 0;
			}
		}

		collections::Pair<vint, vint> FindFirst(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const override
		{
			int length = 0;
			int result = FindNLSStringEx(localeName.Buffer(), FIND_FROMSTART | TranslateNormalization(normalization), text.Buffer(), (int)text.Length(), find.Buffer(), (int)find.Length(), &length, NULL, NULL, NULL);
			return result == -1 ? collections::Pair<vint, vint>(-1, 0) : collections::Pair<vint, vint>(result, length);
		}

		collections::Pair<vint, vint> FindLast(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const override
		{
			int length = 0;
			int result = FindNLSStringEx(localeName.Buffer(), FIND_FROMEND | TranslateNormalization(normalization), text.Buffer(), (int)text.Length(), find.Buffer(), (int)find.Length(), &length, NULL, NULL, NULL);
			return result == -1 ? collections::Pair<vint, vint>(-1, 0) : collections::Pair<vint, vint>(result, length);
		}

		bool StartsWith(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const override
		{
			int result = FindNLSStringEx(localeName.Buffer(), FIND_STARTSWITH | TranslateNormalization(normalization), text.Buffer(), (int)text.Length(), find.Buffer(), (int)find.Length(), NULL, NULL, NULL, NULL);
			return result != -1;
		}

		bool EndsWith(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const override
		{
			int result = FindNLSStringEx(localeName.Buffer(), FIND_ENDSWITH | TranslateNormalization(normalization), text.Buffer(), (int)text.Length(), find.Buffer(), (int)find.Length(), NULL, NULL, NULL, NULL);
			return result != -1;
		}
	};

	ILocaleImpl* GetOSLocaleImpl()
	{
		static WindowsLocaleImpl windowsLocaleImpl;
		return &windowsLocaleImpl;
	}

/***********************************************************************
Locale (Windows Specific)
***********************************************************************/

	WString Locale::ToFullWidth(const WString& str)const
	{
		return Transform(localeName, str, LCMAP_FULLWIDTH);
	}

	WString Locale::ToHalfWidth(const WString& str)const
	{
		return Transform(localeName, str, LCMAP_HALFWIDTH);
	}

	WString Locale::ToHiragana(const WString& str)const
	{
		return Transform(localeName, str, LCMAP_HIRAGANA);
	}

	WString Locale::ToKatagana(const WString& str)const
	{
		return Transform(localeName, str, LCMAP_KATAKANA);
	}

	WString Locale::ToSimplifiedChinese(const WString& str)const
	{
		return Transform(localeName, str, LCMAP_SIMPLIFIED_CHINESE);
	}

	WString Locale::ToTraditionalChinese(const WString& str)const
	{
		return Transform(localeName, str, LCMAP_TRADITIONAL_CHINESE);
	}

	WString Locale::ToTileCase(const WString& str)const
	{
		return Transform(localeName, str, LCMAP_TITLECASE);
	}
}


/***********************************************************************
.\THREADING.WINDOWS.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/

#define _WINSOCKAPI_

#ifndef VCZH_MSVC
static_assert(false, "Do not build this file for non-Windows applications.");
#endif

namespace vl
{
	using namespace threading_internal;
	using namespace collections;

/***********************************************************************
WaitableObject
***********************************************************************/

	namespace threading_internal
	{
		struct WaitableData
		{
			HANDLE			handle;

			WaitableData(HANDLE _handle)
				:handle(_handle)
			{
			}
		};
	}

	WaitableObject::WaitableObject()
		:waitableData(0)
	{
	}

	void WaitableObject::SetData(threading_internal::WaitableData* data)
	{
		waitableData=data;
	}

	bool WaitableObject::IsCreated()
	{
		return waitableData!=0;
	}

	bool WaitableObject::Wait()
	{
		return WaitForTime(INFINITE);
	}

	bool WaitableObject::WaitForTime(vint ms)
	{
		if(IsCreated())
		{
			if(WaitForSingleObject(waitableData->handle, (DWORD)ms)==WAIT_OBJECT_0)
			{
				return true;
			}
		}
		return false;
	}

	bool WaitableObject::WaitAll(WaitableObject** objects, vint count)
	{
		Array<HANDLE> handles(count);
		for(vint i=0;i<count;i++)
		{
			handles[i]=objects[i]->waitableData->handle;
		}
		DWORD result=WaitForMultipleObjects((DWORD)count, &handles[0], TRUE, INFINITE);
		return result==WAIT_OBJECT_0 || result==WAIT_ABANDONED_0;

	}

	bool WaitableObject::WaitAllForTime(WaitableObject** objects, vint count, vint ms)
	{
		Array<HANDLE> handles(count);
		for(vint i=0;i<count;i++)
		{
			handles[i]=objects[i]->waitableData->handle;
		}
		DWORD result=WaitForMultipleObjects((DWORD)count, &handles[0], TRUE, (DWORD)ms);
		return result==WAIT_OBJECT_0 || result==WAIT_ABANDONED_0;
	}

	vint WaitableObject::WaitAny(WaitableObject** objects, vint count, bool* abandoned)
	{
		Array<HANDLE> handles(count);
		for(vint i=0;i<count;i++)
		{
			handles[i]=objects[i]->waitableData->handle;
		}
		DWORD result=WaitForMultipleObjects((DWORD)count, &handles[0], FALSE, INFINITE);
		if(WAIT_OBJECT_0 <= result && result<WAIT_OBJECT_0+count)
		{
			*abandoned=false;
			return result-WAIT_OBJECT_0;
		}
		else if(WAIT_ABANDONED_0 <= result && result<WAIT_ABANDONED_0+count)
		{
			*abandoned=true;
			return result-WAIT_ABANDONED_0;
		}
		else
		{
			return -1;
		}
	}

	vint WaitableObject::WaitAnyForTime(WaitableObject** objects, vint count, vint ms, bool* abandoned)
	{
		Array<HANDLE> handles(count);
		for(vint i=0;i<count;i++)
		{
			handles[i]=objects[i]->waitableData->handle;
		}
		DWORD result=WaitForMultipleObjects((DWORD)count, &handles[0], FALSE, (DWORD)ms);
		if(WAIT_OBJECT_0 <= result && result<WAIT_OBJECT_0+count)
		{
			*abandoned=false;
			return result-WAIT_OBJECT_0;
		}
		else if(WAIT_ABANDONED_0 <= result && result<WAIT_ABANDONED_0+count)
		{
			*abandoned=true;
			return result-WAIT_ABANDONED_0;
		}
		else
		{
			return -1;
		}
	}

/***********************************************************************
Thread
***********************************************************************/

	namespace threading_internal
	{
		struct ThreadData : public WaitableData
		{
			DWORD						id;

			ThreadData()
				:WaitableData(NULL)
			{
				id=-1;
			}
		};

		class ProceduredThread : public Thread
		{
		private:
			Thread::ThreadProcedure		procedure;
			void*						argument;
			bool						deleteAfterStopped;

		protected:
			void Run()
			{
				bool deleteAfterStopped = this->deleteAfterStopped;
				ThreadLocalStorage::FixStorages();
				try
				{
					procedure(this, argument);
					threadState=Thread::Stopped;
					ThreadLocalStorage::ClearStorages();
				}
				catch (...)
				{
					ThreadLocalStorage::ClearStorages();
					throw;
				}
				if(deleteAfterStopped)
				{
					delete this;
				}
			}
		public:
			ProceduredThread(Thread::ThreadProcedure _procedure, void* _argument, bool _deleteAfterStopped)
				:procedure(_procedure)
				,argument(_argument)
				,deleteAfterStopped(_deleteAfterStopped)
			{
			}
		};

		class LambdaThread : public Thread
		{
		private:
			Func<void()>				procedure;
			bool						deleteAfterStopped;

		protected:
			void Run()
			{
				bool deleteAfterStopped = this->deleteAfterStopped;
				ThreadLocalStorage::FixStorages();
				try
				{
					procedure();
					threadState=Thread::Stopped;
					ThreadLocalStorage::ClearStorages();
				}
				catch (...)
				{
					ThreadLocalStorage::ClearStorages();
					throw;
				}
				if(deleteAfterStopped)
				{
					delete this;
				}
			}
		public:
			LambdaThread(const Func<void()>& _procedure, bool _deleteAfterStopped)
				:procedure(_procedure)
				,deleteAfterStopped(_deleteAfterStopped)
			{
			}
		};
	}

	void InternalThreadProc(Thread* thread)
	{
		thread->Run();
	}

	DWORD WINAPI InternalThreadProcWrapper(LPVOID lpParameter)
	{
		InternalThreadProc((Thread*)lpParameter);
		return 0;
	}

	Thread::Thread()
	{
		internalData=new ThreadData;
		internalData->handle=CreateThread(NULL, 0, InternalThreadProcWrapper, this, CREATE_SUSPENDED, &internalData->id);
		threadState=Thread::NotStarted;
		SetData(internalData);
	}

	Thread::~Thread()
	{
		if (internalData)
		{
			Stop();
			CloseHandle(internalData->handle);
			delete internalData;
		}
	}

	Thread* Thread::CreateAndStart(ThreadProcedure procedure, void* argument, bool deleteAfterStopped)
	{
		if(procedure)
		{
			Thread* thread=new ProceduredThread(procedure, argument, deleteAfterStopped);
			if(thread->Start())
			{
				return thread;
			}
			else
			{
				delete thread;
			}
		}
		return 0;
	}

	Thread* Thread::CreateAndStart(const Func<void()>& procedure, bool deleteAfterStopped)
	{
		Thread* thread=new LambdaThread(procedure, deleteAfterStopped);
		if(thread->Start())
		{
			return thread;
		}
		else
		{
			delete thread;
		}
		return 0;
	}

	void Thread::Sleep(vint ms)
	{
		::Sleep((DWORD)ms);
	}

	
	vint Thread::GetCPUCount()
	{
		SYSTEM_INFO info;
		GetSystemInfo(&info);
		return info.dwNumberOfProcessors;
	}

	vint Thread::GetCurrentThreadId()
	{
		return (vint)::GetCurrentThreadId();
	}

	bool Thread::Start()
	{
		if(threadState==Thread::NotStarted && internalData->handle!=NULL)
		{
			if(ResumeThread(internalData->handle)!=-1)
			{
				threadState=Thread::Running;
				return true;
			}
		}
		return false;
	}

	bool Thread::Stop()
	{
		if(internalData->handle!=NULL)
		{
			if (SuspendThread(internalData->handle) != -1)
			{
				threadState=Thread::Stopped;
				return true;
			}
		}
		return false;
	}

	Thread::ThreadState Thread::GetState()
	{
		return threadState;
	}

	void Thread::SetCPU(vint index)
	{
		SetThreadAffinityMask(internalData->handle, ((vint)1 << index));
	}

/***********************************************************************
Mutex
***********************************************************************/

	namespace threading_internal
	{
		struct MutexData : public WaitableData
		{
			MutexData(HANDLE _handle)
				:WaitableData(_handle)
			{
			}
		};
	}

	Mutex::Mutex()
		:internalData(0)
	{
	}

	Mutex::~Mutex()
	{
		if(internalData)
		{
			CloseHandle(internalData->handle);
			delete internalData;
		}
	}

	bool Mutex::Create(bool owned, const WString& name)
	{
		if(IsCreated())return false;
		BOOL aOwned=owned?TRUE:FALSE;
		LPCTSTR aName=name==L""?NULL:name.Buffer();
		HANDLE handle=CreateMutex(NULL, aOwned, aName);
		if(handle)
		{
			internalData=new MutexData(handle);
			SetData(internalData);
		}
		return IsCreated();
	}

	bool Mutex::Open(bool inheritable, const WString& name)
	{
		if(IsCreated())return false;
		BOOL aInteritable=inheritable?TRUE:FALSE;
		HANDLE handle=OpenMutex(SYNCHRONIZE, aInteritable, name.Buffer());
		if(handle)
		{
			internalData=new MutexData(handle);
			SetData(internalData);
		}
		return IsCreated();
	}

	bool Mutex::Release()
	{
		if(IsCreated())
		{
			return ReleaseMutex(internalData->handle)!=0;
		}
		return false;
	}

/***********************************************************************
Semaphore
***********************************************************************/

	namespace threading_internal
	{
		struct SemaphoreData : public WaitableData
		{
			SemaphoreData(HANDLE _handle)
				:WaitableData(_handle)
			{
			}
		};
	}

	Semaphore::Semaphore()
		:internalData(0)
	{
	}

	Semaphore::~Semaphore()
	{
		if(internalData)
		{
			CloseHandle(internalData->handle);
			delete internalData;
		}
	}

	bool Semaphore::Create(vint initialCount, vint maxCount, const WString& name)
	{
		if(IsCreated())return false;
		LONG aInitial=(LONG)initialCount;
		LONG aMax=(LONG)maxCount;
		LPCTSTR aName=name==L""?NULL:name.Buffer();
		HANDLE handle=CreateSemaphore(NULL, aInitial, aMax, aName);
		if(handle)
		{
			internalData=new SemaphoreData(handle);
			SetData(internalData);
		}
		return IsCreated();
	}

	bool Semaphore::Open(bool inheritable, const WString& name)
	{
		if(IsCreated())return false;
		BOOL aInteritable=inheritable?TRUE:FALSE;
		HANDLE handle=OpenSemaphore(SYNCHRONIZE, aInteritable, name.Buffer());
		if(handle)
		{
			internalData=new SemaphoreData(handle);
			SetData(internalData);
		}
		return IsCreated();
	}

	bool Semaphore::Release()
	{
		if(IsCreated())
		{
			return Release(1)!=-1;
		}
		return false;
	}

	vint Semaphore::Release(vint count)
	{
		if(IsCreated())
		{
			LONG previous=-1;
			if(ReleaseSemaphore(internalData->handle, (LONG)count, &previous)!=0)
			{
				return (vint)previous;
			}
		}
		return -1;
	}

/***********************************************************************
EventObject
***********************************************************************/

	namespace threading_internal
	{
		struct EventData : public WaitableData
		{
			EventData(HANDLE _handle)
				:WaitableData(_handle)
			{
			}
		};
	}

	EventObject::EventObject()
		:internalData(0)
	{
	}

	EventObject::~EventObject()
	{
		if(internalData)
		{
			CloseHandle(internalData->handle);
			delete internalData;
		}
	}

	bool EventObject::CreateAutoUnsignal(bool signaled, const WString& name)
	{
		if(IsCreated())return false;
		BOOL aSignaled=signaled?TRUE:FALSE;
		LPCTSTR aName=name==L""?NULL:name.Buffer();
		HANDLE handle=CreateEvent(NULL, FALSE, aSignaled, aName);
		if(handle)
		{
			internalData=new EventData(handle);
			SetData(internalData);
		}
		return IsCreated();
	}

	bool EventObject::CreateManualUnsignal(bool signaled, const WString& name)
	{
		if(IsCreated())return false;
		BOOL aSignaled=signaled?TRUE:FALSE;
		LPCTSTR aName=name==L""?NULL:name.Buffer();
		HANDLE handle=CreateEvent(NULL, TRUE, aSignaled, aName);
		if(handle)
		{
			internalData=new EventData(handle);
			SetData(internalData);
		}
		return IsCreated();
	}

	bool EventObject::Open(bool inheritable, const WString& name)
	{
		if(IsCreated())return false;
		BOOL aInteritable=inheritable?TRUE:FALSE;
		HANDLE handle=OpenEvent(SYNCHRONIZE, aInteritable, name.Buffer());
		if(handle)
		{
			internalData=new EventData(handle);
			SetData(internalData);
		}
		return IsCreated();
	}

	bool EventObject::Signal()
	{
		if(IsCreated())
		{
			return SetEvent(internalData->handle)!=0;
		}
		return false;
	}

	bool EventObject::Unsignal()
	{
		if(IsCreated())
		{
			return ResetEvent(internalData->handle)!=0;
		}
		return false;
	}

/***********************************************************************
ThreadPoolLite
***********************************************************************/

		DWORD WINAPI ThreadPoolQueueFunc(void* argument)
		{
			auto proc=Ptr((Func<void()>*)argument);
			ThreadLocalStorage::FixStorages();
			try
			{
				(*proc.Obj())();
				ThreadLocalStorage::ClearStorages();
			}
			catch (...)
			{
				ThreadLocalStorage::ClearStorages();
			}
			return 0;
		}

		ThreadPoolLite::ThreadPoolLite()
		{
		}

		ThreadPoolLite::~ThreadPoolLite()
		{
		}

		bool ThreadPoolLite::Queue(void(*proc)(void*), void* argument)
		{
			return Queue([=]() {proc(argument); });
		}

		bool ThreadPoolLite::Queue(const Func<void()>& proc)
		{
			Func<void()>* p=new Func<void()>(proc);
			if(QueueUserWorkItem(&ThreadPoolQueueFunc, p, WT_EXECUTEDEFAULT))
			{
				return true;
			}
			else
			{
				delete p;
				return false;
			}
		}

/***********************************************************************
CriticalSection
***********************************************************************/

	namespace threading_internal
	{
		struct CriticalSectionData
		{
			CRITICAL_SECTION		criticalSection;
		};
	}

	CriticalSection::Scope::Scope(CriticalSection& _criticalSection)
		:criticalSection(&_criticalSection)
	{
		criticalSection->Enter();
	}

	CriticalSection::Scope::~Scope()
	{
		criticalSection->Leave();
	}
			
	CriticalSection::CriticalSection()
	{
		internalData=new CriticalSectionData;
		InitializeCriticalSection(&internalData->criticalSection);
	}

	CriticalSection::~CriticalSection()
	{
		DeleteCriticalSection(&internalData->criticalSection);
		delete internalData;
	}

	bool CriticalSection::TryEnter()
	{
		return TryEnterCriticalSection(&internalData->criticalSection)!=0;
	}

	void CriticalSection::Enter()
	{
		EnterCriticalSection(&internalData->criticalSection);
	}

	void CriticalSection::Leave()
	{
		LeaveCriticalSection(&internalData->criticalSection);
	}

/***********************************************************************
ReaderWriterLock
***********************************************************************/

	namespace threading_internal
	{
		struct ReaderWriterLockData
		{
			SRWLOCK			lock;
		};
	}

	ReaderWriterLock::ReaderScope::ReaderScope(ReaderWriterLock& _lock)
		:lock(&_lock)
	{
		lock->EnterReader();
	}

	ReaderWriterLock::ReaderScope::~ReaderScope()
	{
		lock->LeaveReader();
	}

	ReaderWriterLock::WriterScope::WriterScope(ReaderWriterLock& _lock)
		:lock(&_lock)
	{
		lock->EnterWriter();
	}

	ReaderWriterLock::WriterScope::~WriterScope()
	{
		lock->LeaveWriter();
	}

	ReaderWriterLock::ReaderWriterLock()
		:internalData(new threading_internal::ReaderWriterLockData)
	{
		InitializeSRWLock(&internalData->lock);
	}

	ReaderWriterLock::~ReaderWriterLock()
	{
		delete internalData;
	}

	bool ReaderWriterLock::TryEnterReader()
	{
		return TryAcquireSRWLockShared(&internalData->lock)!=0;
	}

	void ReaderWriterLock::EnterReader()
	{
		AcquireSRWLockShared(&internalData->lock);
	}

	void ReaderWriterLock::LeaveReader()
	{
		ReleaseSRWLockShared(&internalData->lock);
	}

	bool ReaderWriterLock::TryEnterWriter()
	{
		return TryAcquireSRWLockExclusive(&internalData->lock)!=0;
	}

	void ReaderWriterLock::EnterWriter()
	{
		AcquireSRWLockExclusive(&internalData->lock);
	}

	void ReaderWriterLock::LeaveWriter()
	{
		ReleaseSRWLockExclusive(&internalData->lock);
	}

/***********************************************************************
ConditionVariable
***********************************************************************/

	namespace threading_internal
	{
		struct ConditionVariableData
		{
			CONDITION_VARIABLE			variable;
		};
	}

	ConditionVariable::ConditionVariable()
		:internalData(new threading_internal::ConditionVariableData)
	{
		InitializeConditionVariable(&internalData->variable);
	}

	ConditionVariable::~ConditionVariable()
	{
		delete internalData;
	}

	bool ConditionVariable::SleepWith(CriticalSection& cs)
	{
		return SleepConditionVariableCS(&internalData->variable, &cs.internalData->criticalSection, INFINITE)!=0;
	}

	bool ConditionVariable::SleepWithForTime(CriticalSection& cs, vint ms)
	{
		return SleepConditionVariableCS(&internalData->variable, &cs.internalData->criticalSection, (DWORD)ms)!=0;
	}

	bool ConditionVariable::SleepWithReader(ReaderWriterLock& lock)
	{
		return SleepConditionVariableSRW(&internalData->variable, &lock.internalData->lock, INFINITE, CONDITION_VARIABLE_LOCKMODE_SHARED)!=0;
	}

	bool ConditionVariable::SleepWithReaderForTime(ReaderWriterLock& lock, vint ms)
	{
		return SleepConditionVariableSRW(&internalData->variable, &lock.internalData->lock, (DWORD)ms, CONDITION_VARIABLE_LOCKMODE_SHARED)!=0;
	}

	bool ConditionVariable::SleepWithWriter(ReaderWriterLock& lock)
	{
		return SleepConditionVariableSRW(&internalData->variable, &lock.internalData->lock, INFINITE, 0)!=0;
	}

	bool ConditionVariable::SleepWithWriterForTime(ReaderWriterLock& lock, vint ms)
	{
		return SleepConditionVariableSRW(&internalData->variable, &lock.internalData->lock, (DWORD)ms, 0)!=0;
	}

	void ConditionVariable::WakeOnePending()
	{
		WakeConditionVariable(&internalData->variable);
	}

	void ConditionVariable::WakeAllPendings()
	{
		WakeAllConditionVariable(&internalData->variable);
	}

/***********************************************************************
ThreadLocalStorage
***********************************************************************/

#define KEY ((DWORD&)key)

	ThreadLocalStorage::ThreadLocalStorage(Destructor _destructor)
		:destructor(_destructor)
	{
		static_assert(sizeof(key) >= sizeof(DWORD), "ThreadLocalStorage's key storage is not large enouth.");
		PushStorage(this);
		KEY = TlsAlloc();
		CHECK_ERROR(KEY != TLS_OUT_OF_INDEXES, L"vl::ThreadLocalStorage::ThreadLocalStorage()#Failed to alloc new thread local storage index.");
	}

	ThreadLocalStorage::~ThreadLocalStorage()
	{
		TlsFree(KEY);
	}

	void* ThreadLocalStorage::Get()
	{
		CHECK_ERROR(!disposed, L"vl::ThreadLocalStorage::Get()#Cannot access a disposed ThreadLocalStorage.");
		return TlsGetValue(KEY);
	}

	void ThreadLocalStorage::Set(void* data)
	{
		CHECK_ERROR(!disposed, L"vl::ThreadLocalStorage::Set()#Cannot access a disposed ThreadLocalStorage.");
		TlsSetValue(KEY, data);
	}

#undef KEY
}


/***********************************************************************
.\ENCODING\CHARFORMAT\CHARFORMAT.WINDOWS.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/

#define _WINSOCKAPI_

#ifndef VCZH_MSVC
static_assert(false, "Do not build this file for non-Windows applications.");
#endif

namespace vl
{
	namespace stream
	{
		bool IsMbcsLeadByte(char c)
		{
			return IsDBCSLeadByte(c);
		}

		void MbcsToWChar(wchar_t* wideBuffer, vint wideChars, vint wideReaded, char* mbcsBuffer, vint mbcsChars)
		{
			MultiByteToWideChar(CP_THREAD_ACP, 0, mbcsBuffer, (int)mbcsChars, wideBuffer, (int)wideChars);
		}

/***********************************************************************
MbcsEncoder
***********************************************************************/

		vint MbcsEncoder::WriteString(wchar_t* _buffer, vint chars)
		{
			vint length = WideCharToMultiByte(CP_THREAD_ACP, 0, _buffer, (int)chars, NULL, NULL, NULL, NULL);
			char* mbcs = new char[length];
			WideCharToMultiByte(CP_THREAD_ACP, 0, _buffer, (int)chars, mbcs, (int)length, NULL, NULL);
			vint result = stream->Write(mbcs, length);
			delete[] mbcs;

			if (result != length)
			{
				Close();
				return 0;
			}
			return chars;
		}

/***********************************************************************
Helper Functions
***********************************************************************/

		extern bool CanBeMbcs(unsigned char* buffer, vint size);
		extern bool CanBeUtf8(unsigned char* buffer, vint size);
		extern bool CanBeUtf16(unsigned char* buffer, vint size, bool& hitSurrogatePairs);
		extern bool CanBeUtf16BE(unsigned char* buffer, vint size, bool& hitSurrogatePairs);

		template<vint Count>
		bool GetEncodingResult(int(&tests)[Count], bool(&results)[Count], int test)
		{
			for (vint i = 0; i < Count; i++)
			{
				if (tests[i] & test)
				{
					if (results[i]) return true;
				}
			}
			return false;
		}
		
/***********************************************************************
TestEncoding
***********************************************************************/

		extern void TestEncodingInternal(
			unsigned char* buffer,
			vint size,
			BomEncoder::Encoding& encoding,
			bool containsBom,
			bool utf16HitSurrogatePairs,
			bool utf16BEHitSurrogatePairs,
			bool roughMbcs,
			bool roughUtf8,
			bool roughUtf16,
			bool roughUtf16BE
		)
		{
			int tests[] =
			{
				IS_TEXT_UNICODE_REVERSE_ASCII16,
				IS_TEXT_UNICODE_REVERSE_STATISTICS,
				IS_TEXT_UNICODE_REVERSE_CONTROLS,

				IS_TEXT_UNICODE_ASCII16,
				IS_TEXT_UNICODE_STATISTICS,
				IS_TEXT_UNICODE_CONTROLS,

				IS_TEXT_UNICODE_ILLEGAL_CHARS,
				IS_TEXT_UNICODE_ODD_LENGTH,
				IS_TEXT_UNICODE_NULL_BYTES,
			};

			const vint TestCount = sizeof(tests) / sizeof(*tests);
			bool results[TestCount];
			for (vint i = 0; i < TestCount; i++)
			{
				int test = tests[i];
				results[i] = IsTextUnicode(buffer, (int)size, &test) != 0;
			}

			if (size % 2 == 0
				&& !GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_ASCII16)
				&& !GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_STATISTICS)
				&& !GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_CONTROLS)
				)
			{
				for (vint i = 0; i < size; i += 2)
				{
					unsigned char c = buffer[i];
					buffer[i] = buffer[i + 1];
					buffer[i + 1] = c;
				}
				// 3 = (count of reverse group) = (count of unicode group)
				for (vint i = 0; i < 3; i++)
				{
					int test = tests[i + 3];
					results[i] = IsTextUnicode(buffer, (int)size, &test) != 0;
				}
				for (vint i = 0; i < size; i += 2)
				{
					unsigned char c = buffer[i];
					buffer[i] = buffer[i + 1];
					buffer[i + 1] = c;
				}
			}

			if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_NOT_UNICODE_MASK))
			{
				if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_NOT_ASCII_MASK))
				{
					encoding = BomEncoder::Utf8;
				}
				else if (roughUtf8 || !roughMbcs)
				{
					encoding = BomEncoder::Utf8;
				}
			}
			else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_ASCII16))
			{
				encoding = BomEncoder::Utf16;
			}
			else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_ASCII16))
			{
				encoding = BomEncoder::Utf16BE;
			}
			else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_CONTROLS))
			{
				encoding = BomEncoder::Utf16;
			}
			else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_REVERSE_CONTROLS))
			{
				encoding = BomEncoder::Utf16BE;
			}
			else
			{
				if (!roughUtf8)
				{
					if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_STATISTICS))
					{
						encoding = BomEncoder::Utf16;
					}
					else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_STATISTICS))
					{
						encoding = BomEncoder::Utf16BE;
					}
				}
				else if (GetEncodingResult(tests, results, IS_TEXT_UNICODE_NOT_UNICODE_MASK))
				{
					encoding = BomEncoder::Utf8;
				}
				else if (roughUtf8 || !roughMbcs)
				{
					encoding = BomEncoder::Utf8;
				}
			}
		}
	}
}


/***********************************************************************
.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET.WINDOWS.CPP
***********************************************************************/

#pragma comment(lib, "Ws2_32.lib")

namespace vl::inter_process::async_tcp_socket::windows_socket
{
	using namespace collections;

	class IocpOperation;
	class IocpRuntime;
	class ConnectionState;
	class AsyncSocketConnection;

	struct NativeOverlapped
	{
		OVERLAPPED							overlapped;
		IocpOperation*					operation = nullptr;
	};

	struct CallbackFrame
	{
		ConnectionState*					connection = nullptr;
		CallbackFrame*						previous = nullptr;
	};

	static thread_local IocpRuntime* currentCompletionRuntime = nullptr;
	static thread_local IocpRuntime* currentCallbackRuntime = nullptr;
	static thread_local CallbackFrame* currentCallbackFrame = nullptr;

	WString SocketErrorMessage(const wchar_t* operation, DWORD error)
	{
		return WString::Unmanaged(operation) + L" failed with Windows error " + itow((vint)error) + L".";
	}

/***********************************************************************
IocpOperation
***********************************************************************/

	class IocpOperation
	{
	public:
		NativeOverlapped					native;

		IocpOperation()
		{
			ZeroMemory(&native.overlapped, sizeof(native.overlapped));
			native.operation = this;
		}

		virtual ~IocpOperation() = default;
		virtual bool Complete(DWORD bytes, DWORD error) = 0;
		virtual void EndPending() = 0;
	};

/***********************************************************************
IocpRuntime
***********************************************************************/

	class IocpRuntime : public Object
	{
	private:
		class CompletionWorker : public Thread
		{
		private:
			IocpRuntime*						runtime = nullptr;
		protected:
			void Run() override
			{
				currentCompletionRuntime = runtime;
				while (true)
				{
					DWORD bytes = 0;
					ULONG_PTR key = 0;
					OVERLAPPED* overlapped = nullptr;
					auto succeeded = GetQueuedCompletionStatus(runtime->iocp, &bytes, &key, &overlapped, INFINITE);
					if (!overlapped)
					{
						break;
					}

					auto native = CONTAINING_RECORD(overlapped, NativeOverlapped, overlapped);
					auto operation = native->operation;
					auto error = succeeded ? ERROR_SUCCESS : GetLastError();
					bool completed = true;
					try
					{
						completed = operation->Complete(bytes, error);
					}
					catch (...)
					{
						completed = true;
					}
					if (completed)
					{
						operation->EndPending();
						delete operation;
					}
				}
				currentCompletionRuntime = nullptr;
			}

		public:
			CompletionWorker(IocpRuntime* _runtime)
				: runtime(_runtime)
			{
			}
		};

		class CallbackWorker : public Thread
		{
		private:
			IocpRuntime*						runtime = nullptr;
		protected:
			void Run() override
			{
				currentCallbackRuntime = runtime;
				runtime->callbackQueue.RunTaskQueue();
				currentCallbackRuntime = nullptr;
			}

		public:
			CallbackWorker(IocpRuntime* _runtime)
				: runtime(_runtime)
			{
			}
		};

		HANDLE								iocp = nullptr;
		TaskQueue						callbackQueue;
		CompletionWorker*				completionWorker = nullptr;
		CallbackWorker*					callbackWorker = nullptr;
		SpinLock							lockStop;
		bool							stopRequested = false;
		bool							finalizing = false;
		bool							finalized = false;
		EventObject						eventFinalized;
		bool							winsockStarted = false;

	public:
		IocpRuntime()
		{
			CHECK_ERROR(eventFinalized.CreateManualUnsignal(false), L"IAsyncSocket failed to create its runtime drain event.");
			bool completionStarted = false;
			bool callbackStarted = false;
			try
			{
				WSADATA data;
				auto error = WSAStartup(MAKEWORD(2, 2), &data);
				CHECK_ERROR(error == 0, L"IAsyncSocket failed to initialize Winsock.");
				winsockStarted = true;

				iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1);
				CHECK_ERROR(iocp != nullptr, L"IAsyncSocket failed to create an IO completion port.");

				completionWorker = new CompletionWorker(this);
				callbackWorker = new CallbackWorker(this);
				completionStarted = completionWorker->Start();
				CHECK_ERROR(completionStarted, L"IAsyncSocket failed to start its completion worker.");
				callbackStarted = callbackWorker->Start();
				CHECK_ERROR(callbackStarted, L"IAsyncSocket failed to start its callback worker.");
			}
			catch (...)
			{
				if (callbackStarted)
				{
					callbackQueue.QueueExitTask();
					callbackWorker->Wait();
				}
				if (completionStarted)
				{
					PostQueuedCompletionStatus(iocp, 0, 0, nullptr);
					completionWorker->Wait();
				}
				delete callbackWorker;
				delete completionWorker;
				if (iocp)
				{
					CloseHandle(iocp);
				}
				if (winsockStarted)
				{
					WSACleanup();
				}
				throw;
			}
		}

		~IocpRuntime()
		{
			Stop();
		}

		bool Associate(SOCKET socket)
		{
			return CreateIoCompletionPort((HANDLE)socket, iocp, 0, 0) == iocp;
		}

		void QueueCallback(Func<void()> callback)
		{
			callbackQueue.QueueTask(callback);
		}

		void Stop()
		{
			bool requestExit = false;
			bool finalizeHere = false;
			bool waitForFinalization = false;
			auto selfWorker = currentCallbackRuntime == this || currentCompletionRuntime == this;
			SPIN_LOCK(lockStop)
			{
				if (finalized)
				{
					return;
				}
				if (!stopRequested)
				{
					stopRequested = true;
					requestExit = true;
				}
				if (!selfWorker)
				{
					if (!finalizing)
					{
						finalizing = true;
						finalizeHere = true;
					}
					else
					{
						waitForFinalization = true;
					}
				}
			}

			if (requestExit)
			{
				callbackQueue.QueueExitTask();
				PostQueuedCompletionStatus(iocp, 0, 0, nullptr);
			}
			if (selfWorker)
			{
				return;
			}
			if (waitForFinalization)
			{
				eventFinalized.Wait();
				return;
			}
			if (!finalizeHere)
			{
				return;
			}

			if (callbackWorker)
			{
				callbackWorker->Wait();
			}
			if (completionWorker)
			{
				completionWorker->Wait();
			}

			delete callbackWorker;
			callbackWorker = nullptr;
			delete completionWorker;
			completionWorker = nullptr;
			if (iocp)
			{
				CloseHandle(iocp);
				iocp = nullptr;
			}
			if (winsockStarted)
			{
				WSACleanup();
				winsockStarted = false;
			}
			SPIN_LOCK(lockStop)
			{
				finalized = true;
			}
			eventFinalized.Signal();
		}
	};

/***********************************************************************
ReadBlock
***********************************************************************/

	class ReadBlock : public Object
	{
	public:
		Array<vuint8_t>						data;

		ReadBlock()
		{
			data.Resize(65536);
		}
	};

/***********************************************************************
ConnectionState
***********************************************************************/

	class ConnectionState : public Object
	{
		friend class AsyncSocketConnection;
	private:
		class ReadOperation;
		class WriteOperation;
		class ConnectOperation;

		IocpRuntime*						runtime = nullptr;
		AsyncSocketConnection*				owner = nullptr;

		// covers every field below, pending counts, and their events
		CriticalSection					lockState;
		SOCKET							socket = INVALID_SOCKET;
		IAsyncSocketCallback*				callback = nullptr;
		bool							connected = false;
		bool							stopping = false;
		bool							stopped = false;
		bool							reading = false;
		bool							readPending = false;
		bool							writePending = false;
		bool							terminalPending = false;
		bool							disconnectedNotified = false;
		vint							pendingIo = 0;
		vint							activeCallbacks = 0;
		EventObject						eventIoDrained;
		EventObject						eventCallbacksDrained;

		bool							clientMode = false;
		vint							clientPort = 0;
		ClientStatus						clientStatus = ClientStatus::Ready;
		EventObject						eventWaitForServer;
		PTP_TIMER						clientRetryTimer = nullptr;
		vint							clientAttempts = 0;
		vint							clientGeneration = 0;

		void BeginPendingLocked()
		{
			if (pendingIo++ == 0)
			{
				eventIoDrained.Unsignal();
			}
		}

		void EndPendingLocked()
		{
			if (--pendingIo == 0)
			{
				eventIoDrained.Signal();
			}
		}

		void EndPending()
		{
			CS_LOCK(lockState)
			{
				EndPendingLocked();
			}
		}

		IAsyncSocketCallback* BeginCallback(bool terminal)
		{
			IAsyncSocketCallback* result = nullptr;
			CS_LOCK(lockState)
			{
				if (callback && (terminal || (!stopping && !terminalPending)))
				{
					result = callback;
					if (activeCallbacks++ == 0)
					{
						eventCallbacksDrained.Unsignal();
					}
				}
			}
			return result;
		}

		void EndCallback()
		{
			CS_LOCK(lockState)
			{
				if (--activeCallbacks == 0)
				{
					eventCallbacksDrained.Signal();
				}
			}
		}

		template<typename TCallback>
		bool InvokeCallback(bool terminal, TCallback&& invoke)
		{
			auto installed = BeginCallback(terminal);
			if (!installed)
			{
				return false;
			}

			CallbackFrame frame;
			frame.connection = this;
			frame.previous = currentCallbackFrame;
			currentCallbackFrame = &frame;
			try
			{
				invoke(installed);
			}
			catch (...)
			{
			}
			currentCallbackFrame = frame.previous;
			EndCallback();
			return true;
		}

		bool IsCurrentCallback()
		{
			for (auto frame = currentCallbackFrame; frame; frame = frame->previous)
			{
				if (frame->connection == this)
				{
					return true;
				}
			}
			return false;
		}

		void PostRead(Ptr<ConnectionState> retainedState);
		Ptr<ConnectionState> Retain();
		void DeliverRead(Ptr<ConnectionState> retainedState, Ptr<ReadBlock> block, vint bytes);
		void DeliverWrite(Ptr<AsyncSocketBuffer> buffer);
		void QueueTerminal(DWORD error, bool reportError);
		void DeliverTerminal(DWORD error, bool reportError);
		void StartConnectAttempt();
		void CompleteConnect(SOCKET operationSocket, vint generation, DWORD error);
		void QueueConnectFailure(DWORD error);
		void DeliverConnectFailure(DWORD error, bool fatal);
		void DeliverConnected();
		void ScheduleRetry();
		static VOID CALLBACK RetryTimerCallback(PTP_CALLBACK_INSTANCE, PVOID context, PTP_TIMER)
		{
			auto self = (ConnectionState*)context;
			self->StartConnectAttempt();
		}

	public:
		ConnectionState(IocpRuntime* _runtime, bool _clientMode, vint _clientPort)
			: runtime(_runtime)
			, clientMode(_clientMode)
			, clientPort(_clientPort)
		{
			CHECK_ERROR(eventIoDrained.CreateManualUnsignal(true), L"IAsyncSocket failed to create its I/O drain event.");
			CHECK_ERROR(eventCallbacksDrained.CreateManualUnsignal(true), L"IAsyncSocket failed to create its callback drain event.");
			CHECK_ERROR(eventWaitForServer.CreateManualUnsignal(false), L"IAsyncSocket failed to create its client wait event.");
			if (clientMode)
			{
				clientRetryTimer = CreateThreadpoolTimer(&RetryTimerCallback, this, nullptr);
				CHECK_ERROR(clientRetryTimer != nullptr, L"IAsyncSocket failed to create its retry timer.");
			}
		}

		ConnectionState(IocpRuntime* _runtime, SOCKET _socket)
			: runtime(_runtime)
			, socket(_socket)
			, connected(true)
		{
			CHECK_ERROR(eventIoDrained.CreateManualUnsignal(true), L"IAsyncSocket failed to create its I/O drain event.");
			CHECK_ERROR(eventCallbacksDrained.CreateManualUnsignal(true), L"IAsyncSocket failed to create its callback drain event.");
			CHECK_ERROR(eventWaitForServer.CreateManualUnsignal(false), L"IAsyncSocket failed to create its client wait event.");
		}

		~ConnectionState()
		{
			if (clientRetryTimer)
			{
				SetThreadpoolTimer(clientRetryTimer, nullptr, 0, 0);
				WaitForThreadpoolTimerCallbacks(clientRetryTimer, TRUE);
				CloseThreadpoolTimer(clientRetryTimer);
				clientRetryTimer = nullptr;
			}
		}

		void CloseRetryTimer()
		{
			PTP_TIMER timer = nullptr;
			CS_LOCK(lockState)
			{
				timer = clientRetryTimer;
				clientRetryTimer = nullptr;
			}
			if (timer)
			{
				SetThreadpoolTimer(timer, nullptr, 0, 0);
				WaitForThreadpoolTimerCallbacks(timer, TRUE);
				CloseThreadpoolTimer(timer);
			}
		}

		void InstallCallback(IAsyncSocketCallback* value);
		void BeginReading();
		void Write(Ptr<AsyncSocketBuffer> buffer);
		void Stop();
		void WaitForServer();
		ClientStatus GetStatus();
	};

/***********************************************************************
AsyncSocketConnection
***********************************************************************/

	class AsyncSocketConnection : public Object, public virtual IAsyncSocketConnection
	{
	private:
		Ptr<ConnectionState>					state;

	public:
		AsyncSocketConnection(Ptr<ConnectionState> _state)
			: state(_state)
		{
			state->owner = this;
		}

		~AsyncSocketConnection()
		{
			state->Stop();
			state->owner = nullptr;
		}

		Ptr<ConnectionState> GetState()
		{
			return state;
		}

		void InstallCallback(IAsyncSocketCallback* callback) override
		{
			state->InstallCallback(callback);
		}

		void BeginReadingLoopUnsafe() override
		{
			state->BeginReading();
		}

		void WriteAsync(Ptr<AsyncSocketBuffer> buffer) override
		{
			state->Write(buffer);
		}

		void Stop() override
		{
			state->Stop();
		}
	};

/***********************************************************************
ConnectionState::ReadOperation
***********************************************************************/

	class ConnectionState::ReadOperation : public IocpOperation
	{
	public:
		Ptr<ConnectionState>					connection;
		Ptr<ReadBlock>						block;

		ReadOperation(Ptr<ConnectionState> _connection)
			: connection(_connection)
			, block(Ptr(new ReadBlock))
		{
		}

		bool Complete(DWORD bytes, DWORD error) override
		{
			bool cancelled = false;
			CS_LOCK(connection->lockState)
			{
				connection->readPending = false;
				cancelled = connection->stopping;
			}
			if (cancelled)
			{
				return true;
			}
			if (error != ERROR_SUCCESS)
			{
				connection->QueueTerminal(error, true);
			}
			else if (bytes == 0)
			{
				connection->QueueTerminal(ERROR_SUCCESS, false);
			}
			else
			{
				auto state = connection;
				auto retainedBlock = block;
				connection->runtime->QueueCallback(Func<void()>([state, retainedBlock, bytes]()
				{
					state->DeliverRead(state, retainedBlock, (vint)bytes);
				}));
			}
			return true;
		}

		void EndPending() override
		{
			connection->EndPending();
		}
	};

/***********************************************************************
ConnectionState::WriteOperation
***********************************************************************/

	class ConnectionState::WriteOperation : public IocpOperation
	{
	public:
		Ptr<ConnectionState>					connection;
		Ptr<AsyncSocketBuffer>				buffer;
		vint								offset = 0;

		WriteOperation(Ptr<ConnectionState> _connection, Ptr<AsyncSocketBuffer> _buffer)
			: connection(_connection)
			, buffer(_buffer)
		{
		}

		DWORD PostLocked()
		{
			WSABUF nativeBuffer;
			nativeBuffer.buf = (CHAR*)&buffer->data[offset];
			nativeBuffer.len = (ULONG)(buffer->data.Count() - offset);
			DWORD sent = 0;
			auto result = WSASend(connection->socket, &nativeBuffer, 1, &sent, 0, &native.overlapped, nullptr);
			if (result == 0)
			{
				return ERROR_SUCCESS;
			}
			auto error = WSAGetLastError();
			return error == WSA_IO_PENDING ? ERROR_SUCCESS : (DWORD)error;
		}

		bool Complete(DWORD bytes, DWORD error) override
		{
			bool queueCompleted = false;
			DWORD terminalError = ERROR_SUCCESS;
			connection->lockState.Enter();
			if (connection->stopping)
			{
				connection->lockState.Leave();
				return true;
			}
			if (error != ERROR_SUCCESS || bytes == 0)
			{
				terminalError = error == ERROR_SUCCESS ? WSAECONNRESET : error;
				connection->lockState.Leave();
				connection->QueueTerminal(terminalError, true);
				return true;
			}

			offset += (vint)bytes;
			if (offset < buffer->data.Count())
			{
				ZeroMemory(&native.overlapped, sizeof(native.overlapped));
				auto postError = PostLocked();
				connection->lockState.Leave();
				if (postError == ERROR_SUCCESS)
				{
					return false;
				}
				connection->QueueTerminal(postError, true);
				return true;
			}
			queueCompleted = true;
			connection->lockState.Leave();

			if (queueCompleted)
			{
				auto state = connection;
				auto retainedBuffer = buffer;
				connection->runtime->QueueCallback(Func<void()>([state, retainedBuffer]()
				{
					state->DeliverWrite(retainedBuffer);
				}));
			}
			return true;
		}

		void EndPending() override
		{
			connection->EndPending();
		}
	};

/***********************************************************************
ConnectionState::ConnectOperation
***********************************************************************/

	class ConnectionState::ConnectOperation : public IocpOperation
	{
	public:
		Ptr<ConnectionState>					connection;
		SOCKET								operationSocket = INVALID_SOCKET;
		vint								generation = 0;

		ConnectOperation(Ptr<ConnectionState> _connection, SOCKET _operationSocket, vint _generation)
			: connection(_connection)
			, operationSocket(_operationSocket)
			, generation(_generation)
		{
		}

		bool Complete(DWORD, DWORD error) override
		{
			connection->CompleteConnect(operationSocket, generation, error);
			return true;
		}

		void EndPending() override
		{
			connection->EndPending();
		}
	};

/***********************************************************************
ConnectionState
***********************************************************************/

	Ptr<ConnectionState> ConnectionState::Retain()
	{
		CHECK_ERROR(owner != nullptr, L"IAsyncSocketConnection lost its canonical state owner.");
		return owner->GetState();
	}

	void ConnectionState::InstallCallback(IAsyncSocketCallback* value)
	{
		if (!value)
		{
			bool selfCallback = IsCurrentCallback();
			CS_LOCK(lockState)
			{
				callback = nullptr;
			}
			if (!selfCallback)
			{
				eventCallbacksDrained.Wait();
			}
			return;
		}

		bool canInstall = false;
		CS_LOCK(lockState)
		{
			canInstall = callback == nullptr && !stopping;
			if (canInstall)
			{
				callback = value;
				if (activeCallbacks++ == 0)
				{
					eventCallbacksDrained.Unsignal();
				}
			}
		}
		CHECK_ERROR(canInstall, L"IAsyncSocketConnection::InstallCallback cannot replace a callback or install one on a stopped connection.");

		CallbackFrame frame;
		frame.connection = this;
		frame.previous = currentCallbackFrame;
		currentCallbackFrame = &frame;
		try
		{
			value->OnInstalled(owner);
		}
		catch (...)
		{
		}
		currentCallbackFrame = frame.previous;
		EndCallback();
	}

	void ConnectionState::BeginReading()
	{
		CS_LOCK(lockState)
		{
			CHECK_ERROR(connected && !stopping && !terminalPending, L"IAsyncSocketConnection::BeginReadingLoopUnsafe requires a connected connection.");
			CHECK_ERROR(callback != nullptr, L"IAsyncSocketConnection::BeginReadingLoopUnsafe requires an installed callback.");
			CHECK_ERROR(!reading, L"IAsyncSocketConnection::BeginReadingLoopUnsafe can only be called once.");
			reading = true;
		}
		PostRead(Retain());
	}

	void ConnectionState::PostRead(Ptr<ConnectionState> retainedState)
	{
		auto operation = new ReadOperation(retainedState);
		DWORD immediateError = ERROR_SUCCESS;
		lockState.Enter();
		if (!connected || stopping || terminalPending || !reading || readPending)
		{
			lockState.Leave();
			delete operation;
			return;
		}

		readPending = true;
		BeginPendingLocked();
		WSABUF buffer;
		buffer.buf = (CHAR*)&operation->block->data[0];
		buffer.len = (ULONG)operation->block->data.Count();
		DWORD flags = 0;
		DWORD received = 0;
		auto result = WSARecv(socket, &buffer, 1, &received, &flags, &operation->native.overlapped, nullptr);
		if (result == SOCKET_ERROR)
		{
			auto error = WSAGetLastError();
			if (error != WSA_IO_PENDING)
			{
				immediateError = (DWORD)error;
				readPending = false;
				EndPendingLocked();
			}
		}
		lockState.Leave();

		if (immediateError != ERROR_SUCCESS)
		{
			delete operation;
			QueueTerminal(immediateError, true);
		}
	}

	void ConnectionState::DeliverRead(Ptr<ConnectionState> retainedState, Ptr<ReadBlock> block, vint bytes)
	{
		auto invoked = InvokeCallback(false, [&](IAsyncSocketCallback* installed)
		{
			installed->OnRead(&block->data[0], bytes);
		});
		if (invoked)
		{
			PostRead(retainedState);
		}
	}

	void ConnectionState::Write(Ptr<AsyncSocketBuffer> buffer)
	{
		CHECK_ERROR(buffer, L"IAsyncSocketConnection::WriteAsync requires a buffer.");
		bool empty = false;
		bool canWrite = false;
		CS_LOCK(lockState)
		{
			canWrite = connected && !stopping && !terminalPending && !writePending;
			if (canWrite)
			{
				writePending = true;
				empty = buffer->data.Count() == 0;
			}
		}
		CHECK_ERROR(canWrite, L"IAsyncSocketConnection::WriteAsync requires a connected connection with no outstanding write.");

		if (empty)
		{
			auto state = Retain();
			runtime->QueueCallback(Func<void()>([state, buffer]()
			{
				state->DeliverWrite(buffer);
			}));
			return;
		}

		auto operation = new WriteOperation(Retain(), buffer);
		DWORD immediateError = ERROR_SUCCESS;
		lockState.Enter();
		if (stopping || terminalPending)
		{
			writePending = false;
			lockState.Leave();
			delete operation;
			return;
		}
		BeginPendingLocked();
		immediateError = operation->PostLocked();
		if (immediateError != ERROR_SUCCESS)
		{
			EndPendingLocked();
		}
		lockState.Leave();

		if (immediateError != ERROR_SUCCESS)
		{
			delete operation;
			QueueTerminal(immediateError, true);
		}
	}

	void ConnectionState::DeliverWrite(Ptr<AsyncSocketBuffer> buffer)
	{
		bool deliver = false;
		CS_LOCK(lockState)
		{
			if (writePending && !stopping && !terminalPending)
			{
				writePending = false;
				deliver = true;
			}
		}
		if (deliver)
		{
			InvokeCallback(false, [&](IAsyncSocketCallback* installed)
			{
				installed->OnWriteCompleted(buffer);
			});
		}
	}

	void ConnectionState::QueueTerminal(DWORD error, bool reportError)
	{
		bool queue = false;
		CS_LOCK(lockState)
		{
			if (!stopping && !terminalPending)
			{
				terminalPending = true;
				queue = true;
			}
		}
		if (queue)
		{
			auto state = Retain();
			runtime->QueueCallback(Func<void()>([state, error, reportError]()
			{
				state->DeliverTerminal(error, reportError);
			}));
		}
	}

	void ConnectionState::DeliverTerminal(DWORD error, bool reportError)
	{
		IAsyncSocketCallback* installed = nullptr;
		bool claimed = false;
		lockState.Enter();
		if (!stopping && terminalPending)
		{
			claimed = true;
			if (reportError && callback)
			{
				installed = callback;
				if (activeCallbacks++ == 0)
				{
					eventCallbacksDrained.Unsignal();
				}
			}
		}
		lockState.Leave();
		if (!claimed)
		{
			return;
		}

		if (installed)
		{
			CallbackFrame frame{ this, currentCallbackFrame };
			currentCallbackFrame = &frame;
			try
			{
				installed->OnError(SocketErrorMessage(L"Asynchronous socket operation", error), true);
			}
			catch (...)
			{
			}
			currentCallbackFrame = frame.previous;
			EndCallback();
		}
		Stop();
	}

	void ConnectionState::StartConnectAttempt()
	{
		auto retainedState = Retain();
		SOCKET createdSocket = INVALID_SOCKET;
		ConnectOperation* operation = nullptr;
		DWORD immediateError = ERROR_SUCCESS;

		lockState.Enter();
		if (!clientMode || stopping || clientStatus != ClientStatus::WaitingForServer)
		{
			lockState.Leave();
			return;
		}
		clientAttempts++;

		createdSocket = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_OVERLAPPED);
		if (createdSocket == INVALID_SOCKET)
		{
			immediateError = WSAGetLastError();
		}

		SOCKADDR_IN localAddress = {};
		localAddress.sin_family = AF_INET;
		localAddress.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
		localAddress.sin_port = 0;
		if (immediateError == ERROR_SUCCESS && bind(createdSocket, (SOCKADDR*)&localAddress, sizeof(localAddress)) == SOCKET_ERROR)
		{
			immediateError = WSAGetLastError();
		}

		LPFN_CONNECTEX connectEx = nullptr;
		GUID connectExGuid = WSAID_CONNECTEX;
		DWORD transferred = 0;
		if (immediateError == ERROR_SUCCESS && WSAIoctl(
			createdSocket,
			SIO_GET_EXTENSION_FUNCTION_POINTER,
			&connectExGuid,
			sizeof(connectExGuid),
			&connectEx,
			sizeof(connectEx),
			&transferred,
			nullptr,
			nullptr
		) == SOCKET_ERROR)
		{
			immediateError = WSAGetLastError();
		}
		if (immediateError == ERROR_SUCCESS && !runtime->Associate(createdSocket))
		{
			immediateError = GetLastError();
		}

		if (immediateError == ERROR_SUCCESS)
		{
			socket = createdSocket;
			connected = false;
			auto generation = ++clientGeneration;
			operation = new ConnectOperation(retainedState, createdSocket, generation);
			BeginPendingLocked();

			SOCKADDR_IN remoteAddress = {};
			remoteAddress.sin_family = AF_INET;
			remoteAddress.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
			remoteAddress.sin_port = htons((u_short)clientPort);
			auto result = connectEx(
				createdSocket,
				(SOCKADDR*)&remoteAddress,
				sizeof(remoteAddress),
				nullptr,
				0,
				nullptr,
				&operation->native.overlapped
			);
			if (!result)
			{
				auto error = WSAGetLastError();
				if (error != WSA_IO_PENDING)
				{
					immediateError = error;
					socket = INVALID_SOCKET;
					EndPendingLocked();
				}
			}
		}
		lockState.Leave();

		if (immediateError != ERROR_SUCCESS)
		{
			if (createdSocket != INVALID_SOCKET)
			{
				closesocket(createdSocket);
			}
			delete operation;
			QueueConnectFailure(immediateError);
		}
	}

	void ConnectionState::CompleteConnect(SOCKET operationSocket, vint generation, DWORD error)
	{
		if (error == ERROR_SUCCESS)
		{
			if (setsockopt(operationSocket, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, nullptr, 0) == SOCKET_ERROR)
			{
				error = WSAGetLastError();
			}
		}

		bool accepted = false;
		bool failed = false;
		lockState.Enter();
		if (!stopping && clientStatus == ClientStatus::WaitingForServer && socket == operationSocket && clientGeneration == generation)
		{
			if (error == ERROR_SUCCESS)
			{
				connected = true;
				clientStatus = ClientStatus::Connected;
				accepted = true;
			}
			else
			{
				socket = INVALID_SOCKET;
				failed = true;
			}
		}
		lockState.Leave();

		if (accepted)
		{
			auto state = Retain();
			runtime->QueueCallback(Func<void()>([state]()
			{
				state->DeliverConnected();
			}));
		}
		else if (failed)
		{
			closesocket(operationSocket);
			QueueConnectFailure(error);
		}
	}

	void ConnectionState::DeliverConnected()
	{
		bool deliver = false;
		CS_LOCK(lockState)
		{
			deliver = connected && !stopping && clientStatus == ClientStatus::Connected;
		}
		if (deliver)
		{
			InvokeCallback(false, [](IAsyncSocketCallback* installed)
			{
				installed->OnConnected();
			});
		}
		eventWaitForServer.Signal();
	}

	void ConnectionState::QueueConnectFailure(DWORD error)
	{
		bool queue = false;
		bool fatal = false;
		CS_LOCK(lockState)
		{
			if (!stopping && clientStatus == ClientStatus::WaitingForServer)
			{
				queue = true;
				fatal = clientAttempts >= AsyncSocketClientRetryCount;
			}
		}
		if (queue)
		{
			auto state = Retain();
			runtime->QueueCallback(Func<void()>([state, error, fatal]()
			{
				state->DeliverConnectFailure(error, fatal);
			}));
		}
	}

	void ConnectionState::DeliverConnectFailure(DWORD error, bool fatal)
	{
		bool deliver = false;
		CS_LOCK(lockState)
		{
			deliver = !stopping && clientStatus == ClientStatus::WaitingForServer;
		}
		if (!deliver)
		{
			return;
		}

		InvokeCallback(false, [&](IAsyncSocketCallback* installed)
		{
			installed->OnError(SocketErrorMessage(L"ConnectEx", error), fatal);
		});
		if (fatal)
		{
			Stop();
		}
		else
		{
			ScheduleRetry();
		}
	}

	void ConnectionState::ScheduleRetry()
	{
		CS_LOCK(lockState)
		{
			if (!stopping && clientStatus == ClientStatus::WaitingForServer && clientRetryTimer)
			{
				LARGE_INTEGER dueTime;
				dueTime.QuadPart = -(LONGLONG)AsyncSocketClientRetryDelay * 10000;
				FILETIME fileTime;
				fileTime.dwLowDateTime = dueTime.LowPart;
				fileTime.dwHighDateTime = dueTime.HighPart;
				SetThreadpoolTimer(clientRetryTimer, &fileTime, 0, 0);
			}
		}
	}

	void ConnectionState::WaitForServer()
	{
		bool begin = false;
		CS_LOCK(lockState)
		{
			if (clientMode && clientStatus == ClientStatus::Ready && !stopping)
			{
				clientStatus = ClientStatus::WaitingForServer;
				begin = true;
			}
		}
		CHECK_ERROR(begin, L"IAsyncSocketClient::WaitForServer can only be called once while the client is ready.");
		StartConnectAttempt();
		eventWaitForServer.Wait();
	}

	ClientStatus ConnectionState::GetStatus()
	{
		ClientStatus result;
		CS_LOCK(lockState)
		{
			result = clientStatus;
		}
		return result;
	}

	void ConnectionState::Stop()
	{
		SOCKET closingSocket = INVALID_SOCKET;
		PTP_TIMER timer = nullptr;
		CS_LOCK(lockState)
		{
			if (!stopping)
			{
				stopping = true;
				connected = false;
				reading = false;
				writePending = false;
				terminalPending = false;
				closingSocket = socket;
				socket = INVALID_SOCKET;
				if (clientMode)
				{
					clientStatus = ClientStatus::Disconnected;
					timer = clientRetryTimer;
				}
			}
			else if (clientMode)
			{
				timer = clientRetryTimer;
			}
		}

		if (timer)
		{
			SetThreadpoolTimer(timer, nullptr, 0, 0);
			WaitForThreadpoolTimerCallbacks(timer, TRUE);
		}
		if (closingSocket != INVALID_SOCKET)
		{
			// Prefer an orderly FIN for the peer while closesocket cancels this
			// connection's pending overlapped operations.
			shutdown(closingSocket, SD_BOTH);
			closesocket(closingSocket);
		}
		eventIoDrained.Wait();

		auto selfCallback = IsCurrentCallback();
		if (!selfCallback)
		{
			eventCallbacksDrained.Wait();
		}

		IAsyncSocketCallback* installed = nullptr;
		lockState.Enter();
		if (!disconnectedNotified)
		{
			disconnectedNotified = true;
			if (callback)
			{
				installed = callback;
				if (activeCallbacks++ == 0)
				{
					eventCallbacksDrained.Unsignal();
				}
			}
		}
		stopped = true;
		lockState.Leave();

		if (installed)
		{
			CallbackFrame frame{ this, currentCallbackFrame };
			currentCallbackFrame = &frame;
			try
			{
				installed->OnDisconnected();
			}
			catch (...)
			{
			}
			currentCallbackFrame = frame.previous;
			EndCallback();
		}

		if (!selfCallback)
		{
			eventCallbacksDrained.Wait();
		}
		if (clientMode)
		{
			eventWaitForServer.Signal();
		}
	}

/***********************************************************************
AsyncSocketServer::Impl
***********************************************************************/

	class AsyncSocketServer::Impl : public Object
	{
	private:
		class AcceptOperation : public IocpOperation
		{
		public:
			Impl*							server = nullptr;
			SOCKET							acceptedSocket = INVALID_SOCKET;
			BYTE							addresses[(sizeof(SOCKADDR_IN) + 16) * 2];

			AcceptOperation(Impl* _server, SOCKET _acceptedSocket)
				: server(_server)
				, acceptedSocket(_acceptedSocket)
			{
				ZeroMemory(addresses, sizeof(addresses));
			}

			bool Complete(DWORD, DWORD error) override
			{
				server->CompleteAccept(this, error);
				return true;
			}

			void EndPending() override
			{
				server->EndAcceptPending();
			}
		};

		vint								port = 0;
		Ptr<IocpRuntime>						runtime;
		CriticalSection					lockState;
		IAsyncSocketServerCallback*		callback = nullptr;
		bool							startCalled = false;
		bool							starting = false;
		vint							startingThreadId = -1;
		bool							started = false;
		bool							stopping = false;
		bool							stopped = false;
		bool							unexpectedStopQueued = false;
		bool							unexpectedStopNotified = false;
		SOCKET							listener = INVALID_SOCKET;
		LPFN_ACCEPTEX						acceptEx = nullptr;
		bool							acceptPending = false;
		vint							pendingAccepts = 0;
		EventObject						eventAcceptDrained;
		EventObject						eventStartFinished;
		EventObject						eventStopped;
		List<Ptr<AsyncSocketConnection>>		connections;

		void FinishStart()
		{
			CS_LOCK(lockState)
			{
				starting = false;
				startingThreadId = -1;
				eventStartFinished.Signal();
			}
		}

		class StartScope
		{
		private:
			Impl*							server = nullptr;

		public:
			StartScope(Impl* _server)
				: server(_server)
			{
			}

			~StartScope()
			{
				server->FinishStart();
			}
		};

		void BeginAcceptPendingLocked()
		{
			if (pendingAccepts++ == 0)
			{
				eventAcceptDrained.Unsignal();
			}
		}

		void EndAcceptPending()
		{
			CS_LOCK(lockState)
			{
				if (--pendingAccepts == 0)
				{
					eventAcceptDrained.Signal();
				}
			}
		}

		bool PostAccept()
		{
			SOCKET acceptedSocket = INVALID_SOCKET;
			AcceptOperation* operation = nullptr;
			DWORD immediateError = ERROR_SUCCESS;

			lockState.Enter();
			if (!started || stopping || acceptPending)
			{
				lockState.Leave();
				return false;
			}
			acceptedSocket = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_OVERLAPPED);
			if (acceptedSocket == INVALID_SOCKET)
			{
				immediateError = WSAGetLastError();
			}
			else
			{
				operation = new AcceptOperation(this, acceptedSocket);
				acceptPending = true;
				BeginAcceptPendingLocked();
				DWORD received = 0;
				auto result = acceptEx(
					listener,
					acceptedSocket,
					operation->addresses,
					0,
					sizeof(SOCKADDR_IN) + 16,
					sizeof(SOCKADDR_IN) + 16,
					&received,
					&operation->native.overlapped
				);
				if (!result)
				{
					auto error = WSAGetLastError();
					if (error != WSA_IO_PENDING)
					{
						immediateError = error;
						acceptPending = false;
						EndPendingAcceptLocked();
					}
				}
			}
			lockState.Leave();

			if (immediateError != ERROR_SUCCESS)
			{
				if (acceptedSocket != INVALID_SOCKET)
				{
					closesocket(acceptedSocket);
				}
				delete operation;
				return false;
			}
			return true;
		}

		void EndPendingAcceptLocked()
		{
			if (--pendingAccepts == 0)
			{
				eventAcceptDrained.Signal();
			}
		}

		void QueueUnexpectedStop()
		{
			bool queue = false;
			CS_LOCK(lockState)
			{
				if (started && !stopping && !unexpectedStopQueued)
				{
					unexpectedStopQueued = true;
					queue = true;
				}
			}
			if (!queue)
			{
				return;
			}

			auto self = this;
			runtime->QueueCallback(Func<void()>([self]()
			{
				IAsyncSocketServerCallback* installed = nullptr;
				CS_LOCK(self->lockState)
				{
					if (self->started && !self->stopping && !self->unexpectedStopNotified)
					{
						self->unexpectedStopNotified = true;
						installed = self->callback;
					}
				}
				if (installed)
				{
					try
					{
						installed->OnServerStopped();
					}
					catch (...)
					{
					}
				}
				self->Stop();
			}));
		}

		void CompleteAccept(AcceptOperation* operation, DWORD error)
		{
			bool running = false;
			CS_LOCK(lockState)
			{
				acceptPending = false;
				running = started && !stopping;
			}

			auto acceptedSocket = operation->acceptedSocket;
			if (!running || error != ERROR_SUCCESS)
			{
				closesocket(acceptedSocket);
				if (running)
				{
					if (!PostAccept())
					{
						QueueUnexpectedStop();
					}
				}
				return;
			}

			if (setsockopt(acceptedSocket, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (CHAR*)&listener, sizeof(listener)) == SOCKET_ERROR || !runtime->Associate(acceptedSocket))
			{
				closesocket(acceptedSocket);
				if (!PostAccept())
				{
					QueueUnexpectedStop();
				}
				return;
			}

			auto state = Ptr(new ConnectionState(runtime.Obj(), acceptedSocket));
			auto connection = Ptr(new AsyncSocketConnection(state));
			bool retain = false;
			CS_LOCK(lockState)
			{
				if (started && !stopping)
				{
					connections.Add(connection);
					retain = true;
				}
			}

			if (!PostAccept())
			{
				QueueUnexpectedStop();
			}
			if (!retain)
			{
				connection->Stop();
				return;
			}

			auto self = this;
			runtime->QueueCallback(Func<void()>([self, connection]()
			{
				bool invoke = false;
				IAsyncSocketServerCallback* installed = nullptr;
				CS_LOCK(self->lockState)
				{
					invoke = self->started && !self->stopping;
					if (invoke)
					{
						installed = self->callback;
					}
				}
				if (!invoke)
				{
					connection->Stop();
					return;
				}

				WaitForClientResult result = WaitForClientResult::Reject;
				try
				{
					result = installed->OnClientConnected(connection.Obj());
				}
				catch (...)
				{
				}
				if (result == WaitForClientResult::Reject)
				{
					CS_LOCK(self->lockState)
					{
						self->connections.Remove(connection.Obj());
					}
					connection->Stop();
				}
			}));
		}

	public:
		Impl(vint _port)
			: port(_port)
			, runtime(Ptr(new IocpRuntime))
		{
			CHECK_ERROR(eventAcceptDrained.CreateManualUnsignal(true), L"AsyncSocketServer failed to create its accept drain event.");
			CHECK_ERROR(eventStartFinished.CreateManualUnsignal(true), L"AsyncSocketServer failed to create its startup drain event.");
			CHECK_ERROR(eventStopped.CreateManualUnsignal(false), L"AsyncSocketServer failed to create its stop event.");
		}

		~Impl()
		{
			Stop();
		}

		vint GetPort()
		{
			return port;
		}

		void Start(IAsyncSocketServerCallback* _callback)
		{
			CHECK_ERROR(_callback != nullptr, L"AsyncSocketServer::Start requires a callback.");
			bool begin = false;
			CS_LOCK(lockState)
			{
				if (!startCalled && !stopping)
				{
					startCalled = true;
					starting = true;
					startingThreadId = Thread::GetCurrentThreadId();
					eventStartFinished.Unsignal();
					begin = true;
				}
			}
			CHECK_ERROR(begin, L"AsyncSocketServer::Start can only be called once.");
			StartScope startScope(this);
			SOCKET createdListener = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_OVERLAPPED);
			if (createdListener == INVALID_SOCKET)
			{
				throw AsyncSocketServerStartException(AsyncSocketServerStartFailure::Other, L"AsyncSocketServer failed to create its listener socket.");
			}

			BOOL exclusive = TRUE;
			if (setsockopt(createdListener, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (CHAR*)&exclusive, sizeof(exclusive)) == SOCKET_ERROR)
			{
				closesocket(createdListener);
				throw AsyncSocketServerStartException(AsyncSocketServerStartFailure::Other, L"AsyncSocketServer failed to apply SO_EXCLUSIVEADDRUSE.");
			}

			SOCKADDR_IN address = {};
			address.sin_family = AF_INET;
			address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
			address.sin_port = htons((u_short)port);
			if (bind(createdListener, (SOCKADDR*)&address, sizeof(address)) == SOCKET_ERROR)
			{
				auto error = WSAGetLastError();
				closesocket(createdListener);
				auto failure = error == WSAEADDRINUSE || error == WSAEACCES ? AsyncSocketServerStartFailure::AddressInUse : AsyncSocketServerStartFailure::Other;
				throw AsyncSocketServerStartException(failure, SocketErrorMessage(L"AsyncSocketServer bind", error));
			}
			if (listen(createdListener, SOMAXCONN) == SOCKET_ERROR)
			{
				auto error = WSAGetLastError();
				closesocket(createdListener);
				auto failure = error == WSAEADDRINUSE || error == WSAEACCES ? AsyncSocketServerStartFailure::AddressInUse : AsyncSocketServerStartFailure::Other;
				throw AsyncSocketServerStartException(failure, SocketErrorMessage(L"AsyncSocketServer listen", error));
			}
			if (!runtime->Associate(createdListener))
			{
				closesocket(createdListener);
				throw AsyncSocketServerStartException(AsyncSocketServerStartFailure::Other, L"AsyncSocketServer failed to associate its listener with the IO completion port.");
			}

			LPFN_ACCEPTEX loadedAcceptEx = nullptr;
			GUID acceptExGuid = WSAID_ACCEPTEX;
			DWORD transferred = 0;
			if (WSAIoctl(
				createdListener,
				SIO_GET_EXTENSION_FUNCTION_POINTER,
				&acceptExGuid,
				sizeof(acceptExGuid),
				&loadedAcceptEx,
				sizeof(loadedAcceptEx),
				&transferred,
				nullptr,
				nullptr
			) == SOCKET_ERROR)
			{
				closesocket(createdListener);
				throw AsyncSocketServerStartException(AsyncSocketServerStartFailure::Other, L"AsyncSocketServer failed to load AcceptEx.");
			}

			bool canStart = false;
			CS_LOCK(lockState)
			{
				if (!stopping)
				{
					listener = createdListener;
					acceptEx = loadedAcceptEx;
					callback = _callback;
					started = true;
					canStart = true;
				}
			}
			if (!canStart)
			{
				closesocket(createdListener);
				throw AsyncSocketServerStartException(AsyncSocketServerStartFailure::Other, L"AsyncSocketServer was stopped during startup.");
			}
			if (!PostAccept())
			{
				Stop();
				throw AsyncSocketServerStartException(AsyncSocketServerStartFailure::Other, L"AsyncSocketServer failed to post AcceptEx.");
			}
		}

		void Stop()
		{
			SOCKET closingListener = INVALID_SOCKET;
			bool first = false;
			bool waitForStart = false;
			bool selfStarting = false;
			auto selfWorker = currentCallbackRuntime == runtime.Obj() || currentCompletionRuntime == runtime.Obj();
			auto currentThreadId = Thread::GetCurrentThreadId();
			CS_LOCK(lockState)
			{
				if (!stopping)
				{
					stopping = true;
					started = false;
					closingListener = listener;
					listener = INVALID_SOCKET;
					first = true;
				}
				selfStarting = starting && startingThreadId == currentThreadId;
				waitForStart = starting && !selfStarting;
			}
			if (waitForStart)
			{
				eventStartFinished.Wait();
			}
			if (!first)
			{
				if (selfStarting)
				{
					return;
				}
				// A runtime callback must not wait for the caller that is draining it.
				if (!selfWorker)
				{
					eventStopped.Wait();
					// A callback-worker caller requests runtime exit but cannot join itself.
					// An external repeated Stop completes that deferred finalization here.
					runtime->Stop();
					CS_LOCK(lockState)
					{
						callback = nullptr;
					}
				}
				return;
			}
			if (closingListener != INVALID_SOCKET)
			{
				closesocket(closingListener);
			}
			eventAcceptDrained.Wait();

			List<Ptr<AsyncSocketConnection>> stoppingConnections;
			CS_LOCK(lockState)
			{
				for (auto connection : connections)
				{
					stoppingConnections.Add(connection);
				}
				connections.Clear();
			}
			for (auto connection : stoppingConnections)
			{
				connection->Stop();
			}

			runtime->Stop();
			CS_LOCK(lockState)
			{
				if (!selfWorker)
				{
					callback = nullptr;
				}
				stopped = true;
			}
			eventStopped.Signal();
		}

		bool IsStopped()
		{
			bool result = false;
			CS_LOCK(lockState)
			{
				result = stopped;
			}
			return result;
		}
	};

/***********************************************************************
AsyncSocketServer
***********************************************************************/

	AsyncSocketServer::AsyncSocketServer(vint port)
	{
		CHECK_ERROR(1 <= port && port <= 65535, L"AsyncSocketServer requires a port in 1..65535.");
		impl = new Impl(port);
	}

	AsyncSocketServer::~AsyncSocketServer()
	{
		delete impl;
	}

	vint AsyncSocketServer::GetPort()
	{
		return impl->GetPort();
	}

	void AsyncSocketServer::Start(IAsyncSocketServerCallback* callback)
	{
		impl->Start(callback);
	}

	void AsyncSocketServer::Stop()
	{
		impl->Stop();
	}

	bool AsyncSocketServer::IsStopped()
	{
		return impl->IsStopped();
	}

/***********************************************************************
AsyncSocketClient::Impl
***********************************************************************/

	class AsyncSocketClient::Impl : public Object
	{
	private:
		vint								port = 0;
		Ptr<IocpRuntime>						runtime;
		Ptr<ConnectionState>				state;
		Ptr<AsyncSocketConnection>			connection;
		SpinLock							lockStop;
		bool							stopped = false;

	public:
		Impl(vint _port)
			: port(_port)
			, runtime(Ptr(new IocpRuntime))
			, state(Ptr(new ConnectionState(runtime.Obj(), true, _port)))
			, connection(Ptr(new AsyncSocketConnection(state)))
		{
		}

		~Impl()
		{
			Stop();
		}

		vint GetPort()
		{
			return port;
		}

		void Stop()
		{
			bool first = false;
			SPIN_LOCK(lockStop)
			{
				if (!stopped)
				{
					stopped = true;
					first = true;
				}
			}
			if (first)
			{
				connection->Stop();
				state->CloseRetryTimer();
				runtime->Stop();
			}
		}

		IAsyncSocketConnection* GetConnection()
		{
			return connection.Obj();
		}

		void WaitForServer()
		{
			state->WaitForServer();
		}

		ClientStatus GetStatus()
		{
			return state->GetStatus();
		}
	};

/***********************************************************************
AsyncSocketClient
***********************************************************************/

	AsyncSocketClient::AsyncSocketClient(vint port)
	{
		CHECK_ERROR(1 <= port && port <= 65535, L"AsyncSocketClient requires a port in 1..65535.");
		impl = new Impl(port);
	}

	AsyncSocketClient::~AsyncSocketClient()
	{
		delete impl;
	}

	vint AsyncSocketClient::GetPort()
	{
		return impl->GetPort();
	}

	Ptr<IAsyncSocketClient> AsyncSocketClient::CreateSameEndpointClient()
	{
		return Ptr(new AsyncSocketClient(GetPort()));
	}

	IAsyncSocketConnection* AsyncSocketClient::GetConnection()
	{
		return impl->GetConnection();
	}

	void AsyncSocketClient::WaitForServer()
	{
		impl->WaitForServer();
	}

	ClientStatus AsyncSocketClient::GetStatus()
	{
		return impl->GetStatus();
	}

}

namespace vl::inter_process::async_tcp_socket
{
	Ptr<IAsyncSocketServer> CreateDefaultAsyncSocketServer(vint port)
	{
		return Ptr(new windows_socket::AsyncSocketServer(port));
	}

	Ptr<IAsyncSocketClient> CreateDefaultAsyncSocketClient(vint port)
	{
		return Ptr(new windows_socket::AsyncSocketClient(port));
	}
}


/***********************************************************************
.\INTERPROCESS\WINDOWS\HTTPCLIENT.WINDOWS.CPP
***********************************************************************/

namespace vl::inter_process::windows_http
{

/***********************************************************************
HttpClient (Reading)
***********************************************************************/

void HttpClient::RaiseLocalError(WString errorMessage, bool fatal)
{
	if (callback)
	{
		callback->OnLocalError(errorMessage, fatal);
	}
	if (fatal)
	{
		Stop();
	}
}

bool HttpClient::IsStopping()
{
	bool result = false;
	SPIN_LOCK(lockState)
	{
		result = state == State::Stopping;
	}
	return result;
}

void HttpClient::BeginReadingLoopUnsafe()
{
	SendHttpRequest(HttpRequestType::Request, urlRequest, WString::Empty);
}

/***********************************************************************
HttpClient (WaitForServer)
***********************************************************************/

INetworkProtocolConnection* HttpClient::GetConnection()
{
	return this;
}

void HttpClient::CompleteConnectRequest(const WString& response, const WString& error)
{
	SPIN_LOCK(lockConnectResult)
	{
		connectResponse = response;
		connectError = error;
		connectCompleted = true;
	}
	eventWaitForServer.Signal();
}

void HttpClient::WaitForServer()
{
	{
		SPIN_LOCK(lockState)
		{
			if (state == State::Stopping) return;
			CHECK_ERROR(state == State::Ready, L"WaitForServer can only be called once.");
			state = State::WaitForServerConnection;
		}
	}

	{
		SPIN_LOCK(lockConnectResult)
		{
			connectCompleted = false;
			connectResponse = WString::Empty;
			connectError = WString::Empty;
		}
	}

	eventWaitForServer.Unsignal();
	if (!SendHttpRequest(HttpRequestType::Connect, urlConnect, WString::Empty))
	{
		return;
	}

	eventWaitForServer.Wait();
	if (IsStopping()) return;

	WString body;
	WString error;
	bool completed = false;
	{
		SPIN_LOCK(lockConnectResult)
		{
			body = connectResponse;
			error = connectError;
			completed = connectCompleted;
		}
	}

	CHECK_ERROR(completed, L"/Connect did not complete.");
	CHECK_ERROR(error == WString::Empty, L"/Connect failed.");

	vint separatorIndex = body.IndexOf(L';');
	CHECK_ERROR(separatorIndex != -1, L"/Connect response body is not in the correct format: requestUrl;responseUrl.");
	urlRequest = baseUrl + body.Left(separatorIndex);
	urlResponse = baseUrl + body.Right(body.Length() - separatorIndex - 1);
	{
		SPIN_LOCK(lockState)
		{
			if (state == State::Stopping) return;
			state = State::Running;
		}
	}

	if (callback)
	{
		callback->OnConnected();
	}
}

ClientStatus HttpClient::GetStatus()
{
	ClientStatus result = ClientStatus::Disconnected;
	SPIN_LOCK(lockState)
	{
		switch (state)
		{
		case State::Ready:
			result = ClientStatus::Ready;
			break;
		case State::WaitForServerConnection:
			result = ClientStatus::WaitingForServer;
			break;
		case State::Running:
			result = ClientStatus::Connected;
			break;
		default:
			result = ClientStatus::Disconnected;
			break;
		}
	}
	return result;
}

/***********************************************************************
HttpClient (Writing)
***********************************************************************/

bool HttpClient::SendHttpRequest(HttpRequestType requestType, const WString& url, const WString& body, vint attempt)
{
	Ptr<HttpClientApi> api;
	{
		SPIN_LOCK(lockState)
		{
			if (state == State::Stopping) return false;
			switch (requestType)
			{
			case HttpRequestType::Connect:
				CHECK_ERROR(state == State::WaitForServerConnection, L"/Connect can only be called when client is waiting for the server.");
				break;
			case HttpRequestType::Request:
				CHECK_ERROR(state == State::Running, L"/Request can only be called when client is running.");
				break;
			case HttpRequestType::Response:
				CHECK_ERROR(state == State::Running, L"/Response can only be called when client is running.");
				break;
			}
			api = httpClientApi;
		}
	}

	if (!api) return false;

	HttpRequest encodedBody;
	if (requestType == HttpRequestType::Response)
	{
		encodedBody.SetBodyUtf8(body);
	}

	HttpRequest request;
	switch (requestType)
	{
	case HttpRequestType::Connect:
		request = CreateHttpNetworkProtocolConnectRequest(url);
		break;
	case HttpRequestType::Request:
		request = CreateHttpNetworkProtocolReceiveRequest(url);
		request.receiveTimeout = 0;
		break;
	case HttpRequestType::Response:
		request = CreateHttpNetworkProtocolSendRequest(url, encodedBody.body);
		request.keepAliveOnStop = true;
		break;
	}

	api->HttpQuery(request, [this, requestType, body, attempt](Variant<HttpResponse, HttpError> result)
	{
		OnHttpRequestCompleted(requestType, body, attempt, std::move(result));
	});
	return true;
}

void HttpClient::OnHttpRequestFailed(HttpRequestType requestType, const WString& body, vint attempt, const WString& errorMessage)
{
	if (IsStopping()) return;

	switch (requestType)
	{
	case HttpRequestType::Connect:
		{
			bool fatal = attempt >= HttpRequestMaxAttempts;
			RaiseLocalError(errorMessage, fatal);
			if (!fatal && !IsStopping())
			{
				SendHttpRequest(HttpRequestType::Connect, urlConnect, WString::Empty, attempt + 1);
			}
		}
		break;
	case HttpRequestType::Request:
		SendHttpRequest(HttpRequestType::Request, urlRequest, WString::Empty, attempt + 1);
		break;
	case HttpRequestType::Response:
		{
			bool fatal = attempt >= HttpRequestMaxAttempts;
			RaiseLocalError(errorMessage, fatal);
			if (!fatal && !IsStopping())
			{
				SendHttpRequest(HttpRequestType::Response, urlResponse, body, attempt + 1);
			}
		}
		break;
	}
}

void HttpClient::OnHttpRequestCompleted(HttpRequestType requestType, WString body, vint attempt, Variant<HttpResponse, HttpError> result)
{
	if (auto error = result.TryGet<HttpError>())
	{
		switch (requestType)
		{
		case HttpRequestType::Connect:
			OnHttpRequestFailed(requestType, body, attempt, L"/Connect failed: " + error->message);
			break;
		case HttpRequestType::Request:
			OnHttpRequestFailed(requestType, body, attempt, L"/Request failed: " + error->message);
			break;
		case HttpRequestType::Response:
			OnHttpRequestFailed(requestType, body, attempt, L"/Response failed: " + error->message);
			break;
		}
		return;
	}

	auto&& response = result.Get<HttpResponse>();
	if (response.statusCode != 200)
	{
		switch (requestType)
		{
		case HttpRequestType::Connect:
			OnHttpRequestFailed(requestType, body, attempt, WString::Unmanaged(L"/Connect returned status code: ") + itow(response.statusCode) + L".");
			break;
		case HttpRequestType::Request:
			OnHttpRequestFailed(requestType, body, attempt, WString::Unmanaged(L"/Request returned status code: ") + itow(response.statusCode) + L", another renderer may have connected to the core.");
			break;
		case HttpRequestType::Response:
			OnHttpRequestFailed(requestType, body, attempt, WString::Unmanaged(L"/Response returned status code: ") + itow(response.statusCode) + L", another renderer may have connected to the core.");
			break;
		}
		return;
	}

	if (response.contentType != HttpNetworkProtocolContentType)
	{
		switch (requestType)
		{
		case HttpRequestType::Connect:
			OnHttpRequestFailed(requestType, body, attempt, L"/Connect response did not return content type: application/json; charset=utf8.");
			break;
		case HttpRequestType::Request:
			OnHttpRequestFailed(requestType, body, attempt, L"/Request response did not return content type: application/json; charset=utf8.");
			break;
		case HttpRequestType::Response:
			OnHttpRequestFailed(requestType, body, attempt, L"/Response response did not return content type: application/json; charset=utf8.");
			break;
		}
		return;
	}

	auto responseBody = response.GetBodyUtf8();
	switch (requestType)
	{
	case HttpRequestType::Connect:
		CompleteConnectRequest(responseBody, WString::Empty);
		break;
	case HttpRequestType::Request:
		if (!IsStopping())
		{
			BeginReadingLoopUnsafe();
			if (responseBody.Length() > 0 && callback)
			{
				callback->OnReadString(responseBody);
			}
		}
		break;
	case HttpRequestType::Response:
		if (!IsStopping() && responseBody.Length() > 0 && callback)
		{
			callback->OnReadString(responseBody);
		}
		break;
	}
}

void HttpClient::SendString(const WString& str)
{
	SendHttpRequest(HttpRequestType::Response, urlResponse, str);
}

/***********************************************************************
HttpClient
***********************************************************************/

HttpClient::HttpClient(const WString _baseUrl, vint port)
	: baseUrl(_baseUrl)
{
	CHECK_ERROR(eventWaitForServer.CreateAutoUnsignal(false), L"HttpClient initialization failed on eventWaitForServer.CreateAutoUnsignal.");

	httpClientApi = Ptr(new HttpClientApi(L"localhost", port));
	urlConnect = baseUrl + HttpServerUrl_Connect;
}

HttpClient::~HttpClient()
{
	Stop();
}

void HttpClient::InstallCallback(INetworkProtocolCallback* _callback)
{
	CHECK_ERROR(!callback || !_callback, L"HttpClient::InstallCallback only accepts one callback at a time.");
	callback = _callback;
	if (!callback) return;
	callback->OnInstalled(this);
}

void HttpClient::Stop()
{
	Ptr<HttpClientApi> stoppingApi;
	bool notifyDisconnected = false;
	{
		SPIN_LOCK(lockState)
		{
			if (httpClientApi)
			{
				state = State::Stopping;
				stoppingApi = httpClientApi;
				httpClientApi = nullptr;
				notifyDisconnected = true;
			}
			else
			{
				state = State::Stopping;
			}
		}
	}

	eventWaitForServer.Signal();
	if (stoppingApi)
	{
		stoppingApi->Stop();
	}

	if (notifyDisconnected && callback)
	{
		callback->OnDisconnected();
	}
}

}


/***********************************************************************
.\INTERPROCESS\WINDOWS\HTTPCLIENTAPI.WINDOWS.CPP
***********************************************************************/

#ifndef VCZH_MSVC
static_assert(false, "Do not build this file for non-Windows applications.");
#endif

#pragma comment(lib, "WinHttp.lib")

namespace vl::inter_process::windows_http
{

using namespace vl::collections;

/***********************************************************************
HttpClientApi
***********************************************************************/

HttpError HttpClientApi::MakeError(const WString& operation, DWORD errorCode)
{
	HttpError error;
	error.operation = operation;
	error.errorCode = errorCode;
	error.message = operation + L" failed with Windows error " + itow((vint)errorCode) + L".";
	return error;
}

bool HttpClientApi::IsStopping()
{
	bool result = false;
	SPIN_LOCK(lockActiveRequests)
	{
		result = stopping;
	}
	return result;
}

void HttpClientApi::BeginPendingCallback()
{
	if (pendingCallbacks++ == 0)
	{
		eventPendingCallbacks.Unsignal();
	}
}

void HttpClientApi::EndPendingCallback()
{
	SPIN_LOCK(lockActiveRequests)
	{
		if (--pendingCallbacks == 0)
		{
			eventPendingCallbacks.Signal();
		}
	}
}

void HttpClientApi::AttachRequestUnsafe(Ptr<HttpRequestContext> context)
{
	activeRequests.Add(context);
}

void HttpClientApi::RemoveRequestUnsafe(Ptr<HttpRequestContext> context)
{
	for (vint index = 0; index < activeRequests.Count(); index++)
	{
		if (activeRequests[index] == context)
		{
			activeRequests.RemoveAt(index);
			return;
		}
	}
}

void HttpClientApi::CloseRequest(Ptr<HttpRequestContext> context)
{
	HINTERNET httpRequest = NULL;
	SPIN_LOCK(context->lockContext)
	{
		if (!context->closing)
		{
			context->closing = true;
			httpRequest = context->httpRequest;
		}
	}
	if (httpRequest)
	{
		WinHttpCloseHandle(httpRequest);
	}
}

void HttpClientApi::OnRequestHandleClosing(Ptr<HttpRequestContext> context)
{
	SPIN_LOCK(lockActiveRequests)
	{
		RemoveRequestUnsafe(context);
	}
	EndPendingCallback();
}

void HttpClientApi::CompleteRequest(Ptr<HttpRequestContext> context, HttpResponse&& response)
{
	Func<void(Variant<HttpResponse, HttpError>)> callback;
	bool stoppingNow = IsStopping();
	bool invokeCallback = false;
	SPIN_LOCK(context->lockContext)
	{
		if (!context->completed)
		{
			context->completed = true;
			callback = context->callback;
			invokeCallback = !stoppingNow;
		}
	}

	if (invokeCallback && callback)
	{
		CloseRequest(context);
		callback(Variant<HttpResponse, HttpError>(std::move(response)));
	}
	else
	{
		CloseRequest(context);
	}
}

void HttpClientApi::CompleteRequest(Ptr<HttpRequestContext> context, HttpError&& error)
{
	Func<void(Variant<HttpResponse, HttpError>)> callback;
	bool stoppingNow = IsStopping();
	bool invokeCallback = false;
	SPIN_LOCK(context->lockContext)
	{
		if (!context->completed)
		{
			context->completed = true;
			callback = context->callback;
			invokeCallback = !stoppingNow;
		}
	}

	if (invokeCallback && callback)
	{
		CloseRequest(context);
		callback(Variant<HttpResponse, HttpError>(std::move(error)));
	}
	else
	{
		CloseRequest(context);
	}
}

void HttpClientApi::CompleteRequestWithLastError(Ptr<HttpRequestContext> context, const WString& operation, DWORD errorCode)
{
	if (errorCode == ERROR_INVALID_HANDLE && IsStopping() && !context->keepAliveOnStop)
	{
		return;
	}
	CompleteRequest(context, MakeError(operation, errorCode));
}

void CALLBACK HttpClientApi::HttpStatusCallback(HINTERNET httpRequest, DWORD_PTR contextValue, DWORD status, LPVOID statusInformation, DWORD statusInformationLength)
{
	if (!contextValue) return;
	auto contextPtr = reinterpret_cast<Ptr<HttpClientApi::HttpRequestContext>*>(contextValue);
	auto context = *contextPtr;
	auto self = context->api;

	switch (status)
	{
	case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
		{
			if (self->IsStopping() && !context->keepAliveOnStop) return;

			BOOL httpResult = WinHttpReceiveResponse(httpRequest, NULL);
			DWORD lastError = GetLastError();
			if (httpResult == FALSE)
			{
				self->CompleteRequestWithLastError(context, L"WinHttpReceiveResponse", lastError);
			}
		}
		break;
	case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
		{
			if (self->IsStopping() && !context->keepAliveOnStop) return;

			DWORD statusCode = 0;
			DWORD dwordLength = sizeof(DWORD);
			BOOL httpResult = WinHttpQueryHeaders(
				httpRequest,
				WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
				WINHTTP_HEADER_NAME_BY_INDEX,
				&statusCode,
				&dwordLength,
				WINHTTP_NO_HEADER_INDEX);
			DWORD lastError = GetLastError();
			if (httpResult == FALSE)
			{
				self->CompleteRequestWithLastError(context, L"WinHttpQueryHeaders(status)", lastError);
				return;
			}
			context->response.statusCode = statusCode;

			DWORD headerLength = 0;
			httpResult = WinHttpQueryHeaders(
				httpRequest,
				WINHTTP_QUERY_CONTENT_TYPE,
				WINHTTP_HEADER_NAME_BY_INDEX,
				NULL,
				&headerLength,
				WINHTTP_NO_HEADER_INDEX);
			lastError = GetLastError();
			if (httpResult == FALSE && lastError == ERROR_INSUFFICIENT_BUFFER)
			{
				Array<wchar_t> headerBuffer(headerLength / sizeof(wchar_t) + 1);
				ZeroMemory(&headerBuffer[0], headerBuffer.Count() * sizeof(wchar_t));
				httpResult = WinHttpQueryHeaders(
					httpRequest,
					WINHTTP_QUERY_CONTENT_TYPE,
					WINHTTP_HEADER_NAME_BY_INDEX,
					&headerBuffer[0],
					&headerLength,
					WINHTTP_NO_HEADER_INDEX);
				lastError = GetLastError();
				if (httpResult == FALSE)
				{
					self->CompleteRequestWithLastError(context, L"WinHttpQueryHeaders(content-type)", lastError);
					return;
				}
				context->response.contentType = &headerBuffer[0];
			}
			else if (httpResult == FALSE && lastError != ERROR_WINHTTP_HEADER_NOT_FOUND)
			{
				self->CompleteRequestWithLastError(context, L"WinHttpQueryHeaders(content-type)", lastError);
				return;
			}

			headerLength = 0;
			httpResult = WinHttpQueryHeaders(
				httpRequest,
				WINHTTP_QUERY_SET_COOKIE,
				WINHTTP_HEADER_NAME_BY_INDEX,
				NULL,
				&headerLength,
				WINHTTP_NO_HEADER_INDEX);
			lastError = GetLastError();
			if (httpResult == FALSE && lastError == ERROR_INSUFFICIENT_BUFFER)
			{
				Array<wchar_t> headerBuffer(headerLength / sizeof(wchar_t) + 1);
				ZeroMemory(&headerBuffer[0], headerBuffer.Count() * sizeof(wchar_t));
				httpResult = WinHttpQueryHeaders(
					httpRequest,
					WINHTTP_QUERY_SET_COOKIE,
					WINHTTP_HEADER_NAME_BY_INDEX,
					&headerBuffer[0],
					&headerLength,
					WINHTTP_NO_HEADER_INDEX);
				lastError = GetLastError();
				if (httpResult == FALSE)
				{
					self->CompleteRequestWithLastError(context, L"WinHttpQueryHeaders(cookie)", lastError);
					return;
				}
				context->response.cookie = &headerBuffer[0];
			}
			else if (httpResult == FALSE && lastError != ERROR_WINHTTP_HEADER_NOT_FOUND)
			{
				self->CompleteRequestWithLastError(context, L"WinHttpQueryHeaders(cookie)", lastError);
				return;
			}

			context->bodyBufferWriting = 0;
			httpResult = WinHttpQueryDataAvailable(httpRequest, NULL);
			lastError = GetLastError();
			if (httpResult == FALSE)
			{
				self->CompleteRequestWithLastError(context, L"WinHttpQueryDataAvailable", lastError);
			}
		}
		break;
	case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
		{
			if (self->IsStopping() && !context->keepAliveOnStop) return;
			CHECK_ERROR(statusInformationLength == sizeof(DWORD), L"WinHttpQueryDataAvailable returned an unexpected payload.");
			DWORD dataAvailable = *(PDWORD)statusInformation;

			if (dataAvailable == 0)
			{
				context->response.body.Resize(context->bodyBufferWriting);
				self->CompleteRequest(context, std::move(context->response));
				return;
			}

			context->bodyBufferWritingAvailable = dataAvailable;
			DWORD bufferSize = context->bodyBufferWriting + dataAvailable + 1;
			if (context->response.body.Count() < (vint)bufferSize)
			{
				context->response.body.Resize((bufferSize + HttpRespondBodyStep - 1) / HttpRespondBodyStep * HttpRespondBodyStep);
			}

			BOOL httpResult = WinHttpReadData(
				httpRequest,
				&context->response.body[context->bodyBufferWriting],
				dataAvailable,
				NULL);
			DWORD lastError = GetLastError();
			if (httpResult == FALSE)
			{
				self->CompleteRequestWithLastError(context, L"WinHttpReadData", lastError);
			}
		}
		break;
	case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
		{
			if (self->IsStopping() && !context->keepAliveOnStop) return;
			if (context->bodyBufferWritingAvailable != statusInformationLength)
			{
				self->CompleteRequest(context, MakeError(L"WinHttpReadData", ERROR_INVALID_DATA));
				return;
			}
			context->bodyBufferWriting += context->bodyBufferWritingAvailable;

			BOOL httpResult = WinHttpQueryDataAvailable(httpRequest, NULL);
			DWORD lastError = GetLastError();
			if (httpResult == FALSE)
			{
				self->CompleteRequestWithLastError(context, L"WinHttpQueryDataAvailable", lastError);
			}
		}
		break;
	case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
		{
			if (self->IsStopping() && !context->keepAliveOnStop) return;

			auto asyncResult = reinterpret_cast<WINHTTP_ASYNC_RESULT*>(statusInformation);
			DWORD errorCode = asyncResult ? asyncResult->dwError : ERROR_WINHTTP_INTERNAL_ERROR;
			HttpError error = MakeError(L"WinHTTP async request", errorCode);
			if (asyncResult)
			{
				error.message += L" Operation code: " + itow((vint)asyncResult->dwResult) + L".";
			}
			self->CompleteRequest(context, std::move(error));
		}
		break;
	case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
		self->OnRequestHandleClosing(context);
		delete contextPtr;
		break;
	}
}

HttpClientApi::HttpClientApi(const WString& _server, vint _port)
	: server(_server)
	, port(_port)
{
	CHECK_ERROR(eventPendingCallbacks.CreateManualUnsignal(true), L"HttpClientApi initialization failed on eventPendingCallbacks.CreateManualUnsignal.");

	httpSession = WinHttpOpen(
		L"vl::inter_process::windows_http::HttpClientApi",
		WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
		WINHTTP_NO_PROXY_NAME,
		WINHTTP_NO_PROXY_BYPASS,
		WINHTTP_FLAG_ASYNC);
	CHECK_ERROR(httpSession != NULL, L"WinHttpOpen failed.");

#ifdef WINHTTP_OPTION_IPV6_FAST_FALLBACK
	// This option is unavailable before Windows 10 1903. Keep the default address fallback when unsupported.
	BOOL ipv6FastFallback = TRUE;
	WinHttpSetOption(httpSession, WINHTTP_OPTION_IPV6_FAST_FALLBACK, &ipv6FastFallback, sizeof(ipv6FastFallback));
#endif

	httpConnection = WinHttpConnect(
		httpSession,
		server.Buffer(),
		(INTERNET_PORT)port,
		0);
	CHECK_ERROR(httpConnection != NULL, L"WinHttpConnect failed.");
}

HttpClientApi::~HttpClientApi()
{
	Stop();
}

void HttpClientApi::HttpQuery(const HttpRequest& request, Func<void(Variant<HttpResponse, HttpError>)> callback)
{
	bool rejected = false;
	{
		SPIN_LOCK(lockActiveRequests)
		{
			if (stopping)
			{
				rejected = true;
			}
			else
			{
				BeginPendingCallback();
			}
		}
	}
	if (rejected)
	{
		if (callback)
		{
			callback(Variant<HttpResponse, HttpError>(MakeError(L"HttpClientApi::HttpQuery", ERROR_OPERATION_ABORTED)));
		}
		return;
	}

	BOOL httpResult = FALSE;
	DWORD lastError = 0;
	List<LPCWSTR> acceptTypes;
	for (vint i = 0; i < request.acceptTypes.Count(); i++)
	{
		acceptTypes.Add(request.acceptTypes.Get(i).Buffer());
	}
	acceptTypes.Add(nullptr);

	auto method = request.method == WString::Empty ? WString::Unmanaged(L"GET") : request.method;
	auto httpRequest = WinHttpOpenRequest(
		httpConnection,
		method.Buffer(),
		request.query.Buffer(),
		NULL,
		WINHTTP_NO_REFERER,
		&acceptTypes[0],
		(request.secure ? WINHTTP_FLAG_SECURE : 0) | WINHTTP_FLAG_REFRESH);
	lastError = GetLastError();
	if (httpRequest == NULL)
	{
		EndPendingCallback();
		if (callback)
		{
			callback(Variant<HttpResponse, HttpError>(MakeError(L"WinHttpOpenRequest", lastError)));
		}
		return;
	}

	httpResult = WinHttpSetTimeouts(
		httpRequest,
		(int)request.resolveTimeout,
		(int)request.connectTimeout,
		(int)request.sendTimeout,
		(int)request.receiveTimeout);
	lastError = GetLastError();
	if (httpResult == FALSE)
	{
		EndPendingCallback();
		WinHttpCloseHandle(httpRequest);
		if (callback)
		{
			callback(Variant<HttpResponse, HttpError>(MakeError(L"WinHttpSetTimeouts", lastError)));
		}
		return;
	}

	auto contextPtr = new Ptr<HttpRequestContext>(new HttpRequestContext);
	auto context = *contextPtr;
	context->api = this;
	context->httpRequest = httpRequest;
	context->callback = callback;
	context->keepAliveOnStop = request.keepAliveOnStop;

	if (request.body.Count() > 0)
	{
		context->requestBody.Resize(request.body.Count());
		memcpy(&context->requestBody[0], &request.body.Get(0), request.body.Count());
	}

	auto failBeforeCallbackInstalled = [&](const WString& operation, DWORD errorCode)
	{
		WinHttpCloseHandle(httpRequest);
		delete contextPtr;
		EndPendingCallback();
		if (callback)
		{
			callback(Variant<HttpResponse, HttpError>(MakeError(operation, errorCode)));
		}
	};

	DWORD_PTR contextValue = reinterpret_cast<DWORD_PTR>(contextPtr);
	httpResult = WinHttpSetOption(
		httpRequest,
		WINHTTP_OPTION_CONTEXT_VALUE,
		&contextValue,
		sizeof(contextValue));
	lastError = GetLastError();
	if (httpResult == FALSE)
	{
		failBeforeCallbackInstalled(L"WinHttpSetOption(WINHTTP_OPTION_CONTEXT_VALUE)", lastError);
		return;
	}

	if (request.username != WString::Empty && request.password != WString::Empty)
	{
		httpResult = WinHttpSetCredentials(
			httpRequest,
			WINHTTP_AUTH_TARGET_SERVER,
			WINHTTP_AUTH_SCHEME_BASIC,
			request.username.Buffer(),
			request.password.Buffer(),
			NULL);
		lastError = GetLastError();
		if (httpResult == FALSE)
		{
			failBeforeCallbackInstalled(L"WinHttpSetCredentials", lastError);
			return;
		}
	}
	if (request.contentType != WString::Empty)
	{
		httpResult = WinHttpAddRequestHeaders(
			httpRequest,
			(L"Content-Type: " + request.contentType).Buffer(),
			-1,
			WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD);
		lastError = GetLastError();
		if (httpResult == FALSE)
		{
			failBeforeCallbackInstalled(L"WinHttpAddRequestHeaders(content-type)", lastError);
			return;
		}
	}
	if (request.cookie != WString::Empty)
	{
		httpResult = WinHttpAddRequestHeaders(
			httpRequest,
			(L"Cookie: " + request.cookie).Buffer(),
			-1,
			WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD);
		lastError = GetLastError();
		if (httpResult == FALSE)
		{
			failBeforeCallbackInstalled(L"WinHttpAddRequestHeaders(cookie)", lastError);
			return;
		}
	}

	for (vint i = 0; i < request.extraHeaders.Count(); i++)
	{
		WString key = request.extraHeaders.Keys()[i];
		WString value = request.extraHeaders.Values().Get(i);
		httpResult = WinHttpAddRequestHeaders(
			httpRequest,
			(key + L": " + value).Buffer(),
			-1,
			WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD);
		lastError = GetLastError();
		if (httpResult == FALSE)
		{
			failBeforeCallbackInstalled(L"WinHttpAddRequestHeaders(extra)", lastError);
			return;
		}
	}

	bool failedBeforeSend = false;
	WString failedOperation;
	DWORD failedError = 0;
	SPIN_LOCK(lockActiveRequests)
	{
		if (stopping)
		{
			failedBeforeSend = true;
			failedOperation = L"HttpClientApi::HttpQuery";
			failedError = ERROR_OPERATION_ABORTED;
		}
		else
		{
			auto previousCallback = WinHttpSetStatusCallback(
				httpRequest,
				HttpStatusCallback,
				WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS,
				NULL);
			lastError = GetLastError();
			if (previousCallback == WINHTTP_INVALID_STATUS_CALLBACK)
			{
				failedBeforeSend = true;
				failedOperation = L"WinHttpSetStatusCallback";
				failedError = lastError;
			}
			else
			{
				AttachRequestUnsafe(context);
			}
		}
	}
	if (failedBeforeSend)
	{
		failBeforeCallbackInstalled(failedOperation, failedError);
		return;
	}

	DWORD requestBodyLength = (DWORD)context->requestBody.Count();
	LPVOID requestBodyBuffer = requestBodyLength == 0 ? WINHTTP_NO_REQUEST_DATA : (LPVOID)&context->requestBody[0];
	httpResult = WinHttpSendRequest(
		httpRequest,
		WINHTTP_NO_ADDITIONAL_HEADERS,
		0,
		requestBodyBuffer,
		requestBodyLength,
		requestBodyLength,
		contextValue);
	lastError = GetLastError();
	if (httpResult == FALSE)
	{
		CompleteRequestWithLastError(context, L"WinHttpSendRequest", lastError);
	}
}

void HttpClientApi::Stop()
{
	if (httpSession == NULL) return;

	List<HINTERNET> stoppingRequests;
	SPIN_LOCK(lockActiveRequests)
	{
		stopping = true;
		for (auto&& context : activeRequests)
		{
			HINTERNET httpRequest = NULL;
			SPIN_LOCK(context->lockContext)
			{
				if (!context->keepAliveOnStop && !context->closing)
				{
					context->closing = true;
					httpRequest = context->httpRequest;
				}
			}
			if (httpRequest)
			{
				stoppingRequests.Add(httpRequest);
			}
		}
	}
	for (auto httpRequest : stoppingRequests)
	{
		WinHttpCloseHandle(httpRequest);
	}

	eventPendingCallbacks.Wait();

	WinHttpCloseHandle(httpConnection);
	WinHttpSetStatusCallback(
		httpSession,
		NULL,
		WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS,
		NULL);
	WinHttpCloseHandle(httpSession);

	SPIN_LOCK(lockActiveRequests)
	{
		activeRequests.Clear();
	}

	httpConnection = NULL;
	httpSession = NULL;
}

WString HttpClientApi::UrlEncodeQuery(const WString& query)
{
	return HttpUrlEncodeQuery(query);
}

WString HttpClientApi::UrlDecodeQuery(const WString& query)
{
	return HttpUrlDecodeQuery(query);
}

}


/***********************************************************************
.\INTERPROCESS\WINDOWS\HTTPSERVER.WINDOWS.CPP
***********************************************************************/

namespace vl::inter_process::windows_http
{

using namespace vl::collections;

/***********************************************************************
HttpServerConnection
***********************************************************************/

void HttpServerConnection::OnCancelCurrentHttpRequestForPendingRequest()
{
	if (httpPendingRequestId != HTTP_NULL_ID)
	{
		if (!server)
		{
			httpPendingRequestId = HTTP_NULL_ID;
			return;
		}
		ULONG result = HttpCancelHttpRequest(
			server->GetHttpRequestQueue(),
			httpPendingRequestId,
			NULL);
		CHECK_ERROR(
			result == NO_ERROR || result == ERROR_CONNECTION_INVALID || result == ERROR_OPERATION_ABORTED,
			L"HttpCancelHttpRequest failed for canceling outdated /Request.");
		httpPendingRequestId = HTTP_NULL_ID;
	}
}

void HttpServerConnection::OnNewHttpRequestForPendingRequest(HTTP_REQUEST_ID httpRequestId)
{
	OnCancelCurrentHttpRequestForPendingRequest();
	httpPendingRequestId = httpRequestId;
	if (pendingRequestsToSend.Count() > 0)
	{
		auto pendingRequest = pendingRequestsToSend[0];
		pendingRequestsToSend.RemoveAt(0);
		HttpServerApi::SendResponseUtf8(server->GetHttpRequestQueue(), httpPendingRequestId, pendingRequest);
		httpPendingRequestId = HTTP_NULL_ID;
	}
}

WString HttpServerConnection::SubmitResponse(PHTTP_REQUEST pRequest)
{
	auto body = server->GetUtf8Body(pRequest).Value();

	SPIN_LOCK(pendingRequestLock)
	{
		submittingResponse = true;
	}

	try
	{
		SPIN_LOCK(lockQueuedStrings)
		{
			if (callback)
			{
				callback->OnReadString(body);
			}
			else
			{
				queuedStrings.Add(body);
			}
		}
	}
	catch (...)
	{
		SPIN_LOCK(pendingRequestLock)
		{
			submittingResponse = false;
			responsesToSubmit.Clear();
		}
		throw;
	}

	WString responseToClient;
	SPIN_LOCK(pendingRequestLock)
	{
		submittingResponse = false;
		if (responsesToSubmit.Count() > 0)
		{
			responseToClient = responsesToSubmit[0];
			responsesToSubmit.RemoveAt(0);
			while (responsesToSubmit.Count() > 0)
			{
				pendingRequestsToSend.Add(responsesToSubmit[0]);
				responsesToSubmit.RemoveAt(0);
			}
		}
		else if (pendingRequestsToSend.Count() > 0)
		{
			responseToClient = pendingRequestsToSend[0];
			pendingRequestsToSend.RemoveAt(0);
		}
	}
	return responseToClient;
}

void HttpServerConnection::InstallCallback(INetworkProtocolCallback* _callback)
{
	CHECK_ERROR(!callback || !_callback, L"HttpServerConnection::InstallCallback only accepts one callback at a time.");
	if (_callback)
	{
		_callback->OnInstalled(this);
	}

	List<WString> strings;
	SPIN_LOCK(lockQueuedStrings)
	{
		callback = _callback;
		if (!callback) return;
		strings = std::move(queuedStrings);
	}
	for (const auto& str : strings)
	{
		_callback->OnReadString(str);
	}
}

void HttpServerConnection::BeginReadingLoopUnsafe()
{
	// Do nothing, HttpServer automatically handles this.
}

void HttpServerConnection::SendString(const WString& str)
{
	SPIN_LOCK(pendingRequestLock)
	{
		if (submittingResponse)
		{
			responsesToSubmit.Add(str);
		}
		else if (httpPendingRequestId != HTTP_NULL_ID)
		{
			ULONG result = HttpServerApi::SendResponse(server->GetHttpRequestQueue(), httpPendingRequestId, { 200, L"OK", str, HttpNetworkProtocolContentType });
			if (result == NO_ERROR)
			{
				httpPendingRequestId = HTTP_NULL_ID;
			}
			else if (result == ERROR_CONNECTION_INVALID || result == ERROR_OPERATION_ABORTED)
			{
				httpPendingRequestId = HTTP_NULL_ID;
				pendingRequestsToSend.Add(str);
			}
			else
			{
				CHECK_FAIL(L"HttpSendHttpResponse failed for responding /Request.");
			}
		}
		else
		{
			pendingRequestsToSend.Add(str);
		}
	}
}

void HttpServerConnection::Stop()
{
	auto holding = Ptr(this);
	if (server)
	{
		SPIN_LOCK(server->lockConnections)
		{
			server->connections.Remove(guid);
		}
		SPIN_LOCK(pendingRequestLock)
		{
			OnCancelCurrentHttpRequestForPendingRequest();
		}
		if (callback)
		{
			callback->OnDisconnected();
		}
	}
}

WString HttpServerConnection::GenerateNewGuid()
{
	RPC_STATUS status = -1;
	UUID guid;
	status = UuidCreate(&guid);
	CHECK_ERROR(status == RPC_S_OK, L"UuidCreate failed.");

	RPC_WSTR guidString = nullptr;
	status = UuidToString(&guid, &guidString);
	CHECK_ERROR(status == RPC_S_OK, L"UuidToString failed.");

	WString result = guidString;
	status = RpcStringFree(&guidString);
	CHECK_ERROR(status == RPC_S_OK, L"RpcStringFree failed.");
	return result;
}

/***********************************************************************
HttpServer (HttpServerApi)
***********************************************************************/

void HttpServer::OnHttpRequestReceived(PHTTP_REQUEST pRequest)
{
	bool isValidRequest = wcsncmp(pRequest->CookedUrl.pAbsPath, urlRequestPrefix.Buffer(), urlRequestPrefix.Length()) == 0;
	bool isValidResponse = wcsncmp(pRequest->CookedUrl.pAbsPath, urlResponsePrefix.Buffer(), urlResponsePrefix.Length()) == 0;

	auto FindExistingConnection = [=, this](const WString& guid)->Ptr<HttpServerConnection>
	{
		SPIN_LOCK(lockConnections)
		{
			vint index = connections.Keys().IndexOf(guid);
			if (index == -1)
			{
				HttpServerApi::SendResponse(GetHttpRequestQueue(), pRequest->RequestId, { 404, L"Unknown connection guid" });
			}
			else
			{
				return connections.Values()[index];
			}
		}
		return {};
	};

	if (pRequest->Verb == HttpVerbGET && pRequest->CookedUrl.pAbsPath == urlConnect)
	{
		auto newGuid = HttpServerConnection::GenerateNewGuid();
		auto connection = Ptr(new HttpServerConnection);
		connection->server = this;
		connection->guid = newGuid;
		SPIN_LOCK(lockConnections)
		{
			connections.Add(newGuid, connection);
		}
		auto result = OnClientConnected(connection.Obj());
		if (result == WaitForClientResult::Reject)
		{
			SPIN_LOCK(lockConnections)
			{
				connections.Remove(newGuid);
			}
			connection->server = nullptr;
			HttpServerApi::SendResponse(GetHttpRequestQueue(), pRequest->RequestId, { 404, L"Connection rejected" });
		}
		else
		{
			auto completeUrlRequest = WString::Unmanaged(HttpServerUrl_Request) + L"/" + newGuid;
			auto completeUrlResponse = WString::Unmanaged(HttpServerUrl_Response) + L"/" + newGuid;
			HttpServerApi::SendResponseUtf8(GetHttpRequestQueue(), pRequest->RequestId, CreateHttpNetworkProtocolConnectBody(completeUrlRequest, completeUrlResponse));
		}
	}
	else if (pRequest->Verb == HttpVerbPOST && isValidRequest)
	{
		auto guid = WString::Unmanaged(pRequest->CookedUrl.pAbsPath + urlRequestPrefix.Length());
		if (auto connection = FindExistingConnection(guid))
		{
			SPIN_LOCK(connection->pendingRequestLock)
			{
				connection->OnNewHttpRequestForPendingRequest(pRequest->RequestId);
			}
		}
	}
	else if (pRequest->Verb == HttpVerbPOST && isValidResponse)
	{
		auto guid = WString::Unmanaged(pRequest->CookedUrl.pAbsPath + urlResponsePrefix.Length());
		if (auto connection = FindExistingConnection(guid))
		{
			auto responseToClient = connection->SubmitResponse(pRequest);
			auto result = HttpServerApi::SendResponse(GetHttpRequestQueue(), pRequest->RequestId, { 200, L"OK", responseToClient, HttpNetworkProtocolContentType });
			CHECK_ERROR(
				result == NO_ERROR || result == ERROR_CONNECTION_INVALID || result == ERROR_OPERATION_ABORTED,
				L"HttpSendHttpResponse failed for responding /Response."
				);
		}
	}
	else
	{
		HttpServerApi::SendResponse(GetHttpRequestQueue(), pRequest->RequestId, { 404, L"Unknown URL" });
	}
}

void HttpServer::OnHttpServerStopping()
{
	List<Ptr<HttpServerConnection>> stoppingConnections;
	SPIN_LOCK(lockConnections)
	{
		for (auto connection : connections.Values())
		{
			stoppingConnections.Add(connection);
		}
		connections.Clear();
	}
	for (auto connection : stoppingConnections)
	{
		SPIN_LOCK(connection->pendingRequestLock)
		{
			connection->OnCancelCurrentHttpRequestForPendingRequest();
		}
		connection->server = nullptr;
	}
	for (auto connection : stoppingConnections)
	{
		if (connection->callback)
		{
			connection->callback->OnDisconnected();
		}
	}
}

/***********************************************************************
HttpServer
***********************************************************************/

HttpServer::HttpServer(const WString _baseUrl, vint port)
	: HttpServerApi(WString::Unmanaged(L"http://localhost:") + itow(port) + _baseUrl + L"/", true)
	, baseUrl(_baseUrl)
{
	urlConnect = baseUrl + HttpServerUrl_Connect;
	urlRequestPrefix = baseUrl + HttpServerUrl_Request + L"/";
	urlResponsePrefix = baseUrl + HttpServerUrl_Response + L"/";
}

HttpServer::~HttpServer()
{
	Stop();
}

WaitForClientResult HttpServer::OnClientConnected(INetworkProtocolConnection* connection)
{
	return WaitForClientResult::Accept;
}

void HttpServer::Start()
{
	HttpServerApi::Start();
}

void HttpServer::Stop()
{
	HttpServerApi::Stop();
}

bool HttpServer::IsStopped()
{
	return HttpServerApi::IsStopped();
}

}


/***********************************************************************
.\INTERPROCESS\WINDOWS\HTTPSERVERAPI.WINDOWS.CPP
***********************************************************************/

#ifndef VCZH_MSVC
static_assert(false, "Do not build this file for non-Windows applications.");
#endif

#pragma comment(lib, "Httpapi.lib")

namespace vl::inter_process::windows_http
{

using namespace vl::collections;

/***********************************************************************
HttpServerApi (ListenToHttpRequest)
***********************************************************************/

void HttpServerApi::OnHttpConnectionBrokenUnsafe()
{
	if (state == State::Running)
	{
		state = State::Stopping;
		OnHttpServerStopping();
	}
}

void HttpServerApi::OnHttpRequestReceivedUnsafe(PHTTP_REQUEST pRequest)
{
	if (state == State::Stopping)
	{
		SendResponse(httpRequestQueue, pRequest->RequestId, { 404, L"Server is stopping" });
		return;
	}

	if (respondToOptions && pRequest->Verb == HttpVerbOPTIONS)
	{
		SendOptionsResponse(httpRequestQueue, pRequest->RequestId);
	}
	else
	{
		OnHttpRequestReceived(pRequest);
	}
	ListenToHttpRequest();
}

ULONG HttpServerApi::ListenToHttpRequest_Init(OVERLAPPED* overlapped)
{
	ZeroMemory(&bufferRequest[0], bufferRequest.Count());

	ULONG result = HttpReceiveHttpRequest(
		httpRequestQueue,
		HTTP_NULL_ID,
		0,
		(PHTTP_REQUEST)&bufferRequest[0],
		(ULONG)bufferRequest.Count(),
		NULL,
		overlapped);

	return result;
}

ULONG HttpServerApi::ListenToHttpRequest_InitMoreData(ULONG* bytesReturned)
{
	HTTP_REQUEST_ID httpRequestIdReading = ((PHTTP_REQUEST)&bufferRequest[0])->RequestId;
	ZeroMemory(&bufferRequest[0], bufferRequest.Count());

	ULONG result = HttpReceiveHttpRequest(
		httpRequestQueue,
		httpRequestIdReading,
		0,
		(PHTTP_REQUEST)&bufferRequest[0],
		(ULONG)bufferRequest.Count(),
		bytesReturned,
		NULL);

	return result;
}

ULONG HttpServerApi::ListenToHttpRequest_OverlappedMoreData(vint expectedBufferSize)
{
	HTTP_REQUEST_ID httpRequestIdReading = ((PHTTP_REQUEST)&bufferRequest[0])->RequestId;
	bufferRequest.Resize(expectedBufferSize);
	ZeroMemory(&bufferRequest[0], bufferRequest.Count());

	ULONG bytesReturned = 0;
	ULONG result = HttpReceiveHttpRequest(
		httpRequestQueue,
		httpRequestIdReading,
		0,
		(PHTTP_REQUEST)&bufferRequest[0],
		(ULONG)bufferRequest.Count(),
		&bytesReturned,
		NULL);

	return result;
}

void HttpServerApi::ListenToHttpRequest()
{
	if (state == State::Stopping) return;

	ResetEvent(hEventRequest);
	ZeroMemory(&overlappedRequest, sizeof(overlappedRequest));
	overlappedRequest.hEvent = hEventRequest;

	ZeroMemory(&bufferRequest[0], sizeof(HTTP_REQUEST));
	PHTTP_REQUEST pRequest = (PHTTP_REQUEST)&bufferRequest[0];

	ULONG result = ListenToHttpRequest_Init(&overlappedRequest);
	if (result == ERROR_CONNECTION_INVALID || result == ERROR_OPERATION_ABORTED)
	{
		OnHttpConnectionBrokenUnsafe();
		return;
	}

	if (result == NO_ERROR)
	{
		OnHttpRequestReceivedUnsafe(pRequest);
		return;
	}

	if (result == ERROR_MORE_DATA)
	{
		ULONG bytesReturned = 0;
		result = ListenToHttpRequest_InitMoreData(&bytesReturned);
		if (result == ERROR_CONNECTION_INVALID || result == ERROR_OPERATION_ABORTED)
		{
			OnHttpConnectionBrokenUnsafe();
			return;
		}
		CHECK_ERROR(result == ERROR_MORE_DATA, L"HttpReceiveHttpRequest(#1) failed on unexpected result.");

		result = ListenToHttpRequest_OverlappedMoreData((vint)bytesReturned);
		if (result == ERROR_CONNECTION_INVALID || result == ERROR_OPERATION_ABORTED)
		{
			OnHttpConnectionBrokenUnsafe();
			return;
		}
		CHECK_ERROR(result == NO_ERROR, L"HttpReceiveHttpRequest(#2) failed on unexpected result.");

		PHTTP_REQUEST pRequest = (PHTTP_REQUEST)&bufferRequest[0];
		OnHttpRequestReceivedUnsafe(pRequest);
		return;
	}

	CHECK_ERROR(result == ERROR_IO_PENDING, L"HttpReceiveHttpRequest(#3) failed on unexpected result.");

	BOOL waitResult = RegisterWaitForSingleObject(
		&hWaitHandleRequest,
		hEventRequest,
		[](PVOID lpParameter, BOOLEAN TimerOrWaitFired)
		{
			auto self = (HttpServerApi*)lpParameter;
			struct PendingCallbackScope
			{
				HttpServerApi* server;

				PendingCallbackScope(HttpServerApi* _server)
					: server(_server)
				{
					server->BeginPendingCallback();
				}

				~PendingCallbackScope()
				{
					server->EndPendingCallback();
				}
			} pendingCallbackScope(self);

			auto waitHandle = std::atomic_ref<HANDLE>(self->hWaitHandleRequest).exchange(INVALID_HANDLE_VALUE);
			if (waitHandle != INVALID_HANDLE_VALUE)
			{
				UnregisterWait(waitHandle);
			}

			DWORD read = 0;
			BOOL result = GetOverlappedResult(self->httpRequestQueue, &self->overlappedRequest, &read, FALSE);
			if (result == TRUE)
			{
				PHTTP_REQUEST pRequest = (PHTTP_REQUEST)&self->bufferRequest[0];
				self->OnHttpRequestReceivedUnsafe(pRequest);
			}
			else
			{
				DWORD error = GetLastError();
				if (error == ERROR_CONNECTION_INVALID || error == ERROR_OPERATION_ABORTED)
				{
					self->OnHttpConnectionBrokenUnsafe();
					return;
				}
				CHECK_ERROR(error == ERROR_MORE_DATA, L"GetOverlappedResult(#4) failed on unexpected GetLastError.");
				CHECK_ERROR(self->bufferRequest.Count() < (vint)read, L"GetOverlappedResult(#5) failed on unexpected read size.");

				ULONG result = self->ListenToHttpRequest_OverlappedMoreData((vint)read);
				if (result == ERROR_CONNECTION_INVALID || result == ERROR_OPERATION_ABORTED)
				{
					self->OnHttpConnectionBrokenUnsafe();
					return;
				}
				CHECK_ERROR(result == NO_ERROR, L"HttpReceiveHttpRequest(#6) failed on unexpected result.");

				PHTTP_REQUEST pRequest = (PHTTP_REQUEST)&self->bufferRequest[0];
				self->OnHttpRequestReceivedUnsafe(pRequest);
			}
		},
		this,
		INFINITE,
		WT_EXECUTEONLYONCE);
	CHECK_ERROR(waitResult, L"RegisterWaitForSingleObject failed for HttpReceiveHttpRequest.");
	if (state == State::Stopping)
	{
		auto waitHandle = std::atomic_ref<HANDLE>(hWaitHandleRequest).exchange(INVALID_HANDLE_VALUE);
		if (waitHandle != INVALID_HANDLE_VALUE)
		{
			UnregisterWaitEx(waitHandle, INVALID_HANDLE_VALUE);
		}
	}
}

void HttpServerApi::BeginPendingCallback()
{
	if (pendingCallbacks++ == 0)
	{
		eventPendingCallbacks.Unsignal();
	}
}

void HttpServerApi::EndPendingCallback()
{
	if (--pendingCallbacks == 0)
	{
		eventPendingCallbacks.Signal();
	}
}

void HttpServerApi::OnHttpServerStopping()
{
}

HANDLE HttpServerApi::GetHttpRequestQueue() const
{
	return httpRequestQueue;
}

Nullable<WString> HttpServerApi::GetUtf8Body(PHTTP_REQUEST pRequest)
{
	ULONG bodyLength = 0;
	ULONG bodyReceived = 0;
	{
		auto& headerContentType = pRequest->Headers.KnownHeaders[HttpHeaderContentType];
		CHECK_ERROR(headerContentType.pRawValue != NULL, L"/Response missing Content-Type header.");
		CHECK_ERROR(
			strncmp((const char*)headerContentType.pRawValue, "application/json; charset=utf8", headerContentType.RawValueLength) == 0,
			L"/Response Content-Type header must be \"application/json; charset=utf8\".");
	}
	{
		auto& headerContentLength = pRequest->Headers.KnownHeaders[HttpHeaderContentLength];
		CHECK_ERROR(headerContentLength.pRawValue != NULL, L"/Response missing Content-Type header.");
		bodyLength = (ULONG)atoi(headerContentLength.pRawValue);
	}
	CHECK_ERROR(bodyLength > 0, L"/Response must contain body data.");

	Array<char8_t> bodyBuffer(bodyLength + 1);
	ZeroMemory(&bodyBuffer[0], bodyBuffer.Count() * sizeof(char8_t));
	ULONG bodyWritten = 0;
	for (USHORT i = 0; i < pRequest->EntityChunkCount; i++)
	{
		auto& chunk = pRequest->pEntityChunks[i];
		CHECK_ERROR(chunk.DataChunkType == HttpDataChunkFromMemory, L"/Response contains an unsupported body chunk.");
		auto chunkLength = chunk.FromMemory.BufferLength;
		CHECK_ERROR(bodyWritten + chunkLength <= bodyLength, L"/Response body is longer than Content-Length.");
		memcpy(&bodyBuffer[bodyWritten], chunk.FromMemory.pBuffer, chunkLength);
		bodyWritten += chunkLength;
	}
	if (pRequest->Flags & HTTP_REQUEST_FLAG_MORE_ENTITY_BODY_EXISTS)
	{
		ULONG result = NO_ERROR;
		result = HttpReceiveRequestEntityBody(
			httpRequestQueue,
			pRequest->RequestId,
			HTTP_RECEIVE_REQUEST_ENTITY_BODY_FLAG_FILL_BUFFER,
			&bodyBuffer[bodyWritten],
			bodyLength - bodyWritten,
			&bodyReceived,
			NULL);
		CHECK_ERROR(result == NO_ERROR || result == ERROR_HANDLE_EOF, L"HttpReceiveRequestEntityBody.");
		bodyWritten += bodyReceived;
	}
	CHECK_ERROR(bodyWritten == bodyLength, L"/Response body is shorter than Content-Length.");

	U8String bodyUtf8 = U8String::Unmanaged(&bodyBuffer[0]);
	return u8tow(bodyUtf8);
}

/***********************************************************************
HttpServerApi (Writing)
***********************************************************************/

void HttpServerApi::SendOptionsResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId)
{
	ULONG bytesSent = 0;
	HTTP_RESPONSE httpResponse;
	ZeroMemory(&httpResponse, sizeof(httpResponse));

	httpResponse.StatusCode = 200;

	static const char headerACAOName[] = "Access-Control-Allow-Origin";
	static const char headerACAMName[] = "Access-Control-Allow-Methods";
	static const char headerACAMValue[] = "GET, POST, OPTIONS";
	static const char headerACAHName[] = "Access-Control-Allow-Headers";
	static const char headerACAHValue[] = "Content-Type";
	static HTTP_UNKNOWN_HEADER unknownHeaders[] = { {
		sizeof(headerACAOName) - 1,
		1,
		headerACAOName,
		"*"
	},{
		sizeof(headerACAMName) - 1,
		sizeof(headerACAMValue) - 1,
		headerACAMName,
		headerACAMValue
	},{
		sizeof(headerACAHName) - 1,
		sizeof(headerACAHValue) - 1,
		headerACAHName,
		headerACAHValue
	} };
	httpResponse.Headers.UnknownHeaderCount = sizeof(unknownHeaders) / sizeof(HTTP_UNKNOWN_HEADER);
	httpResponse.Headers.pUnknownHeaders = unknownHeaders;

	ULONG result = NO_ERROR;
	result = HttpSendHttpResponse(
		httpRequestQueue,
		requestId,
		0,
		&httpResponse,
		NULL,
		&bytesSent,
		NULL,
		0,
		NULL,
		NULL);
	CHECK_ERROR(result == NO_ERROR, L"HttpSendHttpResponse failed (OPTIONS).");
}

ULONG HttpServerApi::SendResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId, const HttpServerResponse& response)
{
	ULONG bytesSent = 0;
	HTTP_RESPONSE httpResponse;
	HTTP_DATA_CHUNK httpResponseBody;
	ZeroMemory(&httpResponse, sizeof(httpResponse));
	ZeroMemory(&httpResponseBody, sizeof(httpResponseBody));

	httpResponse.StatusCode = (USHORT)response.statusCode;

	U8String reasonUtf8;
	if (response.reason != WString::Empty)
	{
		reasonUtf8 = wtou8(response.reason);
		httpResponse.pReason = (PCSTR)reasonUtf8.Buffer();
		httpResponse.ReasonLength = (USHORT)reasonUtf8.Length();
	}

	U8String bodyUtf8;
	if (response.body != WString::Empty)
	{
		bodyUtf8 = wtou8(response.body);
		httpResponse.EntityChunkCount = 1;
		httpResponse.pEntityChunks = &httpResponseBody;
		httpResponseBody.DataChunkType = HttpDataChunkFromMemory;
		httpResponseBody.FromMemory.pBuffer = (PVOID)bodyUtf8.Buffer();
		httpResponseBody.FromMemory.BufferLength = (ULONG)bodyUtf8.Length();
	}

	U8String contentTypeUtf8;
	if (response.contentType != WString::Empty)
	{
		contentTypeUtf8 = wtou8(response.contentType);
		httpResponse.Headers.KnownHeaders[HttpHeaderContentType].pRawValue = (PCSTR)contentTypeUtf8.Buffer();
		httpResponse.Headers.KnownHeaders[HttpHeaderContentType].RawValueLength = (USHORT)contentTypeUtf8.Length();
	}

	static const char headerACAOName[] = "Access-Control-Allow-Origin";
	static HTTP_UNKNOWN_HEADER unknownHeaders[] = { {
		sizeof(headerACAOName) - 1,
		1,
		headerACAOName,
		"*"
	} };
	httpResponse.Headers.UnknownHeaderCount = sizeof(unknownHeaders) / sizeof(HTTP_UNKNOWN_HEADER);
	httpResponse.Headers.pUnknownHeaders = unknownHeaders;

	ULONG result = NO_ERROR;
	result = HttpSendHttpResponse(
		httpRequestQueue,
		requestId,
		0,
		&httpResponse,
		NULL,
		&bytesSent,
		NULL,
		0,
		NULL,
		NULL);
	return result;
}

void HttpServerApi::SendResponseUtf8(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId, WString body)
{
	auto result = SendResponse(httpRequestQueue, requestId, { 200, WString::Unmanaged(L"OK"), body, HttpNetworkProtocolContentType });
	CHECK_ERROR(result == NO_ERROR, L"HttpSendHttpResponse failed for responding UTF-8 body.");
}

/***********************************************************************
HttpServerApi
***********************************************************************/

HttpServerApi::HttpServerApi(const WString& _urlPrefix, bool _respondToOptions)
	: bufferRequest(HttpRequestBufferInitSize)
	, urlPrefix(_urlPrefix)
	, respondToOptions(_respondToOptions)
{
	hEventRequest = CreateEvent(NULL, TRUE, TRUE, NULL);
	CHECK_ERROR(hEventRequest != NULL, L"HttpServerApi initialization failed on CreateEvent(hEventRequest).");
	CHECK_ERROR(eventPendingCallbacks.CreateManualUnsignal(true), L"HttpServerApi initialization failed on eventPendingCallbacks.CreateManualUnsignal.");

	ULONG result = NO_ERROR;

	result = HttpInitialize(
		HTTPAPI_VERSION_2,
		HTTP_INITIALIZE_SERVER,
		NULL);
	CHECK_ERROR(result == NO_ERROR, L"HttpInitialize failed.");

	result = HttpCreateRequestQueue(
		HTTPAPI_VERSION_2,
		NULL,
		NULL,
		0,
		&httpRequestQueue);
	CHECK_ERROR(result == NO_ERROR, L"HttpCreateRequestQueue failed.");

	result = HttpCreateServerSession(
		HTTPAPI_VERSION_2,
		&httpSessionId,
		0);
	CHECK_ERROR(result == NO_ERROR, L"HttpCreateServerSession failed.");

	result = HttpCreateUrlGroup(
		httpSessionId,
		&httpUrlGroupId,
		0);
	CHECK_ERROR(result == NO_ERROR, L"HttpCreateUrlGroup failed.");

	result = HttpAddUrlToUrlGroup(
		httpUrlGroupId,
		urlPrefix.Buffer(),
		0,
		0);
	CHECK_ERROR(result == NO_ERROR, L"HttpAddUrlToUrlGroup failed.");

	HTTP_BINDING_INFO bindingInfo;
	ZeroMemory(&bindingInfo, sizeof(bindingInfo));
	bindingInfo.Flags.Present = 1;
	bindingInfo.RequestQueueHandle = httpRequestQueue;

	result = HttpSetUrlGroupProperty(
		httpUrlGroupId,
		HttpServerBindingProperty,
		&bindingInfo,
		sizeof(bindingInfo));
	CHECK_ERROR(result == NO_ERROR, L"HttpSetUrlGroupProperty failed (HttpServerBindingProperty).");
}

HttpServerApi::~HttpServerApi()
{
	Stop();
	CloseHandle(hEventRequest);
}

void HttpServerApi::Start()
{
	CHECK_ERROR(state == State::Ready, L"HttpServerApi can only be started once.");
	state = State::Running;
	ListenToHttpRequest();
}

void HttpServerApi::Stop()
{
	state = State::Stopping;
	auto waitHandle = std::atomic_ref<HANDLE>(hWaitHandleRequest).exchange(INVALID_HANDLE_VALUE);
	if (waitHandle != INVALID_HANDLE_VALUE)
	{
		UnregisterWaitEx(waitHandle, INVALID_HANDLE_VALUE);
	}
	eventPendingCallbacks.Wait();

	OnHttpServerStopping();

	if (httpRequestQueue != INVALID_HANDLE_VALUE)
	{
		HttpCloseUrlGroup(httpUrlGroupId);
		HttpCloseServerSession(httpSessionId);
		HttpCloseRequestQueue(httpRequestQueue);
		httpRequestQueue = INVALID_HANDLE_VALUE;
		httpUrlGroupId = HTTP_NULL_ID;
		httpSessionId = HTTP_NULL_ID;

		HttpTerminate(
			HTTP_INITIALIZE_SERVER,
			NULL);
	}
}

bool HttpServerApi::IsStopped()
{
	return state == State::Stopping;
}

}


/***********************************************************************
.\INTERPROCESS\WINDOWS\NAMEDPIPE.WINDOWS.CPP
***********************************************************************/

namespace vl::inter_process::named_pipe
{

using namespace vl::console;
using namespace vl::collections;

class NamedPipeConnection::ReadWaitContext
{
public:
	NamedPipeConnection*							connection = nullptr;
	HANDLE											hWaitHandle = INVALID_HANDLE_VALUE;
	EventObject										eventRegistrationFinished;
	atomic_vint										callbackStarted = 0;

	ReadWaitContext()
	{
		CHECK_ERROR(eventRegistrationFinished.CreateManualUnsignal(false), L"ReadFile failed on eventRegistrationFinished.CreateManualUnsignal.");
	}
};

class NamedPipeServer::PendingConnection::ConnectWaitContext
{
public:
	Ptr<PendingConnection>							pendingConnection;
	HANDLE											hWaitHandle = INVALID_HANDLE_VALUE;
	EventObject										eventRegistrationFinished;
	atomic_vint										callbackStarted = 0;

	ConnectWaitContext(Ptr<PendingConnection> _pendingConnection)
		: pendingConnection(_pendingConnection)
	{
		CHECK_ERROR(eventRegistrationFinished.CreateManualUnsignal(false), L"ConnectNamedPipe failed on eventRegistrationFinished.CreateManualUnsignal.");
	}
};

/***********************************************************************
NamedPipeConnection (Reading)
***********************************************************************/

void NamedPipeConnection::BeginReadingUnsafe()
{
	if (firstRead)
	{
		streamReadFile.SeekFromBegin(0);
	}
}

void NamedPipeConnection::SubmitReadBufferUnsafe(vint bytes)
{
	streamReadFile.Write(&bufferReadFile[0], bytes);
}

void NamedPipeConnection::EndReadingUnsafe()
{
	vint32_t position = (vint32_t)streamReadFile.Position();
	streamReadFile.SeekFromBegin(0);
	CHECK_ERROR(position >= 2 * sizeof(vint32_t), L"ReadFile failed on incomplete message.");

	vint32_t bytes = 0;
	vint consumed = 0;
	consumed = streamReadFile.Read(&bytes, sizeof(bytes));
	CHECK_ERROR(consumed == sizeof(bytes), L"ReadFile failed on incomplete message.");

	if (bytes > position - (vint32_t)sizeof(bytes))
	{
		firstRead = false;
		streamReadFile.SeekFromBegin(position);
		return;
	}
	CHECK_ERROR(bytes == position - sizeof(bytes), L"ReadFile failed on corrupted message.");
	firstRead = true;

	Array<wchar_t> strBuffer;
	auto ReadSingleString = [&]()
	{
		vint32_t length = 0;
		consumed = streamReadFile.Read(&length, sizeof(length));
		CHECK_ERROR(consumed == sizeof(length) && streamReadFile.Position() <= position, L"ReadFile failed on incomplete message.");

		if (length == 0)
		{
			return WString::Empty;
		}
		else
		{
			strBuffer.Resize(length);
			consumed = streamReadFile.Read(&strBuffer[0], length * sizeof(wchar_t));
			CHECK_ERROR(consumed == length * sizeof(wchar_t) && streamReadFile.Position() <= position, L"ReadFile failed on incomplete message.");
			return WString::CopyFrom(&strBuffer[0], length);
		}
	};

	callback->OnReadString(ReadSingleString());

	CHECK_ERROR(streamReadFile.Position() == position, L"ReadFile failed on incomplete message.");
}

void NamedPipeConnection::BeginReadingLoopUnsafe()
{
	if (stopped) return;
RESTART_LOOP:
	{
		BeginReadingUnsafe();
		ResetEvent(hEventReadFile);
		ZeroMemory(&overlappedReadFile, sizeof(overlappedReadFile));
		overlappedReadFile.hEvent = hEventReadFile;
		DWORD read = 0;
		BOOL result = ReadFile(hPipe, &bufferReadFile[0], sizeof(BYTE) * MaxMessageSize, &read, &overlappedReadFile);

		if (result == TRUE)
		{
			SubmitReadBufferUnsafe((vint)read);
			EndReadingUnsafe();
			goto RESTART_LOOP;
		}

		DWORD error = GetLastError();
		if (error == ERROR_BROKEN_PIPE || error == ERROR_NO_DATA)
		{
			if (!stopped)
			{
				OnDisconnected();
			}
			return;
		}
		if (error == ERROR_INVALID_HANDLE)
		{
			if (!stopped)
			{
				OnLocalError(L"ReadFile failed because the named pipe was closed.");
				OnDisconnected();
			}
			return;
		}
		CHECK_ERROR(error == ERROR_MORE_DATA || error == ERROR_IO_PENDING, L"ReadFile failed on unexpected GetLastError.");

		auto context = new ReadWaitContext;
		context->connection = this;
		BeginPendingCallback();

		ReadWaitContext* expectedContext = nullptr;
		if (stopped || !readWaitContext.compare_exchange_strong(expectedContext, context))
		{
			EndPendingCallback();
			delete context;
			return;
		}

		BOOL waitResult = RegisterWaitForSingleObject(
			&context->hWaitHandle,
			hEventReadFile,
			[](PVOID lpParameter, BOOLEAN TimerOrWaitFired)
			{
				auto context = (ReadWaitContext*)lpParameter;
				context->callbackStarted = 1;

				auto self = context->connection;
				ReadWaitContext* expectedContext = context;
				bool ownsContext = self->readWaitContext.compare_exchange_strong(expectedContext, nullptr);

				auto finalize = [=]()
				{
					if (ownsContext)
					{
						context->eventRegistrationFinished.Wait();
						UnregisterWait(context->hWaitHandle);
					}
					self->EndPendingCallback();
					if (ownsContext)
					{
						delete context;
					}
				};

				DWORD read = 0;
				BOOL result = GetOverlappedResult(self->hPipe, &self->overlappedReadFile, &read, FALSE);
				if (result == TRUE)
				{
					self->SubmitReadBufferUnsafe((vint)read);
					self->EndReadingUnsafe();
				}
				else
				{
					DWORD error = GetLastError();
					if (error == ERROR_BROKEN_PIPE || error == ERROR_NO_DATA)
					{
						if (!self->stopped)
						{
							self->OnDisconnected();
						}
						finalize();
						return;
					}
					if (error == ERROR_OPERATION_ABORTED || error == ERROR_INVALID_HANDLE)
					{
						if (!self->stopped)
						{
							self->OnLocalError(L"GetOverlappedResult(ReadFile) failed because the named pipe was closed.");
							self->OnDisconnected();
						}
						finalize();
						return;
					}
					CHECK_ERROR(error == ERROR_MORE_DATA, L"GetOverlappedResult(ReadFile) failed on unexpected GetLastError.");
					self->SubmitReadBufferUnsafe((vint)read);
				}
				if (!self->stopped)
				{
					self->BeginReadingLoopUnsafe();
				}
				finalize();
			},
			context,
			INFINITE,
			WT_EXECUTEONLYONCE);

		context->eventRegistrationFinished.Signal();
		if (!waitResult)
		{
			expectedContext = context;
			if (readWaitContext.compare_exchange_strong(expectedContext, nullptr))
			{
				EndPendingCallback();
				delete context;
				CHECK_FAIL(L"RegisterWaitForSingleObject failed for ReadFile.");
			}
		}
	}
}

/***********************************************************************
NamedPipeConnection (Writing)
***********************************************************************/

vint32_t NamedPipeConnection::WriteInt32ToStream(vint32_t number)
{
	return (vint32_t)streamWriteFile.Write(&number, sizeof(number));
}

vint32_t NamedPipeConnection::WriteStringToStream(const WString& str)
{
	vint32_t bytes = 0;
	vint32_t count = (vint32_t)str.Length();
	bytes += (vint32_t)streamWriteFile.Write(&count, sizeof(count));
	if (count > 0)
	{
		bytes += (vint32_t)streamWriteFile.Write((void*)str.Buffer(), sizeof(wchar_t) * str.Length());
	}
	return bytes;
}

void NamedPipeConnection::BeginSendStream()
{
	vint32_t bytes = 0;
	streamWriteFile.SeekFromBegin(0);
	streamWriteFile.Write(&bytes, sizeof(bytes));
}

void NamedPipeConnection::EndSendStream(vint32_t bytes)
{
	streamWriteFile.SeekFromBegin(0);
	WriteInt32ToStream(bytes);

	vint32_t length = bytes + sizeof(bytes);
	for (vint i = 0; i < (length + MaxMessageSize - 1) / MaxMessageSize; i++)
	{
		vint offset = i * MaxMessageSize;
		vint bytesToSend = length >= (i + 1) * MaxMessageSize ? MaxMessageSize : length % MaxMessageSize;
		auto buffer = (LPCVOID)((char*)streamWriteFile.GetInternalBuffer() + offset);

		ResetEvent(hEventWriteFile);
		ZeroMemory(&overlappedWriteFile, sizeof(overlappedWriteFile));
		overlappedWriteFile.hEvent = hEventWriteFile;
		DWORD written = 0;
		BOOL result = WriteFile(hPipe, buffer, (DWORD)bytesToSend, NULL, &overlappedWriteFile);
		if (result == FALSE)
		{
			auto error = GetLastError();
			if (error == ERROR_BROKEN_PIPE || error == ERROR_INVALID_HANDLE)
			{
				if (!stopped)
				{
					OnLocalError(L"WriteFile failed because the named pipe was closed.");
				}
				OnDisconnected();
				return;
			}
			CHECK_ERROR(error == ERROR_IO_PENDING, L"WriteFile failed on unexpected GetLastError.");
			WaitForSingleObject(hEventWriteFile, INFINITE);
			result = GetOverlappedResult(hPipe, &overlappedWriteFile, &written, FALSE);
			if (result == FALSE)
			{
				error = GetLastError();
				if (error == ERROR_BROKEN_PIPE || error == ERROR_INVALID_HANDLE || error == ERROR_OPERATION_ABORTED)
				{
					if (!stopped)
					{
						OnLocalError(L"GetOverlappedResult(WriteFile) failed because the named pipe was closed.");
					}
					OnDisconnected();
					return;
				}
				CHECK_FAIL(L"GetOverlappedResult(WriteFile) failed on unexpected GetLastError.");
			}
		}
		else
		{
			written = (DWORD)bytesToSend;
		}
		CHECK_ERROR(written == (DWORD)bytesToSend, L"WriteFile failed to write all data.");
	}
}

void NamedPipeConnection::SendString(const WString& str)
{
	if (stopped) return;
	SPIN_LOCK(lockWrite)
	{
		vint32_t bytes = 0;
		BeginSendStream();
		bytes += WriteStringToStream(str);
		EndSendStream(bytes);
	}
}

/***********************************************************************
NamedPipeConnection
***********************************************************************/

void NamedPipeConnection::OnLocalError(const WString& errorMessage)
{
	if (callback)
	{
		callback->OnLocalError(errorMessage, true);
	}
}

void NamedPipeConnection::OnDisconnected()
{
	if (callback)
	{
		callback->OnDisconnected();
	}
	auto owningServer = server;
	if (owningServer && pendingCallbacks == 0)
	{
		SPIN_LOCK(owningServer->lockConnections)
		{
			if (server == owningServer)
			{
				owningServer->connections.Remove(this);
				server = nullptr;
			}
		}
	}
}

NamedPipeConnection::NamedPipeConnection(HANDLE _hPipe)
	: bufferReadFile(MaxMessageSize)
{
	hPipe = _hPipe;

	hEventReadFile = CreateEvent(NULL, TRUE, TRUE, NULL);
	CHECK_ERROR(hEventReadFile != NULL, L"NamedPipeConnection initialization failed on CreateEvent(hEventReadFile).");

	hEventWriteFile = CreateEvent(NULL, TRUE, TRUE, NULL);
	CHECK_ERROR(hEventWriteFile != NULL, L"NamedPipeConnection initialization failed on CreateEvent(hEventWriteFile).");

	CHECK_ERROR(eventPendingCallbacks.CreateManualUnsignal(true), L"NamedPipeConnection initialization failed on eventPendingCallbacks.CreateManualUnsignal.");
}

NamedPipeConnection::~NamedPipeConnection()
{
	Stop();
	CloseHandle(hEventReadFile);
	CloseHandle(hEventWriteFile);
}

void NamedPipeConnection::InstallCallback(INetworkProtocolCallback* _callback)
{
	CHECK_ERROR(!callback || !_callback, L"NamedPipeConnection::InstallCallback only accepts one callback at a time.");
	callback = _callback;
	if (!callback) return;
	callback->OnInstalled(this);
}

void NamedPipeConnection::Stop()
{
	stopped = 1;
	SPIN_LOCK(lockWrite)
	{
		if (hPipe != INVALID_HANDLE_VALUE)
		{
			CancelIoEx(hPipe, NULL);
		}
	}

	ReadWaitContext* context = readWaitContext.exchange(nullptr);
	if (context)
	{
		context->eventRegistrationFinished.Wait();
		if (context->hWaitHandle != INVALID_HANDLE_VALUE)
		{
			UnregisterWaitEx(context->hWaitHandle, INVALID_HANDLE_VALUE);
		}
		if (context->callbackStarted == 0)
		{
			EndPendingCallback();
		}
		delete context;
	}
	eventPendingCallbacks.Wait();

	SPIN_LOCK(lockWrite)
	{
		if (hPipe != INVALID_HANDLE_VALUE)
		{
			CloseHandle(hPipe);
			hPipe = INVALID_HANDLE_VALUE;
		}
	}
}

void NamedPipeConnection::BeginPendingCallback()
{
	if (pendingCallbacks++ == 0)
	{
		eventPendingCallbacks.Unsignal();
	}
}

void NamedPipeConnection::EndPendingCallback()
{
	if (--pendingCallbacks == 0)
	{
		eventPendingCallbacks.Signal();
	}
}

/***********************************************************************
NamedPipeServer::PendingConnection
***********************************************************************/

NamedPipeServer::PendingConnection::PendingConnection(NamedPipeServer* _server, Ptr<NamedPipeConnection> _connection)
	: server(_server)
	, connection(_connection)
{
	ZeroMemory(&overlappedConnect, sizeof(overlappedConnect));
	hEventConnect = CreateEvent(NULL, TRUE, FALSE, NULL);
	CHECK_ERROR(hEventConnect != NULL, L"ConnectNamedPipe failed on CreateEvent.");
	overlappedConnect.hEvent = hEventConnect;
	CHECK_ERROR(eventPendingCallbacks.CreateManualUnsignal(true), L"ConnectNamedPipe failed on eventPendingCallbacks.CreateManualUnsignal.");
}

NamedPipeServer::PendingConnection::~PendingConnection()
{
	Stop();
	CloseHandle(hEventConnect);
}

void NamedPipeServer::PendingConnection::Stop()
{
	server = nullptr;
	ConnectWaitContext* context = connectWaitContext.exchange(nullptr);
	if (context)
	{
		context->eventRegistrationFinished.Wait();
		if (context->hWaitHandle != INVALID_HANDLE_VALUE)
		{
			UnregisterWaitEx(context->hWaitHandle, INVALID_HANDLE_VALUE);
		}
		if (context->callbackStarted == 0)
		{
			EndPendingCallback();
		}
		delete context;
	}
	eventPendingCallbacks.Wait();
	if (connection)
	{
		connection->Stop();
		connection = nullptr;
	}
}

void NamedPipeServer::PendingConnection::BeginPendingCallback()
{
	if (pendingCallbacks++ == 0)
	{
		eventPendingCallbacks.Unsignal();
	}
}

void NamedPipeServer::PendingConnection::EndPendingCallback()
{
	if (--pendingCallbacks == 0)
	{
		eventPendingCallbacks.Signal();
	}
}

/***********************************************************************
NamedPipeServer
***********************************************************************/

HANDLE NamedPipeServer::ServerCreatePipe(const WString& pipeName)
{
	HANDLE hPipe = CreateNamedPipe(
		(L"\\\\.\\pipe\\" + pipeName).Buffer(),
		PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
		PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_REJECT_REMOTE_CLIENTS,
		PIPE_UNLIMITED_INSTANCES,
		65536,
		65536,
		6000,
		NULL);
	CHECK_ERROR(hPipe != INVALID_HANDLE_VALUE, L"CreateNamedPipe failed.");
	return hPipe;
}

NamedPipeServer::NamedPipeServer(const WString& _pipeName)
	: pipeName(_pipeName)
{
}

NamedPipeServer::~NamedPipeServer()
{
	Stop();
}

void NamedPipeServer::BeginListening()
{
	Ptr<PendingConnection> pendingConnection;
	SPIN_LOCK(lockConnections)
	{
		if (!started || stopped)
		{
			return;
		}
	}

	auto connection = Ptr(new NamedPipeConnection(ServerCreatePipe(pipeName)));
	pendingConnection = Ptr(new PendingConnection(this, connection));

	BOOL result = ConnectNamedPipe(connection->hPipe, &pendingConnection->overlappedConnect);
	DWORD error = result ? ERROR_SUCCESS : GetLastError();
	switch (error)
	{
	case ERROR_SUCCESS:
	case ERROR_PIPE_CONNECTED:
		CompletePendingConnection(pendingConnection, true);
		break;
	case ERROR_IO_PENDING:
		SPIN_LOCK(lockConnections)
		{
			if (stopped)
			{
				pendingConnection->Stop();
				return;
			}
			pendingConnections.Add(pendingConnection);
			auto context = new PendingConnection::ConnectWaitContext(pendingConnection);
			pendingConnection->BeginPendingCallback();
			PendingConnection::ConnectWaitContext* expectedContext = nullptr;
			CHECK_ERROR(pendingConnection->connectWaitContext.compare_exchange_strong(expectedContext, context), L"ConnectNamedPipe found an existing wait context.");
			BOOL waitResult = RegisterWaitForSingleObject(
				&context->hWaitHandle,
				pendingConnection->hEventConnect,
				[](PVOID lpParameter, BOOLEAN TimerOrWaitFired)
				{
					auto context = (PendingConnection::ConnectWaitContext*)lpParameter;
					context->callbackStarted = 1;

					auto pendingConnection = context->pendingConnection;
					PendingConnection::ConnectWaitContext* expectedContext = context;
					bool ownsContext = pendingConnection->connectWaitContext.compare_exchange_strong(expectedContext, nullptr);

					auto finalize = [=]()
					{
						if (ownsContext)
						{
							context->eventRegistrationFinished.Wait();
							UnregisterWait(context->hWaitHandle);
						}
						pendingConnection->EndPendingCallback();
						if (ownsContext)
						{
							delete context;
						}
					};

					DWORD transferred = 0;
					BOOL result = GetOverlappedResult(pendingConnection->connection->hPipe, &pendingConnection->overlappedConnect, &transferred, FALSE);
					if (result == TRUE)
					{
						if (pendingConnection->server)
						{
							pendingConnection->server->CompletePendingConnection(pendingConnection.Obj(), true);
						}
					}
					else
					{
						auto error = GetLastError();
						if (error == ERROR_OPERATION_ABORTED || error == ERROR_INVALID_HANDLE || error == ERROR_BROKEN_PIPE || error == ERROR_NO_DATA)
						{
							if (pendingConnection->server)
							{
								pendingConnection->server->CompletePendingConnection(pendingConnection.Obj(), false);
							}
							finalize();
							return;
						}
						CHECK_FAIL(L"GetOverlappedResult(ConnectNamedPipe) failed on unexpected GetLastError.");
					}
					finalize();
				},
				context,
				INFINITE,
				WT_EXECUTEONLYONCE);

			context->eventRegistrationFinished.Signal();
			if (!waitResult)
			{
				expectedContext = context;
				if (pendingConnection->connectWaitContext.compare_exchange_strong(expectedContext, nullptr))
				{
					pendingConnection->EndPendingCallback();
					delete context;
					CHECK_FAIL(L"RegisterWaitForSingleObject failed for ConnectNamedPipe.");
				}
			}
		}
		break;
	default:
		CHECK_FAIL(L"ConnectNamedPipe failed on unexpected GetLastError.");
	}
}

void NamedPipeServer::CompletePendingConnection(Ptr<PendingConnection> pendingConnection, bool connected)
{
	auto connection = pendingConnection->connection;
	bool shouldListen = false;
	bool shouldNotify = false;

	{
		SPIN_LOCK(lockConnections)
		{
			pendingConnections.Remove(pendingConnection.Obj());
			if (started && !stopped)
			{
				shouldListen = true;
				if (connected)
				{
					connection->server = this;
					connections.Add(connection);
					shouldNotify = true;
					pendingConnection->connection = nullptr;
					pendingConnection->server = nullptr;
				}
			}
		}
	}

	if (shouldListen)
	{
		BeginListening();
	}

	if (shouldNotify)
	{
		auto result = OnClientConnected(connection.Obj());
		if (result == WaitForClientResult::Reject)
		{
			SPIN_LOCK(lockConnections)
			{
				connections.Remove(connection.Obj());
			}
			connection->server = nullptr;
			connection->Stop();
		}
	}
	else if (connection)
	{
		connection->Stop();
	}
}

void NamedPipeServer::CompletePendingConnection(PendingConnection* pendingConnection, bool connected)
{
	Ptr<PendingConnection> holding;
	{
		SPIN_LOCK(lockConnections)
		{
			for (vint i = 0; i < pendingConnections.Count(); i++)
			{
				if (pendingConnections[i].Obj() == pendingConnection)
				{
					holding = pendingConnections[i];
					break;
				}
			}
		}
	}
	if (holding)
	{
		CompletePendingConnection(holding, connected);
	}
}

WaitForClientResult NamedPipeServer::OnClientConnected(INetworkProtocolConnection* connection)
{
	return WaitForClientResult::Accept;
}

void NamedPipeServer::Start()
{
	{
		SPIN_LOCK(lockConnections)
		{
			CHECK_ERROR(!started, L"NamedPipeServer has already started.");
			CHECK_ERROR(!stopped, L"NamedPipeServer has stopped.");
			started = true;
		}
	}
	BeginListening();
}

void NamedPipeServer::Stop()
{
	List<Ptr<NamedPipeConnection>> stoppingConnections;
	List<Ptr<PendingConnection>> stoppingPendingConnections;
	SPIN_LOCK(lockConnections)
	{
		if (!stopped)
		{
			started = false;
			stopped = true;
			for (auto connection : connections)
			{
				connection->server = nullptr;
				stoppingConnections.Add(connection);
			}
			for (auto pendingConnection : pendingConnections)
			{
				stoppingPendingConnections.Add(pendingConnection);
			}
			connections.Clear();
			pendingConnections.Clear();
		}
	}

	for (auto pendingConnection : stoppingPendingConnections)
	{
		pendingConnection->Stop();
	}
	for (auto connection : stoppingConnections)
	{
		connection->Stop();
	}
}

bool NamedPipeServer::IsStopped()
{
	bool result = false;
	SPIN_LOCK(lockConnections)
	{
		result = stopped;
	}
	return result;
}

/***********************************************************************
NamedPipeClient
***********************************************************************/

HANDLE NamedPipeClient::ClientCreatePipe(const WString& pipeName)
{
	auto fullPipeName = L"\\\\.\\pipe\\" + pipeName;
	auto start = GetTickCount64();
	while (true)
	{
		HANDLE hPipe = CreateFile(
			fullPipeName.Buffer(),
			GENERIC_READ | GENERIC_WRITE,
			0,
			NULL,
			OPEN_EXISTING,
			FILE_FLAG_OVERLAPPED,
			NULL);
		if (hPipe != INVALID_HANDLE_VALUE)
		{
			return hPipe;
		}

		auto error = GetLastError();
		CHECK_ERROR(error == ERROR_FILE_NOT_FOUND || error == ERROR_PIPE_BUSY, L"CreateFile failed.");
		CHECK_ERROR(GetTickCount64() - start < 6000, L"CreateFile failed because the pipe server did not become available.");
		if (error == ERROR_PIPE_BUSY)
		{
			WaitNamedPipe(fullPipeName.Buffer(), 100);
		}
		else
		{
			Thread::Sleep(1);
		}
	}
}

NamedPipeClient::NamedPipeClient(const WString& _pipeName)
	: NamedPipeConnection(ClientCreatePipe(_pipeName))
{
}

NamedPipeClient::~NamedPipeClient()
{
}

INetworkProtocolConnection* NamedPipeClient::GetConnection()
{
	return this;
}

void NamedPipeClient::WaitForServer()
{
	status = ClientStatus::WaitingForServer;
	DWORD dwPipeMode = PIPE_READMODE_MESSAGE;
	BOOL bSucceeded = SetNamedPipeHandleState(
		hPipe,
		&dwPipeMode,
		NULL,
		NULL);
	CHECK_ERROR(bSucceeded, L"SetNamedPipeHandleState failed.");
	status = ClientStatus::Connected;
	if (callback)
	{
		callback->OnConnected();
	}
}

ClientStatus NamedPipeClient::GetStatus()
{
	return status;
}

void NamedPipeClient::Stop()
{
	NamedPipeConnection::Stop();
	status = ClientStatus::Disconnected;
}

}


/***********************************************************************
.\INTERPROCESS\WINDOWS\NETWORKPROTOCOL.WINDOWS.CPP
***********************************************************************/

#pragma comment(lib, "httpapi.lib")
#pragma comment(lib, "rpcrt4.lib")

/***********************************************************************
.\TUI\TUI.WINDOWS.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/

#define _WINSOCKAPI_
#include <exception>

#ifndef VCZH_MSVC
static_assert(false, "Do not build this file for non-Windows applications.");
#endif

using namespace vl;
using namespace vl::collections;

namespace vl
{
	namespace console
	{
		vint TUI::MeasureChar(char32_t code)
		{
#define ERROR_MESSAGE_PREFIX L"vl::console::TUI::MeasureChar(char32_t)#"
			if (!tui_internal::IsScalar(code)) return 0;

			wchar_t text[2];
			auto length = 1;
			if (code <= 0xFFFF)
			{
				text[0] = (wchar_t)code;
			}
			else
			{
				code -= 0x10000;
				text[0] = (wchar_t)(0xD800 + (code >> 10));
				text[1] = (wchar_t)(0xDC00 + (code & 0x3FF));
				length = 2;
			}

			WORD ctype1[2] = {};
			WORD ctype3[2] = {};
			CHECK_ERROR(
				GetStringTypeW(CT_CTYPE1, text, length, ctype1) &&
				GetStringTypeW(CT_CTYPE3, text, length, ctype3),
				ERROR_MESSAGE_PREFIX L"Failed to query character types."
				);
			if (ctype1[0] & C1_CNTRL) return 0;
			if (ctype3[0] & (C3_NONSPACING | C3_DIACRITIC | C3_VOWELMARK)) return 0;
			if (ctype3[0] & C3_HALFWIDTH) return 1;
			if (length == 2 || (ctype3[0] & (C3_FULLWIDTH | C3_IDEOGRAPH | C3_HIRAGANA | C3_KATAKANA))) return 2;
#undef ERROR_MESSAGE_PREFIX
			return 1;
		}

		namespace tui_internal
		{
			void WriteConsoleAll(HANDLE handle, const wchar_t* text, vint length)
			{
				vint written = 0;
				while (written < length)
				{
					DWORD count = 0;
					CHECK_ERROR(WriteConsoleW(handle, text + written, (DWORD)(length - written), &count, nullptr), L"vl::console::TUI Windows backend failed to write terminal output.");
					CHECK_ERROR(count > 0, L"vl::console::TUI Windows backend made no progress while writing terminal output.");
					written += count;
				}
			}

			vint AnsiToWindowsColor(vint color)
			{
				return (color & 8) | ((color & 1) << 2) | (color & 2) | ((color & 4) >> 2);
			}

			class WindowsTuiBackend : public unittest::ITuiBackend
			{
			private:
				HANDLE						inputHandle = INVALID_HANDLE_VALUE;
				HANDLE						originalOutputHandle = INVALID_HANDLE_VALUE;
				HANDLE						outputHandle = INVALID_HANDLE_VALUE;
				HANDLE						classicOutputHandle = INVALID_HANDLE_VALUE;
				DWORD						inputMode = 0;
				DWORD						outputMode = 0;
				CONSOLE_CURSOR_INFO			cursorInfo = {};
				CONSOLE_SCREEN_BUFFER_INFOEX	screenInfo = {};
				CONSOLE_SCREEN_BUFFER_INFO	originalGeometry = {};
				List<unittest::TuiBackendEvent> pendingEvents;
				DWORD						mouseButtons = 0;
				vint						viewportWidth = 0;
				vint						viewportHeight = 0;
				bool						started = false;
				bool						inputModeChanged = false;
				bool						outputModeChanged = false;
				bool						cursorInfoSaved = false;
				bool						geometrySaved = false;
				bool						usingVt = false;

				void SetConsoleGeometry(HANDLE handle, COORD bufferSize, SMALL_RECT window)
				{
					CONSOLE_SCREEN_BUFFER_INFO current = {};
					CHECK_ERROR(GetConsoleScreenBufferInfo(handle, &current), L"vl::console::TUI Windows backend failed to query console geometry.");
					auto currentWidth = (SHORT)(current.srWindow.Right - current.srWindow.Left + 1);
					auto currentHeight = (SHORT)(current.srWindow.Bottom - current.srWindow.Top + 1);
					auto temporaryWidth = currentWidth < bufferSize.X ? currentWidth : bufferSize.X;
					auto temporaryHeight = currentHeight < bufferSize.Y ? currentHeight : bufferSize.Y;
					SMALL_RECT temporary = { 0, 0, (SHORT)(temporaryWidth - 1), (SHORT)(temporaryHeight - 1) };
					if (current.srWindow.Left != temporary.Left ||
						current.srWindow.Top != temporary.Top ||
						current.srWindow.Right != temporary.Right ||
						current.srWindow.Bottom != temporary.Bottom)
					{
						CHECK_ERROR(SetConsoleWindowInfo(handle, TRUE, &temporary), L"vl::console::TUI Windows backend failed to prepare the console window for resizing.");
					}
					if (current.dwSize.X != bufferSize.X || current.dwSize.Y != bufferSize.Y)
					{
						CHECK_ERROR(SetConsoleScreenBufferSize(handle, bufferSize), L"vl::console::TUI Windows backend failed to resize the console screen buffer.");
					}
					if (temporary.Left != window.Left ||
						temporary.Top != window.Top ||
						temporary.Right != window.Right ||
						temporary.Bottom != window.Bottom)
					{
						CHECK_ERROR(SetConsoleWindowInfo(handle, TRUE, &window), L"vl::console::TUI Windows backend failed to resize the console window.");
					}
				}

				void QueueResize(vint width, vint height)
				{
					unittest::TuiBackendEvent event;
					event.type = unittest::TuiBackendEventType::Resize;
					event.width = width;
					event.height = height;
					pendingEvents.Add(event);
				}

				void SynchronizeViewport(bool queueEvent)
				{
					CONSOLE_SCREEN_BUFFER_INFO info = {};
					CHECK_ERROR(GetConsoleScreenBufferInfo(outputHandle, &info), L"vl::console::TUI Windows backend failed to query the console viewport.");
					auto width = (vint)(info.srWindow.Right - info.srWindow.Left + 1);
					auto height = (vint)(info.srWindow.Bottom - info.srWindow.Top + 1);
					CHECK_ERROR(width > 0 && height > 0, L"vl::console::TUI Windows backend received an invalid console viewport.");
					auto changed = width != viewportWidth || height != viewportHeight;
					COORD bufferSize = { (SHORT)width, (SHORT)height };
					SMALL_RECT window = { 0, 0, (SHORT)(width - 1), (SHORT)(height - 1) };
					if (info.dwSize.X != bufferSize.X ||
						info.dwSize.Y != bufferSize.Y ||
						info.srWindow.Left != 0 ||
						info.srWindow.Top != 0)
					{
						SetConsoleGeometry(outputHandle, bufferSize, window);
					}
					viewportWidth = width;
					viewportHeight = height;
					if (queueEvent && changed)
					{
						QueueResize(width, height);
					}
				}

				TuiMouseInfo GetMouseInfo(const MOUSE_EVENT_RECORD& record)
				{
					CONSOLE_SCREEN_BUFFER_INFO info = {};
					CHECK_ERROR(GetConsoleScreenBufferInfo(outputHandle, &info), L"vl::console::TUI Windows backend failed to query the console viewport.");
					TuiMouseInfo result;
					result.x = record.dwMousePosition.X - info.srWindow.Left;
					result.y = record.dwMousePosition.Y - info.srWindow.Top;
					result.ctrl = (record.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0;
					result.shift = (record.dwControlKeyState & SHIFT_PRESSED) != 0;
					result.alt = (record.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0;
					result.left = (record.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) != 0;
					result.middle = (record.dwButtonState & FROM_LEFT_2ND_BUTTON_PRESSED) != 0;
					result.right = (record.dwButtonState & RIGHTMOST_BUTTON_PRESSED) != 0;
					return result;
				}

				TuiKeyInfo GetKeyInfo(const KEY_EVENT_RECORD& record)
				{
					TuiKeyInfo result;
					result.ctrl = (record.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0;
					result.shift = (record.dwControlKeyState & SHIFT_PRESSED) != 0;
					result.alt = (record.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0;
					result.capslock = (record.dwControlKeyState & CAPSLOCK_ON) != 0;
					result.autoRepeatKeyDown = record.bKeyDown && record.wRepeatCount > 1;
					return result;
				}

				void QueueChar(wchar_t code, const KEY_EVENT_RECORD& record)
				{
					unittest::TuiBackendEvent event;
					event.type = unittest::TuiBackendEventType::Char;
					event.charInfo.code = code;
					event.charInfo.ctrl = (record.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0;
					event.charInfo.shift = (record.dwControlKeyState & SHIFT_PRESSED) != 0;
					event.charInfo.alt = (record.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0;
					event.charInfo.capslock = (record.dwControlKeyState & CAPSLOCK_ON) != 0;
					pendingEvents.Add(event);
				}

				void DecodeKey(const KEY_EVENT_RECORD& record)
				{
					unittest::TuiBackendEvent keyEvent;
					keyEvent.type = record.bKeyDown ? unittest::TuiBackendEventType::KeyDown : unittest::TuiBackendEventType::KeyUp;
					keyEvent.keyInfo = GetKeyInfo(record);
					pendingEvents.Add(keyEvent);

					if (!record.bKeyDown || record.uChar.UnicodeChar == 0) return;
					auto codeUnit = record.uChar.UnicodeChar;
					auto repeat = record.wRepeatCount == 0 ? 1 : record.wRepeatCount;
					for (vint i = 0; i < repeat; i++)
					{
						QueueChar(codeUnit, record);
					}
				}

				void QueueMouseButton(unittest::TuiBackendEventType type, TuiMouseButton button, const TuiMouseInfo& info)
				{
					unittest::TuiBackendEvent event;
					event.type = type;
					event.mouseButton = button;
					event.mouseInfo = info;
					pendingEvents.Add(event);
				}

				void DecodeMouse(const MOUSE_EVENT_RECORD& record)
				{
					auto info = GetMouseInfo(record);
					if (record.dwEventFlags == MOUSE_MOVED)
					{
						unittest::TuiBackendEvent event;
						event.type = unittest::TuiBackendEventType::MouseMove;
						event.mouseInfo = info;
						pendingEvents.Add(event);
					}
					else if (record.dwEventFlags == MOUSE_WHEELED || record.dwEventFlags == MOUSE_HWHEELED)
					{
						unittest::TuiBackendEvent event;
						event.type = record.dwEventFlags == MOUSE_WHEELED ? unittest::TuiBackendEventType::MouseVerticalWheel : unittest::TuiBackendEventType::MouseHorizontalWheel;
						event.mouseInfo = info;
						event.mouseInfo.wheel = (SHORT)HIWORD(record.dwButtonState);
						pendingEvents.Add(event);
					}
					else if (record.dwEventFlags == DOUBLE_CLICK)
					{
						auto button = (record.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) ? TuiMouseButton::Left
							: (record.dwButtonState & FROM_LEFT_2ND_BUTTON_PRESSED) ? TuiMouseButton::Middle
							: TuiMouseButton::Right;
						QueueMouseButton(unittest::TuiBackendEventType::MouseDoubleClick, button, info);
					}
					else if (record.dwEventFlags == 0)
					{
						struct ButtonMapping
						{
							DWORD			mask;
							TuiMouseButton	button;
						};
						ButtonMapping mappings[] =
						{
							{ FROM_LEFT_1ST_BUTTON_PRESSED, TuiMouseButton::Left },
							{ FROM_LEFT_2ND_BUTTON_PRESSED, TuiMouseButton::Middle },
							{ RIGHTMOST_BUTTON_PRESSED, TuiMouseButton::Right },
						};
						for (auto mapping : mappings)
						{
							auto before = (mouseButtons & mapping.mask) != 0;
							auto after = (record.dwButtonState & mapping.mask) != 0;
							if (before != after)
							{
								QueueMouseButton(after ? unittest::TuiBackendEventType::MouseDown : unittest::TuiBackendEventType::MouseUp, mapping.button, info);
							}
						}
					}
					mouseButtons = record.dwButtonState;
				}

				void DecodeRecord(const INPUT_RECORD& record)
				{
					switch (record.EventType)
					{
					case KEY_EVENT:
						DecodeKey(record.Event.KeyEvent);
						break;
					case MOUSE_EVENT:
						DecodeMouse(record.Event.MouseEvent);
						break;
					case WINDOW_BUFFER_SIZE_EVENT:
						SynchronizeViewport(true);
						break;
					}
				}

				void AppendColor(WString& output, TuiColor foreground, TuiColor background, TuiColorMode colorMode)
				{
					if (colorMode == TuiColorMode::TrueColor)
					{
						output += L"\x1B[38;2;" + itow(foreground.r) + L";" + itow(foreground.g) + L";" + itow(foreground.b)
							+ L";48;2;" + itow(background.r) + L";" + itow(background.g) + L";" + itow(background.b) + L"m";
					}
					else
					{
						TuiColor custom16[16];
						const TuiColor* custom = nullptr;
						if (colorMode == TuiColorMode::Color16 && screenInfo.cbSize == sizeof(screenInfo))
						{
							for (vint i = 0; i < 16; i++)
							{
								auto color = screenInfo.ColorTable[AnsiToWindowsColor(i)];
								custom16[i] = { GetRValue(color), GetGValue(color), GetBValue(color) };
							}
							custom = custom16;
						}
						auto foregroundIndex = QuantizeColor(foreground, colorMode, custom);
						auto backgroundIndex = QuantizeColor(background, colorMode, custom);
						if (colorMode == TuiColorMode::Color256)
						{
							output += L"\x1B[38;5;" + itow(foregroundIndex) + L";48;5;" + itow(backgroundIndex) + L"m";
						}
						else
						{
							auto foregroundCode = foregroundIndex < 8 ? 30 + foregroundIndex : 90 + foregroundIndex - 8;
							auto backgroundCode = backgroundIndex < 8 ? 40 + backgroundIndex : 100 + backgroundIndex - 8;
							output += L"\x1B[" + itow(foregroundCode) + L";" + itow(backgroundCode) + L"m";
						}
					}
				}

			public:
				TuiColorMode Start(const TuiStartOptions& options) override
				{
					CHECK_ERROR(!started, L"vl::console::TUI Windows backend is already active.");
					inputHandle = GetStdHandle(STD_INPUT_HANDLE);
					originalOutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
					outputHandle = originalOutputHandle;
					CHECK_ERROR(inputHandle != INVALID_HANDLE_VALUE && originalOutputHandle != INVALID_HANDLE_VALUE, L"vl::console::TUI requires valid console handles.");
					CHECK_ERROR(GetConsoleMode(inputHandle, &inputMode) && GetConsoleMode(originalOutputHandle, &outputMode), L"vl::console::TUI requires interactive console handles.");
					cursorInfoSaved = GetConsoleCursorInfo(originalOutputHandle, &cursorInfo) != 0;
					screenInfo = {};
					screenInfo.cbSize = sizeof(screenInfo);
					if (!GetConsoleScreenBufferInfoEx(originalOutputHandle, &screenInfo))
					{
						screenInfo.cbSize = 0;
					}
					CHECK_ERROR(GetConsoleScreenBufferInfo(originalOutputHandle, &originalGeometry), L"vl::console::TUI failed to save the original console geometry.");
					geometrySaved = true;
					started = true;

					try
					{
						auto newInputMode = inputMode;
						newInputMode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_QUICK_EDIT_MODE | ENABLE_VIRTUAL_TERMINAL_INPUT);
						newInputMode |= ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT;
						CHECK_ERROR(SetConsoleMode(inputHandle, newInputMode), L"vl::console::TUI failed to activate console input mode.");
						inputModeChanged = true;

						auto newOutputMode = outputMode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
						usingVt = SetConsoleMode(originalOutputHandle, newOutputMode) != 0;
						if (usingVt)
						{
							outputModeChanged = true;
							const wchar_t sequence[] = L"\x1B[?1049h\x1B[?25l";
							WriteConsoleAll(outputHandle, sequence, sizeof(sequence) / sizeof(*sequence) - 1);
							SynchronizeViewport(false);
							if (options.colorMode == TuiColorMode::Auto) return TuiColorMode::TrueColor;
							return options.colorMode;
						}

						classicOutputHandle = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CONSOLE_TEXTMODE_BUFFER, nullptr);
						CHECK_ERROR(classicOutputHandle != INVALID_HANDLE_VALUE, L"vl::console::TUI failed to create an alternate console screen buffer.");
						auto width = (SHORT)(originalGeometry.srWindow.Right - originalGeometry.srWindow.Left + 1);
						auto height = (SHORT)(originalGeometry.srWindow.Bottom - originalGeometry.srWindow.Top + 1);
						SetConsoleGeometry(classicOutputHandle, { width, height }, { 0, 0, (SHORT)(width - 1), (SHORT)(height - 1) });
						CHECK_ERROR(SetConsoleActiveScreenBuffer(classicOutputHandle), L"vl::console::TUI failed to activate the alternate console screen buffer.");
						outputHandle = classicOutputHandle;
						viewportWidth = width;
						viewportHeight = height;
						return TuiColorMode::Color16;
					}
					catch (...)
					{
						auto exception = std::current_exception();
						try
						{
							Stop();
						}
						catch (...)
						{
						}
						std::rethrow_exception(exception);
					}
				}

				void Stop() override
				{
					if (!started) return;
					auto restored = true;
					if (usingVt)
					{
						auto width = originalGeometry.srWindow.Right - originalGeometry.srWindow.Left + 1;
						auto height = originalGeometry.srWindow.Bottom - originalGeometry.srWindow.Top + 1;
						auto sequence = WString::Unmanaged(L"\x1B[0m\x1B[8;")
							+ itow(height) + L";" + itow(width) + L"t\x1B[?1049l\x1B[?25h";
						try
						{
							WriteConsoleAll(outputHandle, sequence.Buffer(), sequence.Length());
							auto deadline = GetTickCount64() + 250;
							vint stableCount = 0;
							while (true)
							{
								CONSOLE_SCREEN_BUFFER_INFO info = {};
								CHECK_ERROR(GetConsoleScreenBufferInfo(outputHandle, &info), L"vl::console::TUI Windows backend failed to wait for terminal restoration.");
								auto currentWidth = info.srWindow.Right - info.srWindow.Left + 1;
								auto currentHeight = info.srWindow.Bottom - info.srWindow.Top + 1;
								if (currentWidth == width && currentHeight == height)
								{
									if (++stableCount == 10) break;
								}
								else
								{
									stableCount = 0;
								}
								if (GetTickCount64() >= deadline) break;
								::Sleep(1);
							}
						}
						catch (...)
						{
							restored = false;
						}
					}
					if (classicOutputHandle != INVALID_HANDLE_VALUE)
					{
						if (!SetConsoleActiveScreenBuffer(originalOutputHandle)) restored = false;
						if (!CloseHandle(classicOutputHandle)) restored = false;
						classicOutputHandle = INVALID_HANDLE_VALUE;
					}
					if (geometrySaved)
					{
						try
						{
							SetConsoleGeometry(originalOutputHandle, originalGeometry.dwSize, originalGeometry.srWindow);
						}
						catch (...)
						{
							restored = false;
						}
					}
					if (cursorInfoSaved && !SetConsoleCursorInfo(originalOutputHandle, &cursorInfo)) restored = false;
					if (outputModeChanged && !SetConsoleMode(originalOutputHandle, outputMode)) restored = false;
					if (inputModeChanged && !SetConsoleMode(inputHandle, inputMode)) restored = false;
					pendingEvents.Clear();
					mouseButtons = 0;
					viewportWidth = 0;
					viewportHeight = 0;
					outputHandle = INVALID_HANDLE_VALUE;
					originalOutputHandle = INVALID_HANDLE_VALUE;
					inputHandle = INVALID_HANDLE_VALUE;
					usingVt = false;
					inputModeChanged = false;
					outputModeChanged = false;
					cursorInfoSaved = false;
					geometrySaved = false;
					started = false;
					CHECK_ERROR(restored, L"vl::console::TUI Windows backend failed to restore the original console state.");
				}

				bool TryGetConsoleSize(vint& width, vint& height) override
				{
					auto handle = outputHandle != INVALID_HANDLE_VALUE ? outputHandle : GetStdHandle(STD_OUTPUT_HANDLE);
					CONSOLE_SCREEN_BUFFER_INFO info = {};
					if (handle == INVALID_HANDLE_VALUE || !GetConsoleScreenBufferInfo(handle, &info)) return false;
					width = info.srWindow.Right - info.srWindow.Left + 1;
					height = info.srWindow.Bottom - info.srWindow.Top + 1;
					return width > 0 && height > 0;
				}

				vuint64_t GetMonotonicTime() override
				{
					return GetTickCount64();
				}

				bool ReadEvent(vint milliseconds, unittest::TuiBackendEvent& event) override
				{
					if (pendingEvents.Count() == 0)
					{
						SynchronizeViewport(true);
					}
					if (pendingEvents.Count() == 0)
					{
						auto result = WaitForSingleObject(inputHandle, milliseconds < 0 ? INFINITE : (DWORD)milliseconds);
						if (result == WAIT_TIMEOUT)
						{
							SynchronizeViewport(true);
							if (pendingEvents.Count() == 0) return false;
						}
						else
						{
							CHECK_ERROR(result == WAIT_OBJECT_0, L"vl::console::TUI Windows backend failed while waiting for console input.");
							INPUT_RECORD records[32];
							DWORD count = 0;
							CHECK_ERROR(ReadConsoleInputW(inputHandle, records, sizeof(records) / sizeof(*records), &count), L"vl::console::TUI Windows backend failed to read console input.");
							for (DWORD i = 0; i < count; i++) DecodeRecord(records[i]);
							SynchronizeViewport(true);
						}
					}
					if (pendingEvents.Count() == 0) return false;
					event = pendingEvents[0];
					pendingEvents.RemoveAt(0);
					return true;
				}

				void Render(const TuiPixel* buffer, vint width, vint height, TuiColorMode colorMode) override
				{
					if (usingVt)
					{
						WString output;
						TuiColor lastForeground;
						TuiColor lastBackground;
						bool hasLastColor = false;
						for (vint y = 0; y < height; y++)
						{
							output += L"\x1B[" + itow(y + 1) + L";1H";
							for (vint x = 0; x < width; x++)
							{
								auto& pixel = buffer[y * width + x];
								if (pixel.glyph == TuiPixelGlyph::WideCharContinuation) continue;
								if (!hasLastColor || pixel.foregroundColor != lastForeground || pixel.backgroundColor != lastBackground)
								{
									AppendColor(output, pixel.foregroundColor, pixel.backgroundColor, colorMode);
									lastForeground = pixel.foregroundColor;
									lastBackground = pixel.backgroundColor;
									hasLastColor = true;
								}
								auto code = pixel.GetChar32();
								if (code == 0) output += L" ";
								else output += u32tow(U32String::CopyFrom(&code, 1));
							}
						}
						output += L"\x1B[0m";
						WriteConsoleAll(outputHandle, output.Buffer(), output.Length());
					}
					else
					{
						Array<CHAR_INFO> output(width * height);
						TuiColor custom16[16];
						for (vint i = 0; i < 16; i++)
						{
							auto color = screenInfo.ColorTable[AnsiToWindowsColor(i)];
							custom16[i] = { GetRValue(color), GetGValue(color), GetBValue(color) };
						}
						for (vint i = 0; i < width * height; i++)
						{
							auto& pixel = buffer[i];
							auto code = pixel.GetChar32();
							auto measured = code == 0 ? 1 : TUI::MeasureChar(code);
							output[i].Char.UnicodeChar = code == 0 ? L' ' : measured == 2 || code > 0xFFFF ? L'?' : (wchar_t)code;
							if (pixel.glyph == TuiPixelGlyph::WideCharContinuation) output[i].Char.UnicodeChar = L' ';
							auto foreground = AnsiToWindowsColor(QuantizeColor(pixel.foregroundColor, TuiColorMode::Color16, custom16));
							auto background = AnsiToWindowsColor(QuantizeColor(pixel.backgroundColor, TuiColorMode::Color16, custom16));
							output[i].Attributes = (WORD)(foreground | (background << 4));
						}
						COORD size = { (SHORT)width, (SHORT)height };
						COORD origin = {};
						SMALL_RECT area = { 0, 0, (SHORT)(width - 1), (SHORT)(height - 1) };
						CHECK_ERROR(WriteConsoleOutputW(outputHandle, &output[0], size, origin, &area), L"vl::console::TUI Windows backend failed to render the classic console buffer.");
					}
				}
			};

			Ptr<unittest::ITuiBackend> CreateTuiBackend()
			{
				return Ptr(new WindowsTuiBackend);
			}
		}
	}
}

