// BitcasaDiff.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

using namespace System::Threading;

void printusage(param::modetype mode)
{
	_TCHAR exepath[1024];
	if (GetModuleFileName(NULL, exepath, sizeof(exepath) / sizeof(_TCHAR)-1) != 0){
		DWORD buflen = GetFileVersionInfoSize(exepath, 0);
		if (buflen != 0){
			void *buf = new BYTE[buflen];
			VS_FIXEDFILEINFO *var;
			UINT varlen;
			GetFileVersionInfo(exepath, 0, buflen, buf);
			if (VerQueryValue(buf, _T("\\"), (void **)&var, &varlen)){
				_tprintf(_T("BitcasaDiff version %d.%d.%d.%d\n\n"),
					HIWORD(var->dwProductVersionMS),
					LOWORD(var->dwProductVersionMS),
					HIWORD(var->dwProductVersionLS),
					LOWORD(var->dwProductVersionLS));

			}
			delete[] buf;
		}
	}
	switch (mode)
	{
	case param::null:
		_tprintf(_T("BitcasaDiff [ list | local | diff ]\n"));
		break;
	case param::list:
		_tprintf(_T("BitcasaDiff list [remotetarget]\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/remotebase (path) : set remote search basepath\n"));
		_tprintf(_T("\t/remotetarget (target) : set remote search target\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/r : search into subdirectories\n"));
		_tprintf(_T("\t/d : ignore directory structure.\n"));
		_tprintf(_T("\t/regular : treat target string as regular expression\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/hash (type): download and calculate hash\n"));
		_tprintf(_T("\t\t type=[MD5|SHA1|SHA256|SHA384|SHA512|RIPEMD160]\n"));
		_tprintf(_T("\t/maxthread (value): set max threads for download\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/size (minsize)-(maxsize)  : size filter\n"));
		_tprintf(_T("\t\t ex. -200 : less than or equal to 200byte\n"));
		_tprintf(_T("\t\t     10M- : greater than or equal to 10,485,760byte\n"));
		_tprintf(_T("\t\t     10-100 : bitween 10byte and 100byte\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/date (start)-(end) : mtime filter\n"));
		_tprintf(_T("\t\t ex. -20100101 : before 2012/01/01 00:00:00\n"));
		_tprintf(_T("\t\t     1980/01/01.12:00- : after 1980/01/01 12:00\n"));
		_tprintf(_T("\t\t     19800101120000000-2012/12/31,12:30:12.000\n"));
		_tprintf(_T("\t\t       year must be 4 digits.\n"));
		_tprintf(_T("\t\t       month,day,hour,minute,second must be 2 digits.\n"));
		_tprintf(_T("\t\t       millisecond must be 3 digits.\n"));
		_tprintf(_T("\t\t       any charactor for timefield separator is ignored.\n"));
		_tprintf(_T("\t\t       cannot use '-' and ' ' for timefield separator.\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/v : verbose output\n"));
		_tprintf(_T("\t/faillog : output log file for download failure\n"));
		break;
	case param::local:
		_tprintf(_T("BitcasaDiff local [localtarget]\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/localbase (path) : set local search basepath\n"));
		_tprintf(_T("\t/localtarget (target) : set local search target\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/r : search into subdirectories\n"));
		_tprintf(_T("\t/d : ignore directory structure.\n"));
		_tprintf(_T("\t/regular : treat target string as regular expression\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/hash (type): calculate hash\n"));
		_tprintf(_T("\t\t type=[MD5|SHA1|SHA256|SHA384|SHA512|RIPEMD160]\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/size (minsize)-(maxsize)  : size filter\n"));
		_tprintf(_T("\t\t ex. -200 : less than or equal to 200byte\n"));
		_tprintf(_T("\t\t     10M- : greater than or equal to 10,485,760byte\n"));
		_tprintf(_T("\t\t     10-100 : bitween 10byte and 100byte\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/date (start)-(end) : mtime filter\n"));
		_tprintf(_T("\t\t ex. -20100101 : before 2012/01/01 00:00:00\n"));
		_tprintf(_T("\t\t     1980/01/01.12:00- : after 1980/01/01 12:00\n"));
		_tprintf(_T("\t\t     19800101120000000-2012/12/31,12:30:12.000\n"));
		_tprintf(_T("\t\t       year must be 4 digits.\n"));
		_tprintf(_T("\t\t       month,day,hour,minute,second must be 2 digits.\n"));
		_tprintf(_T("\t\t       millisecond must be 3 digits.\n"));
		_tprintf(_T("\t\t       any charactor for timefield separator is ignored.\n"));
		_tprintf(_T("\t\t       cannot use '-' and ' ' for timefield separator.\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/v : verbose output\n"));
		break;
	case param::diff:
		_tprintf(_T("BitcasaDiff diff [localtarget] [remotetarget]\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/localbase (path) : set local search basepath\n"));
		_tprintf(_T("\t/localtarget (target) : set local search target\n"));
		_tprintf(_T("\t/remotebase (path) : set remote search basepath\n"));
		_tprintf(_T("\t/remotetarget (target) : set remote search target\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/regular : treat target string as regular expression\n"));
		_tprintf(_T("\t/localregex : treat local target string as regular expression\n"));
		_tprintf(_T("\t/remoteregex : treat local target string as regular expression\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/r : search into subdirectories(both remote and local)\n"));
		_tprintf(_T("\t/localr : search into local subdirectories\n"));
		_tprintf(_T("\t/remoter : search into remote subdirectories\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/d : ignore directory structure.\n"));
		_tprintf(_T("\t/locald : ignore directory structure in local.\n"));
		_tprintf(_T("\t/remoted : ignore directory structure in remote.\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/ignoretime : ignore time difference.\n"));
		_tprintf(_T("\t/ignoresize : ignore size difference.\n"));
		_tprintf(_T("\t/hash (type): download and check hash difference\n"));
		_tprintf(_T("\t\t type=[MD5|SHA1|SHA256|SHA384|SHA512|RIPEMD160]\n"));
		_tprintf(_T("\t/maxthread (value): set max threads for download\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/size (minsize)-(maxsize)  : size filter\n"));
		_tprintf(_T("\t\t ex. -200 : less than or equal to 200byte\n"));
		_tprintf(_T("\t\t     10M- : greater than or equal to 10,485,760byte\n"));
		_tprintf(_T("\t\t     10-100 : bitween 10byte and 100byte\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/date (start)-(end) : mtime filter\n"));
		_tprintf(_T("\t\t ex. -20100101 : before 2012/01/01 00:00:00\n"));
		_tprintf(_T("\t\t     1980/01/01.12:00- : after 1980/01/01 12:00\n"));
		_tprintf(_T("\t\t     19800101120000000-2012/12/31,12:30:12.000\n"));
		_tprintf(_T("\t\t       year must be 4 digits.\n"));
		_tprintf(_T("\t\t       month,day,hour,minute,second must be 2 digits.\n"));
		_tprintf(_T("\t\t       millisecond must be 3 digits.\n"));
		_tprintf(_T("\t\t       any charactor for timefield separator is ignored.\n"));
		_tprintf(_T("\t\t       cannot use '-' and ' ' for timefield separator.\n"));
		_tprintf(_T("\n"));
		_tprintf(_T("\t/v : verbose output\n"));
		break;
	default:
		break;
	}

	_tprintf(_T("\n"));
}

void process_sizestr(_TCHAR *sizestr, param::paramdata &param)
{
	_TCHAR *delim = _tcschr(sizestr, '-');
	if (delim == NULL){
		param.minsize = -1;
		param.maxsize = -1;
	}
	else {
		*delim = NULL;
		param.minsize = GetByteValue(sizestr);
		delim++;
		param.maxsize = GetByteValue(delim);
	}
	if ((param.minsize < 0) || (param.maxsize < 0)){
		param.usesizefilter = false;
	}
	else {
		if ((param.minsize > 0) && (param.maxsize > 0)){
			if (param.minsize > param.maxsize){
				INT64 tmp = param.minsize;
				param.minsize = param.maxsize;
				param.maxsize = tmp;
			}
		}
		param.usesizefilter = true;
	}
}

void process_datestr(_TCHAR *datestr, param::paramdata &param)
{
	_TCHAR *delim = _tcschr(datestr, '-');
	param.startdate.dwHighDateTime = 0xFFFFFFFF;
	param.startdate.dwLowDateTime = 0xFFFFFFFF;
	param.enddate.dwHighDateTime = 0xFFFFFFFF;
	param.enddate.dwLowDateTime = 0xFFFFFFFF;
	if (delim == NULL){
		param.usedatefilter = false;
	}
	else {
		ULARGE_INTEGER value;
		param.usedatefilter = true;
		*delim = NULL;
		value.QuadPart = GetDateValue(datestr);
		if (value.QuadPart == -1){
			param.usedatefilter = false;
		}
		else {
			param.startdate.dwHighDateTime = value.HighPart;
			param.startdate.dwLowDateTime = value.LowPart;
		}
		delim++;
		value.QuadPart = GetDateValue(delim);
		if (value.QuadPart == -1){
			param.usedatefilter = false;
		}
		else {
			param.enddate.dwHighDateTime = value.HighPart;
			param.enddate.dwLowDateTime = value.LowPart;
		}
	}
	if (param.usedatefilter) {
		ULARGE_INTEGER value;
		FILETIME zerovalue;
		value.QuadPart = 0;
		zerovalue.dwHighDateTime = value.HighPart;
		zerovalue.dwLowDateTime = value.LowPart;
		if ((CompareFileTime(&zerovalue, &param.startdate) != 0) && (CompareFileTime(&zerovalue, &param.enddate) != 0))
		if (CompareFileTime(&param.startdate, &param.enddate) > 0){
			DWORD tmp1, tmp2;
			tmp1 = param.startdate.dwHighDateTime;
			tmp2 = param.startdate.dwLowDateTime;
			param.startdate.dwHighDateTime = param.enddate.dwHighDateTime;
			param.startdate.dwLowDateTime = param.enddate.dwLowDateTime;
			param.enddate.dwHighDateTime = tmp1;
			param.enddate.dwLowDateTime = tmp2;
		}
	}
}

void process_hashstr(_TCHAR *hashstr, param::paramdata &param)
{
	if ((_tcsstr(hashstr, _T("md5")) != NULL) || (_tcsstr(hashstr, _T("MD5")) != NULL))
		param.hashtype = (HashFilter::Hash)(param.hashtype | HashFilter::MD5);
	if ((_tcsstr(hashstr, _T("sha1")) != NULL) || (_tcsstr(hashstr, _T("SHA1")) != NULL))
		param.hashtype = (HashFilter::Hash)(param.hashtype | HashFilter::SHA1);
	if ((_tcsstr(hashstr, _T("sha256")) != NULL) || (_tcsstr(hashstr, _T("SHA256")) != NULL))
		param.hashtype = (HashFilter::Hash)(param.hashtype | HashFilter::SHA256);
	if ((_tcsstr(hashstr, _T("sha384")) != NULL) || (_tcsstr(hashstr, _T("SHA384")) != NULL))
		param.hashtype = (HashFilter::Hash)(param.hashtype | HashFilter::SHA384);
	if ((_tcsstr(hashstr, _T("sha512")) != NULL) || (_tcsstr(hashstr, _T("SHA512")) != NULL))
		param.hashtype = (HashFilter::Hash)(param.hashtype | HashFilter::SHA512);
	if ((_tcsstr(hashstr, _T("ripemd160")) != NULL) || (_tcsstr(hashstr, _T("RIPEMD160")) != NULL))
		param.hashtype = (HashFilter::Hash)(param.hashtype | HashFilter::RIPEMD160);
}

ref class STA_helper
{
public:
	BitcasaAPI *API;
	bool Result;

	void DoWork()
	{
		// ログインウインドウを出すIEコンポーネントがSTAでないと動かないので
		// 別スレッドを作ってそちらで動かす.
		Result = API->prepare();
	}

	bool Run()
	{
		auto t = gcnew Thread(gcnew ThreadStart(this, &STA_helper::DoWork));

		// これから作るスレッドを、STAに設定する
		if (!t->TrySetApartmentState(ApartmentState::STA))
		{
			return false;
		}
		t->Start();
		t->Join();
		return Result;
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	_setmode(_fileno(stdout), _O_U8TEXT);
	setlocale(LC_ALL, "");

	// parameter initialize
	param::paramdata param = {};
	param.mode = param::null;

	// no parameter
	if (argc < 2){
		printusage(param.mode);
		return 1;
	}
	// mode parameter test
	if (_tcsicmp(argv[1], _T("list")) == 0){
		param.mode = param::list;
	}
	if (_tcsicmp(argv[1], _T("local")) == 0){
		param.mode = param::local;
	}
	if (_tcsicmp(argv[1], _T("diff")) == 0){
		param.mode = param::diff;
	}

	// mode parameter not found
	if (param.mode == param::null){
		printusage(param.mode);
		return 1;
	}

	// parse options
	for (int i = 2; i < argc; i++) {
		// options
		switch (argv[i][0]) {
		case _T('/'):
		case _T('-'):
			if (_tcsicmp(&argv[i][1], _T("date")) == 0){
				_TCHAR *datestr = (i + 1 < argc) ? argv[i + 1] : NULL;
				i++;
				if (datestr != NULL) {
					process_datestr(datestr, param);
					if (param.usedatefilter){
						_ftprintf(stderr, _T(" date : %s - %s\n"), filetimestr(param.startdate).c_str(), filetimestr(param.enddate).c_str());
					}
				}
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("faillog")) == 0){
				param.faillog = true;
				fprintf(stderr, " download failure log\n");
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("hash")) == 0){
				_TCHAR *str = (i + 1 < argc) ? argv[i + 1] : NULL;
				i++;
				if (str != NULL) {
					process_hashstr(str, param);
					if (param.hashtype != 0){
						_ftprintf(stderr, _T(" hash: "));
						if ((param.hashtype & HashFilter::MD5) != 0)
							_ftprintf(stderr, _T("MD5 "));
						if ((param.hashtype & HashFilter::SHA1) != 0)
							_ftprintf(stderr, _T("SHA1 "));
						if ((param.hashtype & HashFilter::SHA256) != 0)
							_ftprintf(stderr, _T("SHA256 "));
						if ((param.hashtype & HashFilter::SHA384) != 0)
							_ftprintf(stderr, _T("SHA384 "));
						if ((param.hashtype & HashFilter::SHA512) != 0)
							_ftprintf(stderr, _T("SHA512 "));
						if ((param.hashtype & HashFilter::RIPEMD160) != 0)
							_ftprintf(stderr, _T("RIPEMD160 "));
						_ftprintf(stderr, _T("\n"));
					}
				}
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("ignoresize")) == 0){
				param.ignoresize = true;
				fprintf(stderr, " ignore size difference\n");
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("ignoretime")) == 0){
				param.ignoretime = true;
				fprintf(stderr, " ignore time difference\n");
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("localbase")) == 0){
				param.local.basepath = (i + 1 < argc) ? argv[i + 1] : _T("");
				i++;
				if (!param.local.basepath.empty()) {
					_ftprintf(stderr, _T(" localbase : %s\n"), param.local.basepath.c_str());
				}
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("locald")) == 0){
				param.local.notree = true;
				fprintf(stderr, " ignore local directroy tree\n");
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("localr")) == 0){
				param.local.recursion = true;
				fprintf(stderr, " local recursion mode\n");
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("localregex")) == 0){
				param.local.regular = true;
				_ftprintf(stderr, _T(" local regular expression mode\n"));
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("localtarget")) == 0){
				param.local.targetstr = (i + 1 < argc) ? argv[i + 1] : _T("");
				i++;
				if (!param.local.targetstr.empty()) {
					_ftprintf(stderr, _T(" localtarget : %s\n"), param.local.targetstr.c_str());
				}
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("maxthread")) == 0){
				_TCHAR *sizestr = (i + 1 < argc) ? argv[i + 1] : NULL;
				i++;
				if (sizestr != NULL) {
					auto thread = GetByteValue(sizestr);
					thread = (thread > 100) ? 100 : thread;
					SetMaxDownloadThread((int)thread);
					fprintf(stderr, " download thread max:%I64d\n", thread);
				}
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("regex")) == 0){
				param.remote.regular = true;
				param.local.regular = true;
				_ftprintf(stderr, _T(" regular expression mode\n"));
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("regular")) == 0){
				param.remote.regular = true;
				param.local.regular = true;
				_ftprintf(stderr, _T(" regular expression mode\n"));
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("remotebase")) == 0){
				param.remote.basepath = (i + 1 < argc) ? argv[i + 1] : _T("");
				i++;
				if (!param.remote.basepath.empty()) {
					_ftprintf(stderr, _T(" remotebase : %s\n"), param.remote.basepath.c_str());
				}
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("remoted")) == 0){
				param.remote.notree = true;
				fprintf(stderr, " ignore remote directroy tree\n");
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("remoter")) == 0){
				param.remote.recursion = true;
				fprintf(stderr, " remote recursion mode\n");
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("remoteregex")) == 0){
				param.remote.regular = true;
				_ftprintf(stderr, _T(" remote regular expression mode\n"));
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("remotetarget")) == 0){
				param.remote.targetstr = (i + 1 < argc) ? argv[i + 1] : _T("");
				i++;
				if (!param.remote.targetstr.empty()) {
					_ftprintf(stderr, _T(" remotetarget : %s\n"), param.remote.targetstr.c_str());
				}
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("size")) == 0){
				_TCHAR *sizestr = (i + 1 < argc) ? argv[i + 1] : NULL;
				i++;
				if (sizestr != NULL) {
					process_sizestr(sizestr, param);
					if (param.usesizefilter){
						fprintf(stderr, " size : %I64d - %I64d\n", param.minsize, param.maxsize);
					}
				}
				continue;
			}
			if (_tcsicmp(&argv[i][1], _T("-help")) == 0){
				printusage(param.mode);
				return 0;
			}
			switch (argv[i][1]) {
			case _T('?'):
				printusage(param.mode);
				return 0;
			case _T('d'):
			case _T('D'):
				param.remote.notree = true;
				param.local.notree = true;
				fprintf(stderr, " ignore directroy tree\n");
				continue;
			case _T('r'):
			case _T('R'):
				param.remote.recursion = true;
				param.local.recursion = true;
				fprintf(stderr, " recursion mode\n");
				continue;
			case _T('v'):
			case _T('V'):
				param.verbose = true;
				fprintf(stderr, " verbose output\n");
				continue;
			}
		case 0:
			continue;
		}

		// essential parameter
		switch (param.mode)
		{
		case param::list:
			if (param.remote.targetstr.empty()){
				param.remote.targetstr = argv[i];
				continue;
			}
			break;
		case param::local:
			if (param.local.targetstr.empty()){
				param.local.targetstr = argv[i];
				continue;
			}
			break;
		case param::diff:
			if (param.local.targetstr.empty()){
				param.local.targetstr = argv[i];
				continue;
			}
			if (param.remote.targetstr.empty()){
				param.remote.targetstr = argv[i];
				continue;
			}
			break;
		case param::null:
		default:
			break;
		}

		fprintf(stderr, "illegal argument\n");
		printusage(param.mode);
		return 1;
	}

	// essential parameter check
	switch (param.mode)
	{
	case param::list:
		if (!param.remote.targetstr.empty())
			break;
	case param::local:
		if (!param.local.targetstr.empty())
			break;
	case param::diff:
		if (!param.remote.targetstr.empty() && !param.local.targetstr.empty())
			break;
	case param::null:
	default:
		printusage(param.mode);
		return 1;
	}

	// option print(for debug)
	switch (param.mode)
	{
	case param::list:
		fprintf(stderr, " remote list mode \n");
		_ftprintf(stderr, _T(" remote target:%s\n"), param.remote.targetstr.c_str());
		break;
	case param::local:
		fprintf(stderr, " local list mode \n");
		_ftprintf(stderr, _T(" local target:%s\n"), param.local.targetstr.c_str());
		break;
	case param::diff:
		fprintf(stderr, " diff mode \n");
		_ftprintf(stderr, _T(" remote target:%s\n"), param.remote.targetstr.c_str());
		_ftprintf(stderr, _T(" local target:%s\n"), param.local.targetstr.c_str());
		break;
	case param::null:
	default:
		break;
	}

	// load BitcasaAPI module
	auto API = new BitcasaAPI;
	auto helper = gcnew STA_helper;

	helper->API = API;
	if (!helper->Run()) {
		fprintf(stderr, "BitcasaAPI initialize error\n");
		return -1;
	}

	return mainproc(*API, param);
}
// normal exit 0
// BitcasaAPI error(auth fail) -1
// argument error 1
