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

const char *ClientID = "";
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");

BitcasaAPI::BitcasaAPI()
{
	std::wifstream tokenfile("token.txt");
	tokenfile >> access_token;
}


BitcasaAPI::~BitcasaAPI()
{
	if (!access_token.empty()) {
		std::wofstream tokenfile("token.txt");
		tokenfile << access_token;
	}
}

bool BitcasaAPI::prepare()
{
	if (!access_token.empty()) {
		auto root = ListFolder("/");
		if (root && (*root)["error"]->type == jsonitem::null) return true;
	}
	if (authenticate() == 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);
}

//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::wofstream tokenfile("token.txt");
		tokenfile << access_token;
	}
	return 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)(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;
}


//File Operations
// Download/Stream File
bool BitcasaAPI::DownloadFile(std::string encoded_file_id, std::wstring filename, INT64 wantSize, void(*callback)(INT64 trans))
{
	if (encoded_file_id.empty() || (encoded_file_id.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=") != std::string::npos)){
		std::cerr << "Invalid File id" << std::endl;
		return false;
	}
	std::string URI = API_Files + "/" + URLencode(encoded_file_id) + "/";
	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;
		}
	}
	std::ostream& out = (filename.empty()) ? std::cout : fout;
	
	std::wcerr << "download.";
	const int transbytes = 64 * 1024 * 1024;
	INT64 offset = 0;
	int retrycount = 100;
	while (true){
		if (callback) callback(offset);
		bitcasa.Clear();
		bitcasa.request.query["access_token"] = WideChartoUTF8(access_token);
		bitcasa.AddHeader("Range", dynamic_cast<std::stringstream &>(std::stringstream() << "bytes=" << offset << "-" << (offset + transbytes - 1)).str());
		if (bitcasa.GET(URI) != 0){
			if (--retrycount > 0)
				continue;
		}

		std::wcerr << ".";
		if (bitcasa.response.code == 503){
			Sleep(1000*10);
			if (--retrycount > 0)
				continue;
		}
		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;
			return false;
		}
		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){
			out.seekp(0, std::ios::beg);
			out.write(&bitcasa.response.body[0], bitcasa.response.body.size());
			offset = ContentLength;
			if ((wantSize >= 0) && (offset != wantSize))
				return false;
			break;
		}
		out.seekp(offset, std::ios::beg);
		out.write(&bitcasa.response.body[0], bitcasa.response.body.size());
		if (ContentLength < transbytes){
			if ((wantSize >= 0) && (offset+ContentLength != wantSize)){
				continue;
			}
			offset += ContentLength;
			break;
		}
		offset += transbytes;
	}
	if ((wantSize >= 0) && (offset != wantSize))
		return false;
	if (callback) callback(offset);
	std::wcerr << "done" << std::endl;
	return true;
}

//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;

}
