#include "stdafx.h"
#include "BitcasaAPI.h"
#include "aesfilter.h"

#using "WebWindow.dll"

using namespace System;
using namespace WebWindow;

using namespace System::Threading;

const char *ClientID = "aaefc0f0";
const char *ClientSecret = "";

const std::string APIbase("/v1");
const std::string API_authenticate(APIbase+"/oauth2/authenticate");
const std::string API_redirect("https://developer.api.bitcasa.com/v1/oauth2/accessing");
const std::string API_Granttoken(APIbase + "/oauth2/access_token");
const std::string API_Folders(APIbase + "/folders");
const std::string API_Files(APIbase + "/files");
const std::string API_Profile(APIbase + "/user/profile");

#ifdef _WIN64
int maxdownloadthread = 10;
#else
int maxdownloadthread = 4;
#endif

void SetMaxDownloadThread(int thread){
	maxdownloadthread = thread;
}

BitcasaAPI::BitcasaAPI()
{
	try{
		std::ifstream tokenfile("token", std::ios::binary);
		iAESfilter aes(tokenfile);
		std::string token;
		aes >> token;
		access_token = UTF8toWideChar(token);
	}
	catch (...){}
}


BitcasaAPI::~BitcasaAPI()
{
	if (!access_token.empty()) {
		std::ofstream tokenfile("token", std::ios::binary);
		oAESfilter aes(tokenfile);
		aes << WideChartoUTF8(access_token);
	}
}

bool BitcasaAPI::prepare()
{
	if (!access_token.empty()) {
		auto root = GetUserProfile();
		if (root && (*root)["error"]->type == jsonitem::null) return true;
	}
	if (authenticate_Web() == 0) return true;
	return false;
}

//Authentication
//	Login
int BitcasaAPI::authenticate()
{
	std::string USER;
	std::string PASSWORD;
	DWORD cMode=0;

	////////////////////////////////////////////////////////////////////////////////
	// load user/password
	////////////////////////////////////////////////////////////////////////////////
	std::cout << "Login for developer.api.bitcasa.com" << std::endl;
	std::cout << "Input Email: " ;
	std::getline(std::cin, USER);
	std::cout << "Input Password: ";
	GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &cMode);
	SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), (cMode & ~ENABLE_ECHO_INPUT));
	std::getline(std::cin, PASSWORD);
	SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), cMode);

	std::cout << std::endl << "Login... " << std::endl;
	///////////////////////////////////////////////////////////////////////////////

	///////////////////////////////////////////////////////////////////////////////
	//  Authentication Login stage 1
	//
	//   GET /oauth2/authenticate
	///////////////////////////////////////////////////////////////////////////////
	bitcasa.Reset();
	bitcasa.Clear();
	bitcasa.request.query["client_id"] = ClientID;
	bitcasa.request.query["redirect"] = API_redirect;
	bitcasa.GET(API_authenticate);
	cookie = MakeCookie(bitcasa.response.header);
	referrer = bitcasa.request.URI;

	if (bitcasa.response.code != 200) {
		std::cout << "unrecognize response" << std::endl;
		return 1;
	}

	///////  get post form ///////
	std::string form = &bitcasa.response.body[0];
	auto fstart = form.find("<form");
	auto fend = form.find("</form>");
	form = form.substr(fstart, fend - fstart);

	std::map<std::string, std::string> formparam;
	auto ind = form.find("<input");
	while (ind != std::string::npos) {
		auto ind_e = form.find("/>", ind);
		auto inputitem = form.substr(ind, ind_e - ind);
		auto name_ind = inputitem.find("name=\"")+6;
		auto nameitem = inputitem.substr(name_ind, inputitem.find('"', name_ind) - name_ind);
		auto value_ind = inputitem.find("value=\"");
		auto valueitem = (value_ind != std::string::npos) ? value_ind += 7, inputitem.substr(value_ind, inputitem.find('"', value_ind) - value_ind) : "";
		formparam[nameitem] = valueitem;
		ind = form.find("<input", ind_e + 2);
	}
	formparam["user"] = USER;
	formparam["password"] = PASSWORD;

	///////////////////////////////////////////////////////////////////////////////
	//  Authentication Login stage 2
	//
	//   POST /oauth2/authenticate
	///////////////////////////////////////////////////////////////////////////////
	bitcasa.Clear();
	bitcasa.AddHeader("Cookie", cookie);
	bitcasa.request.query["client_id"] = ClientID;
	bitcasa.POST(API_authenticate, formparam);
	cookie = MakeCookie(bitcasa.response.header);
	referrer = bitcasa.request.URI;

	if (bitcasa.response.code == 200) {
		std::cout << "Login failed." << std::endl;
		return 1;
	}
	if (bitcasa.response.code != 302) {
		std::cout << "unrecognize response" << std::endl;
		return 1;
	}

	///////////////////////////////////////////////////////////////////////////////
	//  Authentication Login stage 3
	//
	//   follow redirection
	///////////////////////////////////////////////////////////////////////////////
	auto redirect = bitcasa.GetResponseHeader("Location");
	std::string str1("http://"), str2("https://");
	auto pos = redirect.find(str1);
	if (pos != std::string::npos) redirect.replace(pos, str1.length(), str2);
	bitcasa.Clear();
	bitcasa.AddHeader("Cookie", cookie);
	bitcasa.AddHeader("Referer", referrer);
	bitcasa.request.query["client_id"] = ClientID;
	bitcasa.GET(redirect);
	referrer = bitcasa.request.URI;

	if (bitcasa.response.code == 200) {
		///////////////////////////////////////////////////////////////////////////////
		//  Authentication Login stage 4.1
		//
		//   Confirm Account Access
		///////////////////////////////////////////////////////////////////////////////
		std::string infomation = &bitcasa.response.body[0];
		if (infomation.find("Confirm Account Access") == std::string::npos) {
			std::cout << "unrecognize response" << std::endl;
			return 1;
		}
		//////// print permission infomation //////////
		auto istart = infomation.find(">",infomation.find("<h1"));
		auto iend = infomation.find("</h1>");
		std::cout << infomation.substr(istart + 1, iend - istart - 1) << std::endl;

		istart = infomation.find(">", infomation.find("<h2"));
		iend = infomation.find("</h2>");
		std::cout << "\t" << infomation.substr(istart + 1, iend - istart - 1) << std::endl;

		istart = infomation.find(">", infomation.find("<ul"));
		iend = infomation.find("</ul>");
		std::string permission = infomation.substr(istart + 1, iend - istart - 1);
		auto ind = permission.find("<li>");
		while (ind != std::string::npos) {
			ind += 4;
			auto ind_e = permission.find("</li>", ind);
			auto stritem = permission.substr(ind, ind_e - ind);
			std::cout << "\t\t* " << stritem << std::endl;
			ind = permission.find("<li>", ind);
		}

		//////// get form item //////////
		std::string form = &bitcasa.response.body[0];
		auto fstart = form.find("<form");
		auto fend = form.find("</form>");
		form = form.substr(fstart, fend - fstart);

		std::vector<std::string> choice;
		std::string submitname;
		ind = form.find("<input");
		while (ind != std::string::npos) {
			auto ind_e = form.find("/>", ind);
			auto inputitem = form.substr(ind, ind_e - ind);
			if (inputitem.find("type=\"submit\"") != std::string::npos) {
				auto name_ind = inputitem.find("name=\"") + 6;
				auto nameitem = inputitem.substr(name_ind, inputitem.find('"', name_ind) - name_ind);
				auto value_ind = inputitem.find("value=\"");
				auto valueitem = (value_ind != std::string::npos) ? value_ind += 7, inputitem.substr(value_ind, inputitem.find('"', value_ind) - value_ind) : "";
				submitname = nameitem;
				choice.push_back(valueitem);
			}
			ind = form.find("<input", ind_e + 2);
		}

		//////// ask your choice ///////
		int choice_no = 0;
		std::for_each(choice.begin(), choice.end(),
			[&](const std::string& it){std::cout << ++choice_no << ") " << it << std::endl; }
		);
		do {
			std::cout << "please select >";
			std::cin >> choice_no;
			std::cin.clear();
			std::cin.ignore(1024, '\n');
		} while ((choice_no < 1) || (choice_no > choice.size()));

		/////// create form parameter //////
		std::map<std::string, std::string> formparam;
		ind = form.find("<input");
		while (ind != std::string::npos) {
			auto ind_e = form.find("/>", ind);
			auto inputitem = form.substr(ind, ind_e - ind);
			if (inputitem.find("type=\"submit\"") != std::string::npos) {
				formparam[submitname] = choice[choice_no - 1];
			}
			else {
				auto name_ind = inputitem.find("name=\"") + 6;
				auto nameitem = inputitem.substr(name_ind, inputitem.find('"', name_ind) - name_ind);
				auto value_ind = inputitem.find("value=\"");
				auto valueitem = (value_ind != std::string::npos) ? value_ind += 7, inputitem.substr(value_ind, inputitem.find('"', value_ind) - value_ind) : "";
				formparam[nameitem] = valueitem;
			}
			ind = form.find("<input", ind_e + 2);
		}

		///////////////////////////////////////////////////////////////////////////////
		//  Authentication Login stage 4.2
		//
		//   POST
		///////////////////////////////////////////////////////////////////////////////
		bitcasa.Clear();
		bitcasa.AddHeader("Cookie", cookie);
		bitcasa.POST(referrer, formparam);
		cookie = MakeCookie(bitcasa.response.header);
		referrer = bitcasa.request.URI;

	}
	if (bitcasa.response.code != 302) {
		std::cout << "unrecognize response" << std::endl;
		return 1;
	}

	///////////////////////////////////////////////////////////////////////////////
	//  Authentication Login stage 5
	//
	//   get authorization_code
	///////////////////////////////////////////////////////////////////////////////
	redirect = bitcasa.GetResponseHeader("Location");
	std::string strauth = "authorization_code=";
	pos = redirect.find(strauth);
	if (pos == std::string::npos) {
		std::cout << "authorization failed" << std::endl;
		return 1;
	}
	auto authcode = redirect.substr(pos + strauth.size());
	bitcasa.Clear();

	return Grant_token(authcode);
}

// use Web compornent
int BitcasaAPI::authenticate_Web()
{
	LoginBitcasa login;
	System::String^ authorization_code = login.Login();

	if (String::IsNullOrEmpty(authorization_code)) return 1;

	std::string authcode = SystemStringToStdString(authorization_code);

	if (authcode.empty()) return 2;

	return Grant_token(authcode);
}

//Authentication
//	Grant access_token
int BitcasaAPI::Grant_token(std::string auth_code)
{
	bitcasa.Clear();
	bitcasa.request.query["code"] = auth_code;
	bitcasa.request.query["secret"] = ClientSecret;
	bitcasa.GET(API_Granttoken);

	if (bitcasa.response.code != 200) {
		std::cout << "Grant access_token failed" << std::endl;
		return 1;
	}
	auto result = jsonitem::Create(&bitcasa.response.body[0]);
	auto token = (*(*result)["result"])["access_token"]->GetString();
	access_token = token;

	if (!access_token.empty()) {
		std::ofstream tokenfile("token", std::ios::binary);
		oAESfilter aes(tokenfile);
		aes << WideChartoUTF8(access_token);
	}
	return 0;
}

//User Operations
// Get User Profile
pjson BitcasaAPI::GetUserProfile()
{
	std::string URI = API_Profile;
	bitcasa.Clear();
	bitcasa.request.query["access_token"] = WideChartoUTF8(access_token);
	bitcasa.GET(URI);

	if (bitcasa.response.code != 200) {
		std::cerr << "GetUserProfile failed" << std::endl;
		std::cerr << std::string(bitcasa.response.body.begin(), bitcasa.response.body.end()) << std::endl;
		return NULL;
	}

	return jsonitem::Create(&bitcasa.response.body[0]);
}


/*
File and Folder name restrictions

In the future we may relax the restrictions, however at the moment, the API has very strict requirements in an attempt to ensure no problems are caused in other clients.

All folder/file names are case insensitive, however the filesystem will present the folder/file name in the original case when possible.
Limit the folder/file name to 64 characters.  A path composed of folder and file names may be greater than 64 characters.
Names may not start with "."
Names may not contain any of the following characters: < > : " / \ | ? *

*/
bool FilenameIsValid(std::wstring name){
	return
		(!name.empty() &&
		name[0] != '.' &&
		name.find_first_of(L"<>:\"/\\|?*") == std::string::npos);
}

//File Operations
// List Folder
pjson BitcasaAPI::ListFolder(std::string encoded_path, bool deep)
{
	if (encoded_path.empty()) encoded_path = "/";
	if (encoded_path[0] != '/') encoded_path = "/" + encoded_path;
	std::string URI = API_Folders + encoded_path;
	bitcasa.Clear();
	bitcasa.request.query["access_token"] = WideChartoUTF8(access_token);
	if (deep) bitcasa.request.query["depth"] = "0";
	bitcasa.GET(URI);

	if (bitcasa.response.code != 200) {
		std::cerr << "ListFolder failed" << std::endl;
		std::cerr << std::string(bitcasa.response.body.begin(), bitcasa.response.body.end()) << std::endl;
		return NULL;
	}

	return jsonitem::Create(&bitcasa.response.body[0]);
}

//File Operations
// Add Folder
bool BitcasaAPI::AddFolder(std::string encoded_path, std::wstring newfoldername)
{
	if (!FilenameIsValid(newfoldername)){
		std::cerr << "newname is invalid" << std::endl;
		return false;
	}
	if (encoded_path.empty()) encoded_path = "/";
	if (encoded_path[0] != '/') encoded_path = "/" + encoded_path;
	std::string URI = API_Folders + encoded_path;
	bitcasa.Clear();
	bitcasa.request.query["access_token"] = WideChartoUTF8(access_token);
	std::map<std::string, std::string> formdata;
	formdata["folder_name"] = WideChartoUTF8(newfoldername);

	bitcasa.POST(URI,formdata);

	if (bitcasa.response.code != 200) {
		std::cerr << "AddFolder failed" << std::endl;
		std::cerr << std::string(bitcasa.response.body.begin(), bitcasa.response.body.end()) << std::endl;
		return false;
	}

	return true;
}

//File Operations
// Delete Folder
bool BitcasaAPI::DeleteFolder(std::string encoded_path)
{
	if (encoded_path.empty() || encoded_path == "/") return false;
	if (encoded_path[0] != '/') encoded_path = "/" + encoded_path;
	std::string URI = API_Folders;
	bitcasa.Clear();
	bitcasa.request.query["access_token"] = WideChartoUTF8(access_token);
	std::map<std::string, std::string> formdata;
	formdata["path"] = encoded_path;

	bitcasa._DELETE(URI, formdata);

	if (bitcasa.response.code != 200) {
		std::cerr<< "DeleteFolder failed" << std::endl;
		std::cerr << std::string(bitcasa.response.body.begin(), bitcasa.response.body.end()) << std::endl;
		return false;
	}

	return true;
}

//File Operations
// Rename Folder
bool BitcasaAPI::RenameFolder(std::string encoded_path, std::wstring newfoldername)
{
	if (!FilenameIsValid(newfoldername)){
		std::cerr << "newname is invalid" << std::endl;
		return false;
	}
	if (encoded_path.empty() || encoded_path == "/") return false;
	if (encoded_path[0] != '/') encoded_path = "/" + encoded_path;
	std::string URI = API_Folders;
	bitcasa.Clear();
	bitcasa.request.query["access_token"] = WideChartoUTF8(access_token);
	bitcasa.request.query["operation"] = "rename";
	std::map<std::string, std::string> formdata;
	formdata["from"] = encoded_path;
	formdata["filename"] = WideChartoUTF8(newfoldername);

	bitcasa.POST(URI, formdata);

	if (bitcasa.response.code != 200) {
		std::cerr << "RenameFolder failed" << std::endl;
		std::cerr << std::string(bitcasa.response.body.begin(), bitcasa.response.body.end()) << std::endl;
		return false;
	}

	return true;
}

//File Operations
// Copy/Move Folder
bool BitcasaAPI::CopyMoveFolder(std::string encoded_path_old, std::string encoded_path_new, std::wstring newfoldername, copymode mode)
{
	if (!FilenameIsValid(newfoldername)){
		std::cerr << "newname is invalid" << std::endl;
		return false;
	}
	if (encoded_path_old.empty()) encoded_path_old = "/";
	if (encoded_path_old[0] != '/') encoded_path_old = "/" + encoded_path_old;
	if (encoded_path_new.empty()) encoded_path_new = "/";
	if (encoded_path_new[0] != '/') encoded_path_new = "/" + encoded_path_new;
	std::string URI = API_Folders;
	bitcasa.Clear();
	bitcasa.request.query["access_token"] = WideChartoUTF8(access_token);
	if (mode == BitcasaAPI::copy)
		bitcasa.request.query["operation"] = "copy";
	if (mode == BitcasaAPI::move)
		bitcasa.request.query["operation"] = "move";
	std::map<std::string, std::string> formdata;
	formdata["from"] = encoded_path_old;
	formdata["to"] = encoded_path_new;
	formdata["filename"] = WideChartoUTF8(newfoldername);

	bitcasa.POST(URI, formdata);

	if (bitcasa.response.code != 200) {
		std::cerr << "Copy/MoveFolder failed" << std::endl;
		std::cerr << std::string(bitcasa.response.body.begin(), bitcasa.response.body.end()) << std::endl;
		return false;
	}

	return true;

}

//File Operations
// Upload File
bool BitcasaAPI::UploadFile(std::string encoded_path, std::wstring infilename, void(CALLBACK *callback)(INT64))
{
	if (encoded_path.empty()) encoded_path = "/";
	if (encoded_path[0] != '/') encoded_path = "/" + encoded_path;

	std::string URI = API_Files + encoded_path + "/";

	std::wcerr << "upload.";

	bitcasa.Clear();
	bitcasa.request.query["access_token"] = WideChartoUTF8(access_token);
	std::map<std::string, std::string> formdata;

	if (bitcasa.POSTfile(URI, formdata, infilename, callback) != 0){
		std::cerr << "\nUploadFile failed" << std::endl;
		return false;
	}

	if (bitcasa.response.code != 200) {
		std::cerr << "\nUploadFile failed" << std::endl;
		std::cerr << std::string(bitcasa.response.body.begin(), bitcasa.response.body.end()) << std::endl;
		return false;
	}

	std::wcerr << "done" << std::endl;
	return true;
}

ref class threadwork{
private:
	const int retrymax = 30;
	HttpsClient *bitcasa;
	std::string *access_token;
	std::string *URI;
	int retrycount = retrymax;
public:
	bool partial;
	HANDLE readySlave;
	HANDLE readyMaster;
	HANDLE die;
	HANDLE failDownload;
	INT64 offset;
	INT64 len;
	array<System::Byte>^ buf;

	threadwork(std::string url, std::string token){
		bitcasa = new HttpsClient();
		readySlave = CreateEvent(NULL, false, false, NULL);
		readyMaster = CreateEvent(NULL, false, false, NULL);
		die = CreateMutex(NULL, false, NULL);
		access_token = new std::string;
		URI = new std::string;
		buf = gcnew array<System::Byte>(0);;
		*URI = url;
		*access_token = token;
	}
	~threadwork(){
		this->!threadwork();
	}
	!threadwork(){
		CloseHandle(readySlave);
		CloseHandle(readyMaster);
		delete access_token;
		delete URI;
		delete buf;
		delete bitcasa;
	}
	void Abort(){
		bitcasa->Abort();
	}
	void DoWork(){
		while (WaitForSingleObject(die, 0) == WAIT_TIMEOUT){
			if ((WaitForSingleObject(readySlave, INFINITE) != WAIT_OBJECT_0) || (WaitForSingleObject(die, 0) != WAIT_TIMEOUT))
				break;
			//std::cerr << "thread " << GetCurrentThreadId() << " wake." << std::endl;
			//std::cerr << "thread " << GetCurrentThreadId() << " offset:" << offset << std::endl;
			bitcasa->Clear();
			bitcasa->request.query["access_token"] = *access_token;
			bitcasa->AddHeader("Range", dynamic_cast<std::stringstream &>(std::stringstream() << "bytes=" << offset << "-" << (offset + len - 1)).str());
			if (bitcasa->GET(*URI) != 0){
				//std::cerr << "thread " << GetCurrentThreadId() << " DownloadFile failed" << std::endl;
				//std::cerr << bitcasa->response.line << std::endl;
				//for (auto it = bitcasa->response.header.begin(); it != bitcasa->response.header.end(); ++it)
				//	std::cerr << it->first << ": " << it->second << std::endl;
				//std::cerr << std::string(bitcasa->response.body.begin(), bitcasa->response.body.end()) << std::endl;
				SetEvent(readySlave);
				Sleep(1000 * 10);
				if (--retrycount > 0)
					continue;
				SetEvent(failDownload);
				break;
			}

			//std::wcerr << ".";
			if (bitcasa->response.code == 503){
				SetEvent(readySlave);
				Sleep(1000 * 10);
				if (--retrycount > 0)
					continue;
				SetEvent(failDownload);
				break;
			}
			if ((bitcasa->response.code != 200) && (bitcasa->response.code != 206)) {
				//std::wcerr << std::endl;
				//std::cerr << "DownloadFile failed" << std::endl;
				//std::cerr << std::string(bitcasa->response.body.begin(), bitcasa->response.body.end()) << std::endl;
				buf = gcnew array<System::Byte>(0);
				SetEvent(readySlave);
				Sleep(1000 * 10);
				if (--retrycount > 0)
					continue;
				SetEvent(failDownload);
				break;
			}
			retrycount = retrymax;
			INT64 ContentLength = -1;
			auto it_length = bitcasa->response.header.find("Content-Length");
			std::stringstream((it_length != bitcasa->response.header.end()) ? it_length->second : "") >> ContentLength;
			if (bitcasa->response.code == 200){
				partial = false;
			}
			else {
				partial = true;
			}
			buf = gcnew array<System::Byte>((int)bitcasa->response.body.size());
			System::Runtime::InteropServices::Marshal::Copy((System::IntPtr)bitcasa->response.body.data(), buf, 0, buf->Length);

			//std::cerr << "thread " << GetCurrentThreadId() << "done. offset:" << offset << std::endl;
			SetEvent(readyMaster);
		}
		//std::cerr << "thread " << GetCurrentThreadId() << " done." << std::endl;
	}
};

//File Operations
// Download/Stream File
bool BitcasaAPI::DownloadFile(std::string encoded_file_id, std::wstring filename, INT64 wantSize, void(CALLBACK *callback)(INT64 trans))
{
	std::ofstream fout;
	if (!filename.empty()){
		fout.open(filename, std::ios::out | std::ios::binary);
		if (fout.fail()){
			std::wcerr << "cannot open file :" << filename << std::endl;
			return false;
		}
	}
	return DownloadFile(encoded_file_id, (filename.empty()) ? std::cout : fout, wantSize, callback);
}

bool BitcasaAPI::DownloadFile(std::string encoded_file_id, std::ostream& out, INT64 wantSize, void(CALLBACK *callback)(INT64 trans))
{
	if (wantSize < 0) return false;
	if (encoded_file_id.empty() || (encoded_file_id.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=") != std::string::npos)){
		std::wcerr << "Invalid File id" << std::endl;
		return false;
	}
	std::string URI = API_Files + "/" + URLencode(encoded_file_id) + "/";
	//std::wcerr << "download.";

	const int transbytes = 64 * 1024 * 1024;
	INT64 offset = 0;
	INT64 progress = 0;
	int working;
	int threadcount = (int)(wantSize / transbytes) + 1;
	threadcount = (threadcount > maxdownloadthread) ? maxdownloadthread : threadcount;

	System::Collections::Generic::List<Thread ^> workThreads;
	System::Collections::Generic::List<threadwork^> data;
	std::vector<HANDLE> waithandles;
	HANDLE failDownload = CreateEvent(NULL, true, false, NULL);
	for (int i = 0; i < threadcount; i++){
		threadwork^ work = gcnew threadwork(URI, WideChartoUTF8(access_token));
		data.Add(work);
		HANDLE h = work->readyMaster;
		waithandles.push_back(h);
		WaitForSingleObject(work->die, INFINITE);
		work->failDownload = failDownload;
		workThreads.Add(gcnew Thread(gcnew ThreadStart(work, &threadwork::DoWork)));
	}
	waithandles.push_back(failDownload);
	for (int i = 0; i < threadcount; i++){
		workThreads[i]->Start();
	}
	for (working = 0; working < threadcount; working++){
		threadwork^ work = data[working];
		work->len = (offset+transbytes < wantSize)? transbytes: wantSize-offset;
		work->offset = offset;
		SetEvent(work->readySlave);
		offset += work->len;
		Sleep(5*1000);
	}
	try{
		while (working > 0){
			auto ret = WaitForMultipleObjects((DWORD)waithandles.size(), &waithandles[0], false, 3*60*1000);
			int thread_no = -1;
			if ((ret >= WAIT_OBJECT_0) && (ret < WAIT_OBJECT_0 + waithandles.size())) thread_no = ret - WAIT_OBJECT_0;
			if ((ret >= WAIT_ABANDONED_0) && (ret < WAIT_ABANDONED_0 + waithandles.size())) thread_no = ret - WAIT_ABANDONED_0;
			if (ret == WAIT_TIMEOUT) 
			{
				//std::cerr << "thread wait timeout" << std::endl;
				for (int i = 0; i < threadcount; i++){
					ReleaseMutex(data[i]->die);
					SetEvent(data[i]->readySlave);
					data[i]->Abort();
				}
				for (int i = 0; i < threadcount; i++){
					workThreads[i]->Abort();
				}
			}
			//std::cerr << "thread return:" << thread_no << std::endl;
			if (thread_no < 0) return false;
			if (thread_no == threadcount) return false;

			threadwork^ work = data[thread_no];

			if (work->partial){
				// 206 success range
				if (work->buf->Length == work->len)
				{
					out.seekp(work->offset, std::ios::beg);
					pin_ptr<System::Byte> dp = &work->buf[0];
					out.write((char *)dp, work->buf->Length);
					dp = nullptr;
					if (callback)
					{
						progress = (progress < work->offset) ? work->offset : progress;
						callback(progress);
					}
					if (offset < wantSize){
						work->len = (offset + transbytes < wantSize) ? transbytes : wantSize - offset;
						work->offset = offset;
						SetEvent(work->readySlave);
						offset += work->len;
						continue;
					}
					else {
						ReleaseMutex(work->die);
						working--;
						continue;
					}
				}
				std::cerr << "206 unmatch offset:" << work->offset << " want:" << work->len << " ret:" << work->buf->Length << " " << std::endl;
			}
			else{
				// 200 want range but return all
				out.seekp(0, std::ios::beg);
				pin_ptr<System::Byte> dp = &work->buf[0];
				out.write((char *)dp, work->buf->Length);
				dp = nullptr;
				if (work->buf->Length == wantSize)
				{
					offset = work->buf->Length;
					break;
				}
				//std::cerr << "200 unmatch want:" << wantSize << " ret:" << work->buf->size() << std::endl;
			}
			// retry
			//std::cerr << "retry " << work->offset << ":" << work->len << std::endl;
			SetEvent(work->readySlave);
		}
		if (callback) callback(offset);
		//std::wcerr << "done" << std::endl;
		return true;
	}
	finally{
		for (int i = 0; i < threadcount; i++){
			ReleaseMutex(data[i]->die);
			SetEvent(data[i]->readySlave);
		}
		for (int i = 0; i < threadcount; i++){
			workThreads[i]->Join();
		}
		for (int i = 0; i < threadcount; i++){
			delete data[i];
		}
		for (int i = 0; i < threadcount; i++){
			delete workThreads[i];
		}
	}
}

//File Operations
// Delete File
bool BitcasaAPI::_DeleteFile(std::string encoded_path)
{
	if (encoded_path.empty() || encoded_path == "/") return false;
	if (encoded_path[0] != '/') encoded_path = "/" + encoded_path;
	std::string URI = API_Files;
	bitcasa.Clear();
	bitcasa.request.query["access_token"] = WideChartoUTF8(access_token);
	std::map<std::string, std::string> formdata;
	formdata["path"] = encoded_path;

	bitcasa._DELETE(URI, formdata);

	if (bitcasa.response.code != 200) {
		std::cerr << "DeleteFile failed" << std::endl;
		std::cerr << std::string(bitcasa.response.body.begin(), bitcasa.response.body.end()) << std::endl;
		return false;
	}

	return true;
}

//File Operations
// Rename File
bool BitcasaAPI::RenameFile(std::string encoded_path, std::wstring newfoldername)
{
	if (!FilenameIsValid(newfoldername)){
		std::cerr << "newname is invalid" << std::endl;
		return false;
	}
	if (encoded_path.empty() || encoded_path == "/") return false;
	if (encoded_path[0] != '/') encoded_path = "/" + encoded_path;
	std::string URI = API_Files;
	bitcasa.Clear();
	bitcasa.request.query["access_token"] = WideChartoUTF8(access_token);
	bitcasa.request.query["operation"] = "rename";
	std::map<std::string, std::string> formdata;
	formdata["from"] = encoded_path;
	formdata["filename"] = WideChartoUTF8(newfoldername);

	bitcasa.POST(URI, formdata);

	if (bitcasa.response.code != 200) {
		std::cerr << "RenameFile failed" << std::endl;
		std::cerr << std::string(bitcasa.response.body.begin(), bitcasa.response.body.end()) << std::endl;
		return false;
	}

	return true;
}

//File Operations
// Copy/Move Folder
bool BitcasaAPI::CopyMoveFile(std::string encoded_path_old, std::string encoded_path_new, std::wstring newfoldername, copymode mode)
{
	if (!FilenameIsValid(newfoldername)){
		std::cerr << "newname is invalid" << std::endl;
		return false;
	}
	if (encoded_path_old.empty()) encoded_path_old = "/";
	if (encoded_path_old[0] != '/') encoded_path_old = "/" + encoded_path_old;
	if (encoded_path_new.empty()) encoded_path_new = "/";
	if (encoded_path_new[0] != '/') encoded_path_new = "/" + encoded_path_new;
	std::string URI = API_Files;
	bitcasa.Clear();
	bitcasa.request.query["access_token"] = WideChartoUTF8(access_token);
	if (mode == BitcasaAPI::copy)
		bitcasa.request.query["operation"] = "copy";
	if (mode == BitcasaAPI::move)
		bitcasa.request.query["operation"] = "move";
	std::map<std::string, std::string> formdata;
	formdata["from"] = encoded_path_old;
	formdata["to"] = encoded_path_new;
	formdata["filename"] = WideChartoUTF8(newfoldername);

	bitcasa.POST(URI, formdata);

	if (bitcasa.response.code != 200) {
		std::cerr << "Copy/MoveFile failed" << std::endl;
		std::cerr << std::string(bitcasa.response.body.begin(), bitcasa.response.body.end()) << std::endl;
		return false;
	}

	return true;

}

