#include "stdafx.h"

using namespace System::Text::RegularExpressions;

const std::wstring BitcasaInfiniteDrive = L"Bitcasa Infinite Drive";

std::unordered_map<std::string, std::vector<BitcasaFileItem>> BitcasaFileItem::BitcasaItemList;
std::unordered_map<std::string, std::wstring> BitcasaFileItem::BitcasaEncToNameList;
std::unordered_map<std::wstring, std::string> BitcasaFileItem::BitcasaNameToEncList;
std::unordered_map<std::string, BitcasaFileItem> BitcasaFileItem::BitcasaVagrantList;

BitcasaFileItem::BitcasaFileItem(json_object jsonItem)
	: name(jsonItem["name"]->GetString()),
	path(WideChartoUTF8(jsonItem["path"]->GetString())),
	id(WideChartoUTF8(jsonItem["id"]->GetString())),
	mount_point(WideChartoUTF8(jsonItem["mount_point"]->GetString())),
	size(jsonItem["size"]->GetInteger())
{
	LARGE_INTEGER t1;
	t1.QuadPart = LONGLONG(jsonItem["mtime"]->GetDouble() * 10000) + 116444736000000000;
	LastWriteTime.dwHighDateTime = t1.HighPart;
	LastWriteTime.dwLowDateTime = t1.LowPart;
	t1.QuadPart = LONGLONG(jsonItem["ctime"]->GetDouble() * 10000) + 116444736000000000;
	CreationTime.dwHighDateTime = t1.HighPart;
	CreationTime.dwLowDateTime = t1.LowPart;
	t1.QuadPart = LONGLONG(jsonItem["birth_time"]->GetDouble() * 10000) + 116444736000000000;
	BirthTime.dwHighDateTime = t1.HighPart;
	BirthTime.dwLowDateTime = t1.LowPart;


	namepath_parent = BitcasaEncToNameList[gouppath(path)];
	BitcasaNameToEncList[namepath()] = path;
	BitcasaNameToEncList[namepath()+L"\\"] = path;
	BitcasaEncToNameList[path] = namepath();
	if (mount_point.find("%%%HOME%%%/Bitcasa") == 0){
		BitcasaNameToEncList[L"\\"+BitcasaInfiniteDrive+namepath()] = path;
		BitcasaNameToEncList[L"\\" + BitcasaInfiniteDrive+namepath() + L"\\"] = path;
		vagrant = (mount_point.find("%%%HOME%%%/Bitcasa/Bitcasa Infinite Drive") != 0);
		BitcasaVagrantList[path] = *this;
	}
}

std::wstring BitcasaFileItem::gouppath(std::wstring path)
{
	auto pos = path.find_last_of(L"/\\");
	if (pos == path.length() - 1){
		pos = path.substr(0, pos).find_last_of(L"/\\");
	}
	return (pos < 2) ? ((pos < 1)?L"\\":L"\\\\") : path.substr(0, pos+1);
}
std::string BitcasaFileItem::gouppath(std::string path)
{
	auto pos = path.find_last_of("/");
	return (pos < 2) ? "" : path.substr(0, pos);
}

std::string BitcasaFileItem::rootparent(std::wstring path)
{
	auto pos = path.find_first_of(L"/\\");
	while ((pos < 2) && (pos != std::string::npos)){
		pos = path.substr(pos+1).find_first_of(L"/\\");
	}
	if (pos == std::string::npos) return "";
	return BitcasaNameToEncList[path.substr(0, pos + 1)];
}

std::vector<BitcasaFileItem> BitcasaServer::DirBitcasa(std::string path, bool deep)
{
	std::vector<BitcasaFileItem> result;

	System::String^ targetpath = gcnew System::String(path.c_str());
	pjson res = API.ListFolder(path, deep);
	auto list = (res)? (*(*res)["result"])["items"]: NULL;
	int retry = 3;
	while (!res && (retry-- > 0))
	{
		fprintf(stderr, "retry...\n");
		Sleep(5000);
		res = API.ListFolder(path, deep);
		list = (res) ? (*(*res)["result"])["items"] : NULL;
	}
	for (int i = 0; i < list->size(); i++)
	{
		auto item = dynamic_cast<json_object *>(&*(*list)[i]);
		if (item){
			auto FileItem = BitcasaFileItem(*item);
			result.push_back(FileItem);
		}
	}
	return result;
}

bool CheckFilePathMatch(tstring& str, std::wstring& testpath, bool regular)
{
	std::wstring str1 = str; //unicode compile—p
	System::String^ str2 = gcnew System::String(str1.c_str());

	if (str.empty()) return true;

	if (!regular && (str1.find_first_of(L"*?") != std::string::npos)){
		str2 = str2->Replace("\\", "\\\\");
		str2 = str2->Replace("+", "\\+");
		str2 = str2->Replace(".", "\\.");
		str2 = str2->Replace("?", ".");
		str2 = str2->Replace("*", ".*");
		str2 = str2->Replace("{", "\\{");
		str2 = str2->Replace("}", "\\}");
		str2 = str2->Replace("(", "\\(");
		str2 = str2->Replace(")", "\\)");
		str2 = str2->Replace("[", "\\[");
		str2 = str2->Replace("]", "\\]");
		str2 = str2->Replace("^", "\\^");
		str2 = str2->Replace("$", "\\$");
		str2 = "^" + str2 + "$";
		regular = true;
	}

	if (regular){
		return Regex::IsMatch(gcnew System::String(testpath.c_str()), str2);
	}
	else {
		if (str1[str1.length() - 1] == '\\'){
			return (testpath.find(str1) == 0);
		}
		else {
			return (str1 == testpath);
		}
	}
}


std::vector<BitcasaFileItem> BitcasaServer::listBitcasaDir(std::wstring target, bool deep)
{
	// target format
	//  \      -> \Bitcasa Infinite Drive or \ 
	//  \\     -> \ 
	//  \hoge  -> \hoge or \Bitcasa Infinite Drive\hoge
	//  \\hoge -> \hoge
	std::vector<BitcasaFileItem> result;
	std::vector<BitcasaFileItem> tempresult;
	bool trueroot = (target.find(L"\\\\") == 0);
	if (target.empty()) target = L"\\";

	if (!trueroot && (target != L"\\") && (target.find(BitcasaInfiniteDrive) != 1))
		target = L"\\" + BitcasaInfiniteDrive + target;

	if (!deep && BitcasaFileItem::BitcasaItemList.count(BitcasaFileItem::BitcasaNameToEncList[target]) != 0)
		return BitcasaFileItem::BitcasaItemList[BitcasaFileItem::BitcasaNameToEncList[target]];


	if ((target.find_first_of(L"\\") != std::string::npos) && (target != L"\\") && (target != L"\\\\")){
		listBitcasaDir(BitcasaFileItem::gouppath(target), false);
	}

	auto ind = target.find_last_of('\\');
	if (target.find(L"\\\\") == 0)
		target = target.substr(1);

	if ((ind != std::string::npos) && (ind > 1)){
		auto targettmp = target;
		if ((target != L"\\" + BitcasaInfiniteDrive) && (target.find(BitcasaInfiniteDrive) == 1))
			targettmp = target.substr(BitcasaInfiniteDrive.length() + 1);
		if (BitcasaFileItem::BitcasaVagrantList[BitcasaFileItem::rootparent(targettmp)].vagrant){
			target = targettmp;
		}
		tempresult = DirBitcasa(BitcasaFileItem::BitcasaNameToEncList[target], deep);
	}
	else {
		if (trueroot){
			tempresult = DirBitcasa("/", deep);
		}
		else {
			auto root = DirBitcasa("/", false);

			std::vector<BitcasaFileItem> Iroot;
			for (auto it = root.begin(); it != root.end(); ++it){
				if (it->name == BitcasaInfiniteDrive){
					auto item = DirBitcasa(BitcasaFileItem::BitcasaNameToEncList[L"\\" + BitcasaInfiniteDrive], deep);
					Iroot.insert(Iroot.end(), item.begin(), item.end());
					continue;
				}
				if (it->mount_point.find("%%%HOME%%%/Bitcasa") == 0){
					if (deep){
						auto item = DirBitcasa(it->path, deep);
						Iroot.insert(Iroot.end(), item.begin(), item.end());
					}
					Iroot.push_back(*it);
				}
			}
			tempresult = Iroot;
		}
	}
	for (auto it = tempresult.begin(); it != tempresult.end(); ++it){
		auto pathname = it->namepath();
		auto targettmp = target;
		if (!trueroot && (target != L"\\" + BitcasaInfiniteDrive) && (pathname.find(BitcasaInfiniteDrive) == 1))
			pathname = pathname.substr(BitcasaInfiniteDrive.length() + 1);
		if (!trueroot && (target != L"\\" + BitcasaInfiniteDrive) && (target.find(BitcasaInfiniteDrive) == 1))
			targettmp = target.substr(BitcasaInfiniteDrive.length() + 1);
		if (CheckFilePathMatch(targettmp, pathname))
			result.push_back(*it);
	}
	if (!deep){
		BitcasaFileItem::BitcasaItemList[BitcasaFileItem::BitcasaNameToEncList[target]] = result;
	}
	return result;
}

std::vector<BitcasaFileItem> remotesearch(BitcasaAPI& API, param::paramdata& param)
{
	BitcasaServer server(API);
	std::vector<BitcasaFileItem> result;

	bool regular = param.remote.regular;

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

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

	bool trueroot = (base.find(L"\\\\") == 0);

	auto list = server.listBitcasaDir(base, recursion);
	for (auto it = list.begin(); it != list.end(); ++it){
		auto pathname = it->namepath();
		if (!trueroot && (pathname.find(BitcasaInfiniteDrive) == 1) && (pathname != L"\\" + BitcasaInfiniteDrive)) {
			pathname = pathname.substr(BitcasaInfiniteDrive.length() + 1);
		}
		std::wstring prefix = (base == L"\\\\") ? L"\\" : (trueroot)? base.substr(1): base;
		if (pathname.find(prefix) == 0) pathname = pathname.substr(prefix.length());
		if (CheckFilePathMatch(target, pathname, regular)){
			if (it->id.empty()){
				pathname = pathname + L"\\";
			}
			if (param.usedatefilter){
				const FILETIME zerovalue = { 0 };
				FILETIME t = it->LastWriteTime;
				if ((CompareFileTime(&zerovalue, &param.startdate) != 0) && (CompareFileTime(&t, &param.startdate) < 0)){
					continue;
				}
				if ((CompareFileTime(&zerovalue, &param.enddate) != 0) && (CompareFileTime(&t, &param.enddate) > 0)){
					continue;
				}
			}
			if (param.usesizefilter){
				if ((param.minsize > 0) && (param.minsize > it->size)){
					continue;
				}
				if ((param.maxsize > 0) && (param.maxsize < it->size)){
					continue;
				}
			}
			if (param.remote.notree){
				if (pathname.find_last_of('\\') != pathname.length() - 1)
					it->display = it->name;
			}
			else {
				it->display = pathname;
			}
			if(param.verbose){
				std::stringstream ss;
				ss << std::endl;
				ss << "Size: " << it->size << " (" << bytestring(it->size) << ')' << std::endl;
				ss << "mTime: " << filetimestr(it->LastWriteTime) << std::endl;
				ss << "cTime: " << filetimestr(it->CreationTime) << std::endl;
				ss << "bTime: " << filetimestr(it->BirthTime) << std::endl;
				it->attribute = UTF8toWideChar(ss.str());
			}
			result.push_back(*it);
		}
	}
	std::wcerr << "remote match " << result.size() << "\n";
	return result;
}

INT64 size;
Tick starttick;

std::wstring RemoteFileHash(BitcasaAPI& API, std::string fileid, INT64 filesize, HashFilter::Hash hashtype)
{
	int retrycount = 3;
	while (retrycount-- > 0){
		HashFilter::hashfilter hasher(hashtype);
		starttick = GetTick();
		size = filesize;
		fwprintf(stderr, L"download...\n");
		bool done = API.DownloadFile(
			fileid,
			hasher,
			size,
			[](INT64 trans){
			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));
		});
		if (!done){
			fwprintf(stderr, L"\ndownload failed. \n");
			continue;
		}
		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());
	}
	return L"";
}

std::vector<std::wstring> findremote(BitcasaAPI& API, param::paramdata& param)
{
	std::vector<std::wstring> ret;
	auto list = remotesearch(API, param);
	size_t loopmax = list.size();
	size_t loopi = 0;
	std::for_each(
		list.begin(), 
		list.end(), 
		[&](BitcasaFileItem &it)
		{ 
			++loopi;
			if (!it.id.empty() && param.hashtype){
				std::wcerr << loopi << "/" << loopmax <<" hash check:" << it.name << std::endl;

				it.hashstr = RemoteFileHash(API, it.id, it.size, param.hashtype);
				if (param.faillog && it.hashstr.empty()){
					std::wofstream logfail("fail_download.log", std::ios::app);
					if (logfail){
						logfail << it.display << std::endl;
					}
				}
				//std::wcerr << hashinfo << std::endl;
			}
			ret.push_back(it.display + it.attribute + it.hashstr);
		});
	std::sort(ret.begin(), ret.end());
	return ret;
}

int listBitcasa(BitcasaAPI& API, param::paramdata& param)
{
	auto result = findremote(API, param);

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