/*
 * myLibS3.cpp
 *
 *  Created on: Jun 28, 2014
 *      Author: nick
 */

#include "myLibS3.h"
#include "myListObj.h"
#include "myGetObj.h"
#include "myPutObj.h"
#include "myDeleteObj.h"
#include "myDir.h"
#include "myS3Common.h"
#include <cstdio>
#include <libgen.h>
#include <cstdlib>
#include <cstring>
#include <string>

using namespace std;

extern string getFileContentType(const string& fileName);


bool writeS3Objects(const string& strFilename, const S3ObjSet& list)
{
	ofstream out(strFilename,  std::ofstream::out | std::ofstream::trunc);
	if (out.is_open())
	{
		for (S3ObjSet::const_iterator it = list.begin(); it != list.end(); it++)
		{
			out << it->key << endl << it->size << endl << it->time << endl << it->tag << endl;
		}
	}
	else
	{
		return false;
	}
	return true;
}

bool readS3Objects(const string& strFilename, S3ObjSet& list)
{
	ifstream in(strFilename, ifstream::in);
	if (in.is_open())
	{
		S3Obj obj;
		while (std::getline(in, obj.key, '\n') && in >> obj.size && in >> obj.time && in >> obj.tag)
		{
			in.ignore(100000, '\n');
			list.insert(obj);
		}
	}
	else
	{
		return false;
	}
	return true;
}

bool readS3Objects(const string& strFilename, S3ObjVect& list)
{
	ifstream in(strFilename, ifstream::in);
	if (in.is_open())
	{
		S3Obj obj;
		while (std::getline(in, obj.key, '\n') && in >> obj.size && in >> obj.time && in >> obj.tag)
		{
			in.ignore(100000, '\n');
			list.push_back(obj);
		}
	}
	else
	{
		return false;
	}
	return true;
}

void testReadS3Cache(const string& strFilename)
{
	S3ObjVect list;
	ifstream in(strFilename, ifstream::in);
	if (in.is_open())
	{
		S3Obj obj;
		while (std::getline(in, obj.key, '\n') && in >> obj.size && in >> obj.time && in >> obj.tag)
		{
			in.ignore(100000, '\n');
			list.push_back(obj);
		}

		cout << "last key:" << list.back().key << endl;
	}
}
void test1()
{
	S3ObjSet s3Set;
	string strBucket = "diabloforum";
	if (listObjects(strBucket, s3Set))
	{
		//cout << "successfully get "<< s3Vect.size() << " objects of bucket of " << strBucket << endl;
		printS3ObjSet(s3Set);
	}
}

void test2()
{
	if (getObject("diabloforum", "rules.txt", "nick-test.txt"))
	{
		cout << " successful " << endl;
	}
}

void put_test()
{
	if (putObject("huangqingzhe", "index.htm", "/home/nick/Downloads/diabloforum/public_html/index.htm"))
	{
		cout << " successful" << endl;
	}
}

void test4()
{
	S3ObjSet s3List;
	if (getAllFiles("/home/nick/Downloads/diabloforum/public_html", s3List))
	{
		cout << " successful " << endl;
		printS3ObjSet(s3List);
	}
}

void sync(const string& strBucket, const string& strRootPathIn, bool bForce=false);

void sync(const string& strBucket, const string& strRootPathIn, bool bForce)
{
	const string strCacheFile = "/BigDisk/diabloforum/s3.cache";
	S3ObjSet localList, remoteList;
	string strRootPath = strRootPathIn + "/";
	if (!getAllFiles(strRootPath, localList))
	{
		cout << " getAllFiles failed " << endl;
		return;
	}
	if (!readS3Objects(strCacheFile, remoteList))
	{
		if (!listObjects(strBucket, remoteList))
		{
			cout << " listObjects failed " << endl;
			return;
		}
	}
	//cout << "*****************************locallist*************************" << endl;
	//printS3ObjSet(localList);
	//cout << "**************************remoteList*****************************" << endl;
	//printS3ObjSet(remoteList);

	for (S3ObjSet::const_iterator localIt = localList.begin(); localIt != localList.end(); localIt ++)
	{
		SyncDirectionEnum direction = DirectionNone;
		S3ObjSet::const_iterator remoteIt = remoteList.find(*localIt);
		if (remoteIt != remoteList.end())
		{
			// found it
			if (bForce || remoteIt->size != localIt->size || (remoteIt->tag.size() != 0 && remoteIt->tag.compare(localIt->tag) != 0))
			{
				direction = DirectionUpload;
			}
		}
		else
		{
			cout << "DirectionUpload:" << localIt->key << endl << localIt->size << localIt->time << endl;
			direction = DirectionUpload; // upload
		}
		string strPath = strRootPath + localIt->key;
		string strPersonal = strRootPath + "personal";
		S3CannedAcl acl = S3CannedAclPublicRead;
		switch (direction)
		{
		case DirectionUpload:
			if (strPath.compare(0, strPersonal.size(), strPersonal) == 0)
			{
				acl = S3CannedAclPrivate;
			}
			cout << "upload:" << strPath << " ; " << localIt->key << endl;
			if (!putObject(strBucket, localIt->key, strPath, acl))
			{
				cout << "upload error: bucket: " << strBucket << " key:" << strPath << " path:" << localIt->key << endl;
			}
			// update remote list
			remoteList.erase(*localIt);
			remoteList.insert(*localIt);

			break;
		default:
			break;
		}
	}
	if (writeS3Objects(strCacheFile, remoteList))
	{
		cout << "save cache successfully" << endl;
	}
	else
	{
		cout << "saving cache failed" << endl;
	}
/*
	for (S3ObjSet::const_iterator remoteIt = remoteList.begin(); remoteIt != remoteList.end(); remoteIt ++)
	{
		SyncDirectionEnum direction = DirectionNone; // nothing
		if (remoteIt->key[0] == '.')
		{
			continue;
		}

		S3ObjSet::const_iterator localIt = localList.find(*remoteIt);
		if (localIt == localList.end())
		{
			cout << "find downloadable item..." << remoteIt->key.c_str() << endl;
			direction = DirectionDownload;
		}
		string strPath = strRootPath + remoteIt->key;

		switch (direction)
		{
		case DirectionDownload:
			if (ensureParentDirectory(strPath))
			{
				cout << "download:" << localIt->key << "; " << strPath << endl;
				if (!getObject(strBucket, localIt->key, strPath))
				{
					cout << "download error: bucket: " << strBucket << " key:" << strPath << " path:" << localIt->key << endl;
				}
			}
			fprintf(stdout, "strPath=%s***********************\n",  strPath.c_str());
			break;
		default:

			break;
		}
	}
	*/
}


bool compareCache(const string& strRootPathIn, const string& strCacheFile)
{
	S3ObjSet localList, remoteList;
	string strRootPath = strRootPathIn + "/";
	if (!readS3Objects(strCacheFile, remoteList))
	{
		cout << "read remote cache failed" << endl;
		return false;
	}
	cout << "read s3cache:" << remoteList.size() << endl;
	if (!getAllFiles(strRootPath, localList))
	{
		cout << " getAllFiles failed " << endl;
		return false;
	}

	ofstream remoteOut("/BigDisk/diabloforum/remoteS3.cache", ofstream::out|ofstream::trunc);

	for (S3ObjSet::const_iterator localIt = localList.begin(); localIt != localList.end(); localIt ++)
	{
		bool bRemoteOut = false;
		S3ObjSet::const_iterator remoteIt = remoteList.find(*localIt);
		if (remoteIt != remoteList.end())
		{
			// found it
			if (remoteIt->size != localIt->size || (!remoteIt->tag.empty() && remoteIt->tag != localIt->tag))
			{
				bRemoteOut = true;
			}
		}
		else
		{
			bRemoteOut = true;
		}

		if (bRemoteOut)
		{
			cout << "remote missing or stale:" << localIt->key << endl << localIt->size <<endl << localIt->tag << endl;
			remoteOut << localIt->key << endl << localIt->size << endl << localIt->time <<endl<< localIt->tag << endl;
		}
	}
	ofstream localOut("/BigDisk/diabloforum/localS3.cache", ofstream::out|ofstream::trunc);
	for (S3ObjSet::const_iterator remoteIt = remoteList.begin(); remoteIt != remoteList.end(); remoteIt ++)
	{
		bool bLocalOut = false;
		S3ObjSet::const_iterator localIt = localList.find(*remoteIt);
		if (localIt != localList.end())
		{
			if (localIt->size != remoteIt->size || (!remoteIt->tag.empty() && localIt->tag != remoteIt->tag))
			{
				bLocalOut = true;
			}
		}
		else
		{
			bLocalOut = true;
		}
		if (bLocalOut)
		{
			cout << "local file missing:" << remoteIt->key << remoteIt->size << endl << remoteIt->tag << endl;
			localOut << remoteIt->key << endl << remoteIt->size << endl << remoteIt->time <<endl<< remoteIt->tag << endl;
		}
	}
	return true;
}

bool cleanRemote(const string& strRootPathIn, const string& strCacheFile)
{
	S3ObjSet localList, remoteList;
	string strRootPath = strRootPathIn + "/";
	if (!readS3Objects(strCacheFile, remoteList))
	{
		cout << "read remote cache failed" << endl;
		return false;
	}
	cout << "read s3cache:" << remoteList.size() << endl;
	if (!getAllFiles(strRootPath, localList))
	{
		cout << " getAllFiles failed " << endl;
		return false;
	}

	ofstream localOut("/BigDisk/diabloforum/localS3.cache", ofstream::out|ofstream::trunc);
	for (S3ObjSet::const_iterator remoteIt = remoteList.begin(); remoteIt != remoteList.end(); remoteIt ++)
	{
		S3ObjSet::const_iterator localIt = localList.find(*remoteIt);
		if (localIt == localList.end())
		{
			cout << "local file missing:" << remoteIt->key <<endl<< remoteIt->size << endl << remoteIt->tag << endl;
			localOut << remoteIt->key << endl << remoteIt->size << endl << remoteIt->time <<endl<< remoteIt->tag << endl;
		}
	}
	return true;
}

bool deleteByCache(const string& strBucket, const string& strCachefile)
{
	S3ObjVect vect;
	if (!readS3Objects(strCachefile, vect))
	{
		cout << "read remote cache failed" << endl;
		return false;
	}
	cout << "read s3cache:" << vect.size() << endl;
	for (size_t i = 0; i < vect.size(); i ++)
	{
		const S3Obj& obj = vect[i];
		if (!deleteObject(strBucket, obj.key))
		{
			return false;
		}
	}
	return true;

}
bool downloadCache(const string& strBucket, const string& strCacheFile)
{
	S3ObjSet remoteList;
	if (listObjects(strBucket, remoteList))
	{
		cout << "listObject successfully"<< endl;
	}
	else
	{
		return false;
	}
	if (writeS3Objects(strCacheFile, remoteList))
	{
		cout << "save cache successfully" << endl;
		return true;
	}
	return false;
}


void upload(const string& strBucket, const string& strInputRootPath)
{
	S3ObjSet localList;
	string strRootPath = strInputRootPath;
	if (strRootPath.size() > 0 && strRootPath[strRootPath.size()-1] != '/')
	{
		strRootPath.push_back('/');
	}
	if (!getAllFiles(strRootPath, localList))
	{
		cout << " getAllFiles failed " << endl;
		return;
	}

	for (S3ObjSet::const_iterator localIt = localList.begin(); localIt != localList.end(); localIt ++)
	{
		string strPath = strRootPath + localIt->key;
		string strPersonal = strRootPath + "personal";
		S3CannedAcl acl = S3CannedAclPublicRead;

		if (strPath.compare(0, strPersonal.size(), strPersonal) == 0)
		{
			acl = S3CannedAclPrivate;
		}
		cout << "upload:" << strPath << " ; " << localIt->key << endl;
		if (!putObject(strBucket, localIt->key, strPath, acl))
		{
			cout << "upload error: bucket: " << strBucket << " key:" << strPath << " path:" << localIt->key << endl;
		}
	}
}

int main1()
{
	const string& strBucket = "www.staroceans.com";
	const string& strRootPath = "/home/nick/Downloads/diabloforum/public_html/";
	upload(strBucket, strRootPath);
	//ensureParentDirectry("/home/nick/dir/./tmp//tmp//tm././//../dir/d");

	return 0;

}

extern void myListTool(const string& srcPath);

void doRetrieveContentType(const string& strKey, string& strContentType)
{
	S3ResponseHandler response
	{
		&MyS3ResponseRetrieveContentTypeCallback,
		&MyS3ResponseCompleteCallbackQuiet
	};
	static S3BucketContext ctx;
	static MyS3Context myS3;
	bool bInitialized = false;
	const string strBucket = "www.staroceans.org";

	if (!bInitialized && !myS3.initialize(strBucket.c_str(), ctx))
	{
		cout << "initialize failed" << endl;
		return;
	}
	else
	{
		bInitialized = true;
	}
	S3_head_object(&ctx, strKey.c_str(), NULL, &response, &strContentType);
}

void check_content_type(int nMax)
{
	const char* pszBucketName = "www.staroceans.org";
	const char* pszRootPath = "/BigDisk/diabloforum/public_html/";
	ofstream out("/tmp/output.log");
	int counter = 0;
	S3ObjSet localList, remoteList;
	string strRootPath = realpath(pszRootPath, NULL);
	strRootPath += "/";
	getAllFiles(strRootPath, localList, nMax);

	cout << " getAllFiles finished with " << localList.size() <<  endl;
	if (!out.is_open())
	{
		cout << "cannot open output file." << endl;
		return;
	}
	for (S3ObjSet::const_iterator localIt = localList.begin(); localIt != localList.end(); localIt ++)
	{
		string strRemoteContentType;
		doRetrieveContentType(localIt->key, strRemoteContentType);
		string strLocalContentType;
		string strPath = strRootPath + localIt->key;

		strLocalContentType = getFileContentType(strPath);
		if (strRemoteContentType != strLocalContentType)
		{
			out << "###############################" <<endl;
			out << "Different!" << strPath << endl;
			out << "remote:" << strRemoteContentType <<endl
					<< "local:" << strLocalContentType <<endl;
			out << "###############################" <<endl;
			counter ++;

			S3CannedAcl acl = S3CannedAclPublicRead;
			string strPersonal = strRootPath + "personal";
			if (strPath.compare(0, strPersonal.size(), strPersonal) == 0)
			{
				acl = S3CannedAclPrivate;
			}
			cout << "upload:" << strPath << " ; " << localIt->key << endl;
			if (!putObject(pszBucketName, localIt->key, strPath, acl))
			{
				cout << "upload error: bucket: " << pszBucketName << " key:" << strPath << " path:"
						<< localIt->key << endl;
			}
		}
		else
		{
			cout << ".";
			cout.flush();
			//cout << "*****************identical!" << strPath <<"**************"<< endl;
		}

//		string strPath = strRootPath + localIt->key;
//		string strPersonal = strRootPath + "personal";
//		S3CannedAcl acl = S3CannedAclPublicRead;
//
//		if (strPath.compare(0, strPersonal.size(), strPersonal) == 0)
//		{
//			acl = S3CannedAclPrivate;
//		}
//		cout << "upload:" << strPath << " ; " << localIt->key << endl;
//		if (!putObject(pszBucketName, localIt->key, strPath, acl))
//		{
//			cout << "upload error: bucket: " << pszBucketName << " key:" << strPath << " path:" << localIt->key << endl;
//		}

	}
	out << "total:" << counter << endl;
}


int main(int argc, char**argv)
{
	const char* pszBucketName = "www.staroceans.org";
	const char* pszRootPath = "/BigDisk/diabloforum/public_html/";

	if (argc == 1)
	{
		myListTool(pszRootPath);
		bool bForce = false;
		char* ptr = getenv("FORCE_UPLOAD");
		if (ptr && string(ptr).compare("TRUE") == 0)
		{
			bForce = true;
		}
		sync(pszBucketName, realpath(pszRootPath, NULL), bForce);
	}
	else
	{
		if (argc == 2)
		{
			char* pszRealPath = realpath(argv[1], NULL);
			if (pszRealPath)
			{
				char* pszFileName = basename(pszRealPath);

				putObject(pszBucketName, pszFileName, pszRealPath);
			}
			else
			{
				cout << "invalid path:" << argv[1] << endl;
			}
		}
		else
		{
			if (string(argv[1]).compare("check") == 0)
			{
				cout << "check content type" << endl;
				int nMax = atoi(argv[2]);
				check_content_type(nMax);
			}
			else if (string(argv[1]).compare("downloadCache") == 0)
			{
				//"/BigDisk/diabloforum/s3.cache"
				downloadCache(pszBucketName, argv[2]);
			}
			else if (string(argv[1]).compare("compareCache") == 0)
			{
				//"/BigDisk/diabloforum/s3.cache"
				compareCache(realpath(pszRootPath, NULL), argv[2]);
			}
			else if (string(argv[1]).compare("testReadCache") == 0)
			{
				testReadS3Cache(argv[2]);
			}
			else if (string(argv[1]).compare("cleanRemote") == 0)
			{
				cleanRemote(realpath(pszRootPath, NULL),argv[2]);
			}
			else if (string(argv[1]).compare("deleteByCache") == 0)
			{
				deleteByCache(pszBucketName,argv[2]);
			}

			//cout << "usage:" << argv[0] << "[filetoupload]" << endl;
		}
	}
	//test1();
	//put_test();
	//upload("huangqingzhe", "/home/nick/Downloads/diabloforum/public_html/");

	return 0;
}



int main_simplet_test(int argc, char** argv)
{
	S3ObjSet s3Obj;
	const std::string strBucket = "www.staroceans.org";
	const std::string strObject = "staroceans2013-";
	//if (deleteObject(strBucket, strObject))
	{
		if (listObjects(strBucket, strObject, s3Obj))
		{
			cout << " succeed!" << endl;
			printS3ObjSet(s3Obj);
		}
	}
	return 0;
}

int main_test(int argc, char** argv)
{
	const char* pszPath = "/home/nick/diabloforum";
	char*realPtr = realpath(pszPath, NULL);
	printf("%s\n", realPtr);
	printf("%s\n", dirname(realPtr));
	printf("%s\n", basename(realPtr));
	return 0;
}

int main_list_prefix(int argc, char** argv)
{
	const std::string strBucket = "www.staroceans.org";
	const std::string strPrefix = "staroceans2012-";
	S3ObjSet s3Set;

	if (listObjects(strBucket, strPrefix, s3Set))
	{
		printS3ObjSet(s3Set);
	}

	return 0;
}
