bool CFileSystemObject::isMovableTo(const CFileSystemObject& dest) const
{
	if (!isValid() || !dest.isValid())
		return false;

	const auto fileSystemId = rootFileSystemId(), otherFileSystemId = dest.rootFileSystemId();
	return fileSystemId == otherFileSystemId && fileSystemId != std::numeric_limits<uint64_t>::max() && otherFileSystemId != std::numeric_limits<uint64_t>::max();
}
void PrintLevel(const std::wstring & aParentName, CFileSystemObject & aObject)
{
	std::wstring fullName = aParentName + L'\\' + aObject.getName();
	printf("0x%x: %S\n", aObject.getFileAttributes(), fullName.c_str());
	for (auto it = aObject.begin(); it != aObject.end(); ++it) {
		PrintLevel(fullName, *it->second);
	}

	return;
}
void scanDirectory(const CFileSystemObject& root, const std::function<void(const CFileSystemObject&)>& observer, const std::atomic<bool>& abort)
{
	if (observer)
		observer(root);

	if (!root.isDir() || abort)
		return;

	const auto list = root.qDir().entryInfoList(QDir::Files | QDir::Dirs | QDir::Hidden | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::System);
	for (const auto& entry : list)
	{
		scanDirectory(CFileSystemObject(entry), observer, abort);

		if (abort)
			return;
	}
}
inline static qulonglong hash(const CFileSystemObject& object)
{
	const auto properties = object.properties();
	const auto hashData = QByteArray::fromRawData((const char*)&properties.hash, sizeof(properties.hash)) +
			QByteArray::fromRawData((const char*)&properties.modificationDate, sizeof(properties.modificationDate)) +
			QByteArray::fromRawData((const char*)&properties.type, sizeof(properties.type));

	return fasthash64(hashData.constData(), hashData.size(), 0);
}
static DWORD _BuildTreeLevel(const HANDLE hDirectory, CFileSystemObject & FSObject)
{
	DWORD ret = ERROR_GEN_FAILURE;

	ret = FSObject.FindChildren(hDirectory);
	if (ret == ERROR_SUCCESS) {
		for (auto it = FSObject.begin(); it != FSObject.end(); ++it) {
			CFileSystemObject *ch = it->second;
			ULONG attr = ch->getFileAttributes();
			
			if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0 && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
				HANDLE hChild = NULL;

				ret = _OpenDirectoryName(hDirectory, ch->getName(), &hChild);
				if (ret == ERROR_SUCCESS) {
					ret = _BuildTreeLevel(hChild, *ch);
					CloseHandle(hChild);
				}
			}

//			if (ret != ERROR_SUCCESS)
//				break;
		}
	}

	return ret;
}
static DWORD _ScanFileIds(const std::wstring VolumeName, CFileSystemObject & Root)
{
	HANDLE volumeHandle = NULL;
	unsigned int i = 0;
	BY_HANDLE_FILE_INFORMATION bhfi;
	HANDLE fileHandle = INVALID_HANDLE_VALUE;
	DWORD ret = ERROR_GEN_FAILURE;

	volumeHandle = CreateFileW(VolumeName.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	ret = (volumeHandle != INVALID_HANDLE_VALUE) ? ERROR_SUCCESS : GetLastError();
	if (ret == ERROR_SUCCESS) {
		for (i = 0; i < 16 * 1024 * 1024; ++i) {
			ret = _OpenFileById(volumeHandle, i, &fileHandle);
			if (ret == ERROR_SUCCESS) {
				POBJECT_NAME_INFORMATION oni = NULL;

				ret = (GetFileInformationByHandle(fileHandle, &bhfi)) ? ERROR_SUCCESS : GetLastError();
				if (ret == ERROR_SUCCESS) {
					ret = QueryObjectName(fileHandle, &oni);
					if (ret == ERROR_SUCCESS) {
						CFileSystemObject *o = NULL;
						std::wstring fileName = std::wstring(oni->Name.Buffer, oni->Name.Length / sizeof(WCHAR)).substr(1);
						size_t pos = fileName.find_first_of(L'\\');

						fileName = fileName.substr(pos + 1);
						pos = fileName.find_first_of(L'\\');
						fileName = fileName.substr(pos + 1);
						o = new CFileSystemObject(L"", bhfi.dwFileAttributes, i);
						Root.AddObject(fileName, o);

						HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, oni);
					}
				}

				CloseHandle(fileHandle);
			}
		}

		CloseHandle(volumeHandle);
	}

	return ret;
}
// Returns true if this object is a child of parent, either direct or indirect
bool CFileSystemObject::isChildOf(const CFileSystemObject &parent) const
{
	if (!isValid() || !parent.isValid())
		return false;

	if (fullAbsolutePath().startsWith(parent.fullAbsolutePath(), caseSensitiveFilesystem() ? Qt::CaseSensitive : Qt::CaseInsensitive))
		return true;

	if (!isSymLink() && !parent.isSymLink())
		return false;

	const auto resolvedChildLink = isSymLink() ? symLinkTarget() : fullAbsolutePath();
	const auto resolvedParentLink = parent.isSymLink() ? parent.symLinkTarget() : parent.fullAbsolutePath();

	assert_and_return_r(!resolvedChildLink.isEmpty() && !resolvedParentLink.isEmpty(), false);
	return resolvedChildLink.startsWith(resolvedParentLink, caseSensitiveFilesystem() ? Qt::CaseSensitive : Qt::CaseInsensitive);
}
DISABLE_COMPILER_WARNINGS
#include <QDateTime>
#include <QInputDialog>
#include <QLineEdit>
RESTORE_COMPILER_WARNINGS

CPromptDialog::CPromptDialog(QWidget *parent, Operation op, HaltReason promptReason,
	const CFileSystemObject& source, const CFileSystemObject& dest, const QString& message) :

		QDialog(parent),
		ui(new Ui::CPromptDialog),
		_response(urNone)
{
	ui->setupUi(this);

	connect(ui->btnCancel,          &QPushButton::clicked, this, &CPromptDialog::onCancelClicked);
	connect(ui->btnCancelDeletion,  &QPushButton::clicked, this, &CPromptDialog::onCancelClicked);
	connect(ui->btnDeleteAnyway,    &QPushButton::clicked, this, &CPromptDialog::onProceedClicked);
	connect(ui->btnDeleteAllAnyway, &QPushButton::clicked, this, &CPromptDialog::onProceedAllClicked);
	connect(ui->btnOverwrite,       &QPushButton::clicked, this, &CPromptDialog::onProceedClicked);
	connect(ui->btnOverwriteAll,    &QPushButton::clicked, this, &CPromptDialog::onProceedAllClicked);
	connect(ui->btnRename,          &QPushButton::clicked, this, &CPromptDialog::onRenameClicked);
	connect(ui->btnSkip,            &QPushButton::clicked, this, &CPromptDialog::onSkipClicked);
	connect(ui->btnSkipAll,         &QPushButton::clicked, this, &CPromptDialog::onSkipAllClicked);
	connect(ui->btnSkipDeletion,    &QPushButton::clicked, this, &CPromptDialog::onSkipClicked);
	connect(ui->btnSkipAllDeletion, &QPushButton::clicked, this, &CPromptDialog::onSkipAllClicked);
	connect(ui->btnRetry,           &QPushButton::clicked, this, &CPromptDialog::onRetryClicked);

	switch (promptReason)
	{
	case hrFileExists:
		ui->lblQuestion->setText("File or folder already exists.");
		break;
	case hrSourceFileIsReadOnly:
		ui->btnOverwrite->setVisible(false);
		ui->btnOverwriteAll->setVisible(false);
		ui->btnRename->setVisible(false);
		ui->lblQuestion->setText("The source file or folder is read-only.");
		break;
	case hrDestFileIsReadOnly:
		ui->lblQuestion->setText("The destination file or folder is read-only.");
		break;
	case hrFailedToMakeItemWritable:
		ui->lblQuestion->setText("Failed to make the file or folder writable.");
		ui->btnOverwrite->setVisible(false);
		ui->btnOverwriteAll->setVisible(false);
		ui->btnRename->setVisible(false);
	case hrFileDoesntExit:
		ui->lblQuestion->setText("The file or folder doesn't exist.");
		ui->btnOverwrite->setVisible(false);
		ui->btnOverwriteAll->setVisible(false);
		ui->btnRename->setVisible(false);
		ui->btnDeleteAllAnyway->setVisible(false);
		ui->btnDeleteAnyway->setVisible(false);
		break;
	case hrCreatingFolderFailed:
		ui->lblQuestion->setText(tr("Failed to create the folder\n%1").arg(source.fullAbsolutePath()));
		ui->btnOverwrite->setVisible(false);
		ui->btnOverwriteAll->setVisible(false);
		ui->btnRename->setVisible(false);
		break;
	case hrFailedToDelete:
		ui->btnOverwrite->setVisible(false);
		ui->btnOverwriteAll->setVisible(false);
		ui->btnDeleteAnyway->setVisible(false);
		ui->btnDeleteAllAnyway->setVisible(false);
		ui->btnRename->setVisible(false);
		ui->lblQuestion->setText(tr("Failed to delete\n%1").arg(source.fullAbsolutePath()));
		break;
	case hrUnknownError:
		ui->lblQuestion->setText("An unknown error occurred. What do you want to do?");
		ui->btnOverwrite->setVisible(false);
		ui->btnOverwriteAll->setVisible(false);
		ui->btnRename->setVisible(false);
		break;
	default:
		ui->lblQuestion->setText("An unknown error occurred. What do you want to do?");
		break;
	}

	if (!message.isEmpty())
		ui->lblQuestion->setText(ui->lblQuestion->text() + "\n\n" + message);

	if (op == operationDelete || promptReason == hrSourceFileIsReadOnly || promptReason == hrFailedToMakeItemWritable)
	{
		ui->stackedWidget->setCurrentIndex(1);

		ui->m_lblItemBeingDeleted->setText(source.fullAbsolutePath());
		ui->lblSize->setText(fileSizeToString(source.size()));
		QDateTime modificationDate;
		modificationDate.setTime_t((uint)source.properties().modificationDate);
		modificationDate = modificationDate.toLocalTime();
		ui->lblModTime->setText(modificationDate.toString("dd.MM.yyyy hh:mm"));
	}
	else
	{
		ui->stackedWidget->setCurrentIndex(0);

		if (source.isValid())
		{
			ui->lblSrcFile->setText(source.fullAbsolutePath());
			ui->lblSourceSize->setText(fileSizeToString(source.size()));
			QDateTime modificationDate;
			modificationDate.setTime_t((uint)source.properties().modificationDate);
			modificationDate = modificationDate.toLocalTime();
			ui->lblSourceModTime->setText(modificationDate.toString("dd.MM.yyyy hh:mm"));
		}
		else
			WidgetUtils::setLayoutVisible(ui->sourceFileInfo, false);

		if (dest.isValid())
		{
			ui->lblDstFile->setText(dest.fullAbsolutePath());
			ui->lblDestSize->setText(fileSizeToString(dest.size()));
			QDateTime modificationDate;
			modificationDate.setTime_t((uint)dest.properties().modificationDate);
			modificationDate = modificationDate.toLocalTime();
			ui->lblDestModTime->setText(modificationDate.toString("dd.MM.yyyy hh:mm"));
		}
		else
			WidgetUtils::setLayoutVisible(ui->destFileInfo, false);
	}

	_srcFileName = source.fullName();
}
bool CFileSystemObject::operator==(const CFileSystemObject& other) const
{
	return hash() == other.hash();
}