#include "StdAfx.h"
#include "fs/NativeUtils.h"
#include "fs/AbsPath.h"
#include "tl/finally.h"
#include "tl/fixed_string.h"

#if defined(TL_PLATFORM_WINDOWS_FAMILY)
#	include <Shlwapi.h>
#	include <Windows.h>
#	include <io.h>
#	include <sys/utime.h>//_utime

#elif defined(TL_PLATFORM_POSIX_API)
#	include <sys/types.h>
#	include <utime.h> //utime
#	include <unistd.h>
#	include <time.h>
#endif

#include <Logger.h>
#include "tl/narrow_cast.h"

//////////////////////////////////////////////////////////////////////////

namespace fs
{
//////////////////////////////////////////////////////////////////////////

namespace native_utils
{

//////////////////////////////////////////////////////////////////////////

#if defined(TL_PLATFORM_WINDOWS_DESKTOP_APP)

//////////////////////////////////////////////////////////////////////////

static bool isCapitalizationCorrect(const WCHAR* name, HANDLE handle) noexcept
{
	tl::fixed_string<wchar_t, 512> buffer;
	buffer.resize(buffer.max_size());
	DWORD length = GetFinalPathNameByHandleW(handle, buffer.data(), static_cast<DWORD>(buffer.size()), FILE_NAME_NORMALIZED | VOLUME_NAME_NONE);
	if (length > buffer.size())
	{
		buffer.resize(length);
		length = GetFinalPathNameByHandleW(handle, buffer.data(), static_cast<DWORD>(buffer.size()), FILE_NAME_NORMALIZED | VOLUME_NAME_NONE);
		TL_ASSERT(length + 1== buffer.size());
	}

	return wcscmp(buffer.data(), name + 2) == 0;
}

//////////////////////////////////////////////////////////////////////////

bool isCapitalizationCorrect(const tl::string& str, HANDLE handle) noexcept
{
	const size_t wNameSize = str.size() + 1;
	tl::fixed_vector<wchar_t, 512> wName(wNameSize);
	swprintf(wName.data(), wNameSize, L"%hs", str.c_str());
	return isCapitalizationCorrect(wName.data(), handle);
}

//////////////////////////////////////////////////////////////////////////

bool isCapitalizationCorrect(AbsPathView path, HANDLE handle) noexcept
{
	return isCapitalizationCorrect(path.str(), handle);
}

//////////////////////////////////////////////////////////////////////////

static bool isFileCapitalizationCorrect(AbsPathView filePath) noexcept
{
	const tl::string fileStr = filePath.str();
	const char* fileName = fileStr.c_str();

	const size_t wNameSize = fileStr.size() + 1;
	tl::fixed_vector<wchar_t, 512> wName(wNameSize);
	swprintf(wName.data(), wNameSize, L"%hs", fileName);

	const HANDLE hFile = CreateFile2(wName.data(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, OPEN_EXISTING, nullptr);
	if (hFile == INVALID_HANDLE_VALUE)
		return true; //couldn't find it, so it must be a new file

	const bool result = isCapitalizationCorrect(wName.data(), hFile);
	CloseHandle(hFile);

	return result;
}

//////////////////////////////////////////////////////////////////////////

static bool isFolderCapitalizationCorrect(AbsPathView folderPath) noexcept
{
	const tl::string folderStr = folderPath.str();
	const char* folderName = folderStr.c_str();

	const size_t wNameSize = folderStr.size() + 1;
	tl::fixed_vector<wchar_t, 512> wName(wNameSize);
	swprintf(wName.data(), wNameSize, L"%hs", folderName);

	CREATEFILE2_EXTENDED_PARAMETERS extendedParameters = {};
	extendedParameters.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS);
	extendedParameters.dwFileFlags = FILE_FLAG_BACKUP_SEMANTICS;
	const HANDLE hFolder = CreateFile2(wName.data(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, OPEN_EXISTING, &extendedParameters);
	if (hFolder == INVALID_HANDLE_VALUE)
		return true; //couldn't find it, so it must be a new folder

	const bool result = isCapitalizationCorrect(wName.data(), hFolder);
	CloseHandle(hFolder);

	return result;
}

#endif //TL_PLATFORM_WINDOWS_DESKTOP_APP

//////////////////////////////////////////////////////////////////////////

bool validateCaseSensitiveFilename(AbsPathView filepath) noexcept
{
#ifdef TL_PLATFORM_WINDOWS_DESKTOP_APP
	return isFileCapitalizationCorrect(filepath);
#else
	return true;
#endif
}

//////////////////////////////////////////////////////////////////////////

bool validateCaseSensitiveFilename(AbsPathView filepath, int fd) noexcept
{
#ifdef TL_PLATFORM_WINDOWS_DESKTOP_APP
	const HANDLE handle = (HANDLE)_get_osfhandle(fd);
	if (handle == INVALID_HANDLE_VALUE)
		return false;

	return isCapitalizationCorrect(filepath, handle);
#else
	return true;
#endif
}

//////////////////////////////////////////////////////////////////////////

#ifdef TL_PLATFORM_WINDOWS_FAMILY
bool validateCaseSensitiveFilename(AbsPathView filepath, HANDLE handle) noexcept
{
#ifdef TL_PLATFORM_WINDOWS_DESKTOP_APP
	if (handle == INVALID_HANDLE_VALUE)
		return false;

	return isCapitalizationCorrect(filepath, handle);
#else
	return true;
#endif
}
#endif
//////////////////////////////////////////////////////////////////////////

bool validateCaseSensitiveFolder(AbsPathView folderPath) noexcept
{
#ifdef TL_PLATFORM_WINDOWS_DESKTOP_APP
	return isFolderCapitalizationCorrect(folderPath);
#else
	return true;
#endif
}

////////////////////////////////////////////////////////////////////////

wstring_t utf8To16(tl::span<const char> str) noexcept
{
	TL_ASSERT(str.size() > 0);

	const size_t maxNameSize = str.size() + 1;
	wstring_t ret;
	ret.resize(maxNameSize);

#ifdef TL_PLATFORM_WINDOWS_FAMILY
	const int charactersCopied = MultiByteToWideChar(CP_UTF8, 0, str.data(), -1, const_cast<wchar_t*>(ret.data()), tl::narrow<int>(maxNameSize));
#else
	const int charactersCopied = swprintf(const_cast<wchar_t*>(ret.data()), maxNameSize, L"%hs", utf8String);
#endif

	if (charactersCopied <= 0)
		return {};

	ret.resize(charactersCopied);
	return ret;
}

////////////////////////////////////////////////////////////////////////

string_t utf16To8(tl::span<const wchar_t> str) noexcept
{
	TL_ASSERT(str.size() > 0);

	string_t ret;
	const size_t maxNameSize = str.size() + 1;
	ret.resize(maxNameSize);

	const int charactersCopied = snprintf(const_cast<char*>(ret.data()), maxNameSize, "%ls", str.data());
	if (charactersCopied <= 0)
		return {};

	ret.resize(charactersCopied);
	return ret;
}

////////////////////////////////////////////////////////////////////////

}
}
