#include "stdafx.h"

using namespace System::Text::RegularExpressions;

void descendpath(const tstring root, std::wstring relativepath, std::queue<tstring> &pathlist)
{
	std::queue<std::wstring> searchpath;
	WIN32_FIND_DATAW fd;
	HANDLE h = FindFirstFileExW((root + relativepath + L"*").c_str(),
		findex_flag,
		&fd,
		FindExSearchNameMatch, NULL, 0);
	if (h == INVALID_HANDLE_VALUE) {
		return;
	}
	do {
		if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			if ((wcscmp(fd.cFileName, L".") == 0) ||
				(wcscmp(fd.cFileName, L"..") == 0)) {
				continue;
			}
			std::wstring dirname = fd.cFileName;
			dirname = relativepath + dirname + L'\\';
			searchpath.push(dirname);
			pathlist.push(dirname);
			continue;
		}
	} while (FindNextFileW(h, &fd));
	FindClose(h);
	while (!searchpath.empty()){
		descendpath(root, searchpath.front(), pathlist);
		searchpath.pop();
	}
}

std::vector<fileentry> SearchSrcfiles(std::wstring basepath, std::wstring pattern, param::paramdata &param)
{
	std::wstring srcpath = basepath;
	std::wstring matchpattern = pattern;

	std::queue<std::wstring> searchpath;
	searchpath.push(L"");
	if (param.local.recursion){
		descendpath(srcpath, L"", searchpath);
	}

	std::vector<fileentry> targetfiles;

	while (!searchpath.empty()){
		tstring relativepath = searchpath.front();
		searchpath.pop();

		WIN32_FIND_DATAW fd;
		HANDLE h = FindFirstFileExW((srcpath + relativepath + matchpattern).c_str(),
			findex_flag,
			&fd,
			FindExSearchNameMatch, NULL, 0);
		if (h == INVALID_HANDLE_VALUE) {
			continue;
		}
		do {
			std::wstring filename = fd.cFileName;
			fileentry aFile;
			if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
				if ((wcscmp(fd.cFileName, L".") == 0) ||
					(wcscmp(fd.cFileName, L"..") == 0)) {
					continue;
				}
				filename += L"\\";
				aFile.directory = true;
			}
			else {
				aFile.directory = false;
			}
			LARGE_INTEGER filesize;
			FILETIME LastWriteTime = fd.ftLastWriteTime;
			filesize.HighPart = fd.nFileSizeHigh;
			filesize.LowPart = fd.nFileSizeLow;
			aFile.absolutepath = srcpath + relativepath + filename;
			aFile.name = filename;
			aFile.relativepath = relativepath + filename;
			aFile.filesize = filesize.QuadPart;
			aFile.LastWriteTime = LastWriteTime;
			targetfiles.push_back(aFile);
		} while (FindNextFileW(h, &fd));
		FindClose(h);
	}

	return targetfiles;
}

std::vector<fileentry> localsearch(param::paramdata& param)
{
	std::vector<fileentry> result;

	bool regular = param.local.regular;

	System::String^ strtarget = gcnew System::String(param.local.targetstr.c_str());
	bool recursion = Regex::IsMatch(strtarget, ".*[*?]+.*\\\\");
	bool forcerecursion = false;
	std::wstring base;
	std::wstring target;
	if (!param.local.basepath.empty()){
		base = param.local.basepath;
		target = param.local.targetstr;
	}
	else if (recursion){
		base = SystemStringToStdWString(Regex::Replace(strtarget, "(([^\\\\]*\\\\)*\\\\)[^\\\\]*[*?]+[^\\\\]*\\\\?.*", "$1"));
		target = SystemStringToStdWString(Regex::Replace(strtarget, "([^\\\\]*\\\\)*([^\\\\]*[*?]+[^\\\\]*\\\\?.*)", "$2"));
		forcerecursion = true;
	}
	else {
		base = SystemStringToStdWString(Regex::Replace(strtarget, "(([^\\\\]*\\\\)*)[^\\\\]*", "$1"));
		target = SystemStringToStdWString(Regex::Replace(strtarget, "([^\\\\]*\\\\)*([^\\\\]*)", "$2"));
		target = (target.empty()) ? L"*" : target;
	}

	param.local.recursion = param.local.recursion || recursion;

	std::wcerr << "target " << target << "\n";
	std::wcerr << "recursion " << param.local.recursion << "\n";
	std::wcerr << "...loading " << base << "\n";


	auto list = SearchSrcfiles(base, (regular || forcerecursion)? L"*": target, param);
	for (auto it = list.begin(); it != list.end(); ++it){
		auto pathname = it->relativepath;
		if (CheckFilePathMatch(target, pathname, regular)){
			if (param.usedatefilter){
				const FILETIME zerovalue = { 0 };
				if ((CompareFileTime(&zerovalue, &param.startdate) != 0) && (CompareFileTime(&it->LastWriteTime, &param.startdate) < 0)){
					continue;
				}
				if ((CompareFileTime(&zerovalue, &param.enddate) != 0) && (CompareFileTime(&it->LastWriteTime, &param.enddate) > 0)){
					continue;
				}
			}
			if (param.usesizefilter){
				if ((param.minsize > 0) && (param.minsize > it->filesize)){
					continue;
				}
				if ((param.maxsize > 0) && (param.maxsize < it->filesize)){
					continue;
				}
			}
			if (param.local.notree){
				if (!it->directory)
					it->display = it->name;
			}
			else {
				it->display = pathname;
			}
			if (param.verbose){
				std::stringstream ss;
				ss << std::endl;
				ss << "Size: " << it->filesize << " (" << bytestring(it->filesize) << ')' << std::endl;
				ss << "mTime: " << filetimestr(it->LastWriteTime) << std::endl;
				it->attribute = UTF8toWideChar(ss.str());
			}
			result.push_back(*it);
		}
	}
	std::wcerr << "local match " << result.size() << "\n";
	return result;
}

std::wstring FileHash(std::wstring absolutepath, INT64 size, HashFilter::Hash hashtype)
{
	HashFilter::hashfilter hasher(hashtype);
	std::ifstream infile(absolutepath, std::ios::binary);
	std::vector<char> buf(64 * 1024 * 1024);
	Tick starttick;
	INT64 trans = 0;
	starttick = GetTick();
	while (infile){
		infile.read(buf.data(), buf.size());
		hasher.write(buf.data(), infile.gcount());
		trans += infile.gcount();
		Tick nowtick = GetTick();
		fwprintf(stderr, L"%I64d / %I64d bytes (%.2f %%) processed...(%.2f MiB/sec)\r", trans, size, 100.0*trans / size, (double)trans / (1024 * 1024) * 1000 / (nowtick - starttick));
	}
	fwprintf(stderr, L"\ndone.\n");
	hasher.flush();
	std::stringstream str;
	str << std::endl;
	if ((hashtype & HashFilter::MD5) != 0)
		str << "MD5:" << hasher.md5() << std::endl;
	if ((hashtype & HashFilter::SHA1) != 0)
		str << "SHA1:" << hasher.sha1() << std::endl;
	if ((hashtype & HashFilter::SHA256) != 0)
		str << "SHA256:" << hasher.sha256() << std::endl;
	if ((hashtype & HashFilter::SHA384) != 0)
		str << "SHA384:" << hasher.sha384() << std::endl;
	if ((hashtype & HashFilter::SHA512) != 0)
		str << "SHA512:" << hasher.sha512() << std::endl;
	if ((hashtype & HashFilter::RIPEMD160) != 0)
		str << "RIPEMD160:" << hasher.ripemd160() << std::endl;
	return UTF8toWideChar(str.str());
}

std::vector<std::wstring> findlocal(param::paramdata& param)
{
	std::vector<std::wstring> ret;
	auto list = localsearch(param);
	size_t loopmax = list.size();
	size_t loopi = 0;
	std::for_each(
		list.begin(), 
		list.end(), 
		[&](fileentry &it){ 
			++loopi;
			if (!it.directory && param.hashtype){
				std::wcerr << loopi << "/" << loopmax << " hash check:" << it.name << std::endl;

				it.hashstr = FileHash(it.absolutepath, it.filesize, param.hashtype);
				//std::wcerr << hashinfo << std::endl;
			}
			ret.push_back(it.display + it.attribute + it.hashstr);
	});
	std::sort(ret.begin(), ret.end());
	return ret;
}

int listLocal(param::paramdata& param)
{
	auto result = findlocal(param);

	std::copy(result.begin(), result.end(), std::ostream_iterator<std::wstring, wchar_t>(std::wcout, L"\n"));
	return 0;
}
