#include <Filesystem.h>
#include <algorithm>
#include <sys/stat.h>
#include <sys/types.h>
#include <fstream>
#ifdef _WIN32
#include <io.h>
#include <direct.h>
#include <Windows.h>
#else
#include <unistd.h>
#include <dirent.h>
#endif

// 路径分隔符(Linux:‘/’,Windows:’\\’)
#ifdef _WIN32
#define PATH_SEPARATOR '\\'
#else
#define PATH_SEPARATOR '/'
#endif

using namespace std;

namespace migraphxSamples
{

	static std::vector<std::string> SplitString(std::string str, std::string separator)
	{
		std::string::size_type pos;
		std::vector<std::string> result;
		str += separator; // 扩展字符串以方便操作
		int size = str.size();

		for (int i = 0; i < size; i++)
		{
			pos = str.find(separator, i);
			if (pos < size)
			{
				std::string s = str.substr(i, pos - i);
				result.push_back(s);
				i = pos + separator.size() - 1;
			}
		}
		return result;
	}

#if defined _WIN32 || defined WINCE
	const char dir_separators[] = "/\\";

	struct dirent
	{
		const char *d_name;
	};

	struct DIR
	{
#ifdef WINRT
		WIN32_FIND_DATAW data;
#else
		WIN32_FIND_DATAA data;
#endif
		HANDLE handle;
		dirent ent;
#ifdef WINRT
		DIR() {}
		~DIR()
		{
			if (ent.d_name)
				delete[] ent.d_name;
		}
#endif
	};

	DIR *opendir(const char *path)
	{
		DIR *dir = new DIR;
		dir->ent.d_name = 0;
#ifdef WINRT
		string full_path = string(path) + "\\*";
		wchar_t wfull_path[MAX_PATH];
		size_t copied = mbstowcs(wfull_path, full_path.c_str(), MAX_PATH);
		CV_Assert((copied != MAX_PATH) && (copied != (size_t)-1));
		dir->handle = ::FindFirstFileExW(wfull_path, FindExInfoStandard,
										 &dir->data, FindExSearchNameMatch, NULL, 0);
#else
		dir->handle = ::FindFirstFileExA((string(path) + "\\*").c_str(),
										 FindExInfoStandard, &dir->data, FindExSearchNameMatch, NULL, 0);
#endif
		if (dir->handle == INVALID_HANDLE_VALUE)
		{
			/*closedir will do all cleanup*/
			delete dir;
			return 0;
		}
		return dir;
	}

	dirent *readdir(DIR *dir)
	{
#ifdef WINRT
		if (dir->ent.d_name != 0)
		{
			if (::FindNextFileW(dir->handle, &dir->data) != TRUE)
				return 0;
		}
		size_t asize = wcstombs(NULL, dir->data.cFileName, 0);
		CV_Assert((asize != 0) && (asize != (size_t)-1));
		char *aname = new char[asize + 1];
		aname[asize] = 0;
		wcstombs(aname, dir->data.cFileName, asize);
		dir->ent.d_name = aname;
#else
		if (dir->ent.d_name != 0)
		{
			if (::FindNextFileA(dir->handle, &dir->data) != TRUE)
				return 0;
		}
		dir->ent.d_name = dir->data.cFileName;
#endif
		return &dir->ent;
	}

	void closedir(DIR *dir)
	{
		::FindClose(dir->handle);
		delete dir;
	}
#else
#include <dirent.h>
#include <sys/stat.h>
	const char dir_separators[] = "/";
#endif

	static bool isDir(const string &path, DIR *dir)
	{
#if defined _WIN32 || defined WINCE
		DWORD attributes;
		BOOL status = TRUE;
		if (dir)
			attributes = dir->data.dwFileAttributes;
		else
		{
			WIN32_FILE_ATTRIBUTE_DATA all_attrs;
#ifdef WINRT
			wchar_t wpath[MAX_PATH];
			size_t copied = mbstowcs(wpath, path.c_str(), MAX_PATH);
			CV_Assert((copied != MAX_PATH) && (copied != (size_t)-1));
			status = ::GetFileAttributesExW(wpath, GetFileExInfoStandard, &all_attrs);
#else
			status = ::GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &all_attrs);
#endif
			attributes = all_attrs.dwFileAttributes;
		}

		return status && ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
#else
		(void)dir;
		struct stat stat_buf;
		if (0 != stat(path.c_str(), &stat_buf))
			return false;
		int is_dir = S_ISDIR(stat_buf.st_mode);
		return is_dir != 0;
#endif
	}

	bool IsDirectory(const string &path)
	{
		return isDir(path, NULL);
	}

	bool Exists(const string &path)
	{

#if defined _WIN32 || defined WINCE
		BOOL status = TRUE;
		{
			WIN32_FILE_ATTRIBUTE_DATA all_attrs;
#ifdef WINRT
			wchar_t wpath[MAX_PATH];
			size_t copied = mbstowcs(wpath, path.c_str(), MAX_PATH);
			CV_Assert((copied != MAX_PATH) && (copied != (size_t)-1));
			status = ::GetFileAttributesExW(wpath, GetFileExInfoStandard, &all_attrs);
#else
			status = ::GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &all_attrs);
#endif
		}

		return !!status;
#else
		struct stat stat_buf;
		return (0 == stat(path.c_str(), &stat_buf));
#endif
	}

	bool IsPathSeparator(char c)
	{
		return c == '/' || c == '\\';
	}

	string JoinPath(const string &base, const string &path)
	{
		if (base.empty())
			return path;
		if (path.empty())
			return base;

		bool baseSep = IsPathSeparator(base[base.size() - 1]);
		bool pathSep = IsPathSeparator(path[0]);
		string result;
		if (baseSep && pathSep)
		{
			result = base + path.substr(1);
		}
		else if (!baseSep && !pathSep)
		{
			result = base + PATH_SEPARATOR + path;
		}
		else
		{
			result = base + path;
		}
		return result;
	}

	static bool wildcmp(const char *string, const char *wild)
	{
		const char *cp = 0, *mp = 0;

		while ((*string) && (*wild != '*'))
		{
			if ((*wild != *string) && (*wild != '?'))
			{
				return false;
			}

			wild++;
			string++;
		}

		while (*string)
		{
			if (*wild == '*')
			{
				if (!*++wild)
				{
					return true;
				}

				mp = wild;
				cp = string + 1;
			}
			else if ((*wild == *string) || (*wild == '?'))
			{
				wild++;
				string++;
			}
			else
			{
				wild = mp;
				string = cp++;
			}
		}

		while (*wild == '*')
		{
			wild++;
		}

		return *wild == 0;
	}

	static void glob_rec(const string &directory, const string &wildchart, std::vector<string> &result,
						 bool recursive, bool includeDirectories, const string &pathPrefix)
	{
		DIR *dir;

		if ((dir = opendir(directory.c_str())) != 0)
		{
			/* find all the files and directories within directory */
			try
			{
				struct dirent *ent;
				while ((ent = readdir(dir)) != 0)
				{
					const char *name = ent->d_name;
					if ((name[0] == 0) || (name[0] == '.' && name[1] == 0) || (name[0] == '.' && name[1] == '.' && name[2] == 0))
						continue;

					string path = JoinPath(directory, name);
					string entry = JoinPath(pathPrefix, name);

					if (isDir(path, dir))
					{
						if (recursive)
							glob_rec(path, wildchart, result, recursive, includeDirectories, entry);
						if (!includeDirectories)
							continue;
					}

					if (wildchart.empty() || wildcmp(name, wildchart.c_str()))
						result.push_back(entry);
				}
			}
			catch (...)
			{
				closedir(dir);
				throw;
			}
			closedir(dir);
		}
		else
		{
			printf("could not open directory: %s", directory.c_str());
		}
	}

	void GetFileNameList(const string &directory, const string &pattern, std::vector<string> &result, bool recursive, bool addPath)
	{
		// split pattern
		vector<string> patterns = SplitString(pattern, ",");

		result.clear();

		for (int i = 0; i < patterns.size(); ++i)
		{
			string eachPattern = patterns[i];
			std::vector<string> eachResult;
			glob_rec(directory, eachPattern, eachResult, recursive, true, directory);
			for (int j = 0; j < eachResult.size(); ++j)
			{
				if (IsDirectory(eachResult[j]))
					continue;
				if (addPath)
				{
					result.push_back(eachResult[j]);
				}
				else
				{
					result.push_back(GetFileName(eachResult[j]));
				}
			}
		}
		std::sort(result.begin(), result.end());
	}

	void GetFileNameList2(const string &directory, const string &pattern, std::vector<string> &result, bool recursive, bool addPath)
	{
		// split pattern
		vector<string> patterns = SplitString(pattern, ",");

		result.clear();

		for (int i = 0; i < patterns.size(); ++i)
		{
			string eachPattern = patterns[i];
			std::vector<string> eachResult;
			glob_rec(directory, eachPattern, eachResult, recursive, true, directory);
			for (int j = 0; j < eachResult.size(); ++j)
			{
				string filePath = eachResult[j];
				if (IsDirectory(filePath))
				{
					filePath = filePath + "/";
					for (int k = 0; k < filePath.size(); ++k)
					{
						if (IsPathSeparator(filePath[k]))
						{
							filePath[k] = '/';
						}
					}
				}
				if (addPath)
				{
					result.push_back(filePath);
				}
				else
				{
					if (!IsDirectory(filePath))
					{
						result.push_back(GetFileName(filePath));
					}
				}
			}
		}
		std::sort(result.begin(), result.end());
	}

	void RemoveAll(const string &path)
	{

		if (!Exists(path))
			return;

		if (IsDirectory(path))
		{
			std::vector<string> entries;
			GetFileNameList2(path, string(), entries, false, true);
			for (size_t i = 0; i < entries.size(); i++)
			{
				const string &e = entries[i];
				RemoveAll(e);
			}
#ifdef _MSC_VER
			bool result = _rmdir(path.c_str()) == 0;
#else
			bool result = rmdir(path.c_str()) == 0;
#endif
			if (!result)
			{
				printf("can't remove directory: %s\n", path.c_str());
			}
		}
		else
		{
#ifdef _MSC_VER
			bool result = _unlink(path.c_str()) == 0;
#else
			bool result = unlink(path.c_str()) == 0;
#endif
			if (!result)
			{
				printf("can't remove file: %s\n", path.c_str());
			}
		}
	}

	void Remove(const string &directory, const string &extension)
	{

		DIR *dir;

		static int numberOfFiles = 0;

		if ((dir = opendir(directory.c_str())) != 0)
		{
			/* find all the files and directories within directory */
			try
			{
				struct dirent *ent;
				while ((ent = readdir(dir)) != 0)
				{
					const char *name = ent->d_name;
					if ((name[0] == 0) || (name[0] == '.' && name[1] == 0) || (name[0] == '.' && name[1] == '.' && name[2] == 0))
						continue;

					string path = JoinPath(directory, name);

					if (isDir(path, dir))
					{
						Remove(path, extension);
					}

					// �ж���չ��
					if (extension.empty() || wildcmp(name, extension.c_str()))
					{
						RemoveAll(path);
						++numberOfFiles;
						printf("%s deleted! number of deleted files:%d\n", path.c_str(), numberOfFiles);
					}
				}
			}
			catch (...)
			{
				closedir(dir);
				throw;
			}
			closedir(dir);
		}
		else
		{
			printf("could not open directory: %s", directory.c_str());
		}

		// ����RemoveAllɾ��Ŀ¼
		RemoveAll(directory);
	}
	string GetFileName(const string &path)
	{
		string fileName;
		int indexOfPathSeparator = -1;
		for (int i = path.size() - 1; i >= 0; --i)
		{
			if (IsPathSeparator(path[i]))
			{
				fileName = path.substr(i + 1, path.size() - i - 1);
				indexOfPathSeparator = i;
				break;
			}
		}
		if (indexOfPathSeparator == -1)
		{
			fileName = path;
		}

		return fileName;
	}
	string GetFileName_NoExtension(const string &path)
	{
		string fileName = GetFileName(path);
		string fileName_NoExtension;
		for (int i = fileName.size() - 1; i > 0; --i)
		{
			if (fileName[i] == '.')
			{
				fileName_NoExtension = fileName.substr(0, i);
				break;
			}
		}

		return fileName_NoExtension;
	}

	string GetExtension(const string &path)
	{
		string fileName;
		for (int i = path.size() - 1; i >= 0; --i)
		{
			if (path[i] == '.')
			{
				fileName = path.substr(i, path.size() - i);
				break;
			}
		}

		return fileName;
	}

	string GetParentPath(const string &path)
	{
		string fileName;
		for (int i = path.size() - 1; i >= 0; --i)
		{
			if (IsPathSeparator(path[i]))
			{
				fileName = path.substr(0, i + 1);
				break;
			}
		}

		return fileName;
	}

	static bool CreateDirectory(const string &path)
	{
#if defined WIN32 || defined _WIN32 || defined WINCE
#ifdef WINRT
		wchar_t wpath[MAX_PATH];
		size_t copied = mbstowcs(wpath, path.c_str(), MAX_PATH);
		CV_Assert((copied != MAX_PATH) && (copied != (size_t)-1));
		int result = CreateDirectoryA(wpath, NULL) ? 0 : -1;
#else
		int result = _mkdir(path.c_str());
#endif
#elif defined __linux__ || defined __APPLE__
		int result = mkdir(path.c_str(), 0777);
#else
		int result = -1;
#endif

		if (result == -1)
		{
			return IsDirectory(path);
		}
		return true;
	}

	bool CreateDirectories(const string &directoryPath)
	{
		string path = directoryPath;

		for (;;)
		{
			char last_char = path.empty() ? 0 : path[path.length() - 1];
			if (IsPathSeparator(last_char))
			{
				path = path.substr(0, path.length() - 1);
				continue;
			}
			break;
		}

		if (path.empty() || path == "./" || path == ".\\" || path == ".")
			return true;
		if (IsDirectory(path))
			return true;

		size_t pos = path.rfind('/');
		if (pos == string::npos)
			pos = path.rfind('\\');
		if (pos != string::npos)
		{
			string parent_directory = path.substr(0, pos);
			if (!parent_directory.empty())
			{
				if (!CreateDirectories(parent_directory))
					return false;
			}
		}

		return CreateDirectory(path);
	}

	bool CopyFile(const string srcPath, const string dstPath)
	{
		std::ifstream srcFile(srcPath, ios::binary);
		std::ofstream dstFile(dstPath, ios::binary);

		if (!srcFile.is_open())
		{
			printf("can not open %s\n", srcPath.c_str());
			return false;
		}
		if (!dstFile.is_open())
		{
			printf("can not open %s\n", dstPath.c_str());
			return false;
		}
		if (srcPath == dstPath)
		{
			printf("src can not be same with dst\n");
			return false;
		}
		char buffer[2048];
		unsigned int numberOfBytes = 0;
		while (srcFile)
		{
			srcFile.read(buffer, 2048);
			dstFile.write(buffer, srcFile.gcount());
			numberOfBytes += srcFile.gcount();
		}
		srcFile.close();
		dstFile.close();
		return true;
	}

	bool CopyDirectories(string srcPath, const string dstPath)
	{
		if (srcPath == dstPath)
		{
			printf("src can not be same with dst\n");
			return false;
		}

		// ȥ������·���ָ���
		srcPath = srcPath.substr(0, srcPath.size() - 1);

		vector<string> fileNameList;
		GetFileNameList2(srcPath, "", fileNameList, true, true);

		string parentPathOfSrc = GetParentPath(srcPath);
		int length = parentPathOfSrc.size();

		// create all directories
		for (int i = 0; i < fileNameList.size(); ++i)
		{
			// create directory
			string srcFilePath = fileNameList[i];
			string subStr = srcFilePath.substr(length, srcFilePath.size() - length);
			string dstFilePath = dstPath + subStr;
			string parentPathOfDst = GetParentPath(dstFilePath);
			CreateDirectories(parentPathOfDst);
		}

		// copy file
		for (int i = 0; i < fileNameList.size(); ++i)
		{
			string srcFilePath = fileNameList[i];
			if (IsDirectory(srcFilePath))
			{
				continue;
			}
			string subStr = srcFilePath.substr(length, srcFilePath.size() - length);
			string dstFilePath = dstPath + subStr;

			// copy file
			CopyFile(srcFilePath, dstFilePath);

			// process
			double process = (1.0 * (i + 1) / fileNameList.size()) * 100;
			printf("%s done! %f% \n", GetFileName(fileNameList[i]).c_str(), process);
		}
		printf("all done!(the number of files:%d)\n", fileNameList.size());

		return true;
	}

}
