/*!
	@file
	@author		Albert Semenov
	@date		09/2009
	@module
*/
#ifndef FILE_SYSTEM_INFO_H_
#define FILE_SYSTEM_INFO_H_

#include <MyGUI.h>
#if MYGUI_PLATFORM == MYGUI_PLATFORM_WIN32
	#include <windows.h>
	#include <io.h>
#else
	#include <unistd.h>
	#include <dirent.h>
	#include <sys/types.h>
	#include <sys/stat.h>
	#include <fnmatch.h>
#endif

#include <string>
#include <vector>
#include <locale>

namespace common
{

	struct FileInfo
	{
		FileInfo(const std::wstring& _name, bool _folder) :
			name(_name),
			folder(_folder)
		{
		}
		std::wstring name;
		bool folder;
	};
	using VectorFileInfo = std::vector<FileInfo>;

	inline std::wstring toLower(const std::wstring& _input)
	{
		std::wstring result;
		result.resize(_input.size());
		static std::locale sLocale("");
		for (unsigned int i = 0; i < _input.size(); ++i)
			result[i] = std::tolower(_input[i], sLocale);
		return result;
	}

	inline bool sortFiles(const common::FileInfo& left, const common::FileInfo& right)
	{
		if (left.folder < right.folder)
			return true;
		if (left.folder > right.folder)
			return false;

		return toLower(left.name) < toLower(right.name);
	}

	inline bool isAbsolutePath(const wchar_t* path)
	{
#if MYGUI_PLATFORM == MYGUI_PLATFORM_WIN32
		if (IsCharAlphaW(path[0]) && path[1] == ':')
			return true;
#endif
		return path[0] == '/' || path[0] == '\\';
	}

	inline bool endWith(const std::wstring& _source, const std::wstring& _value)
	{
		size_t count = _value.size();
		if (_source.size() < count)
			return false;
		size_t offset = _source.size() - count;
		for (size_t index = 0; index < count; ++index)
		{
			if (_source[index + offset] != _value[index])
				return false;
		}
		return true;
	}

	inline std::wstring concatenatePath(const std::wstring& _base, const std::wstring& _name)
	{
		if (_base.empty() || isAbsolutePath(_name.c_str()))
		{
			return _name;
		}

		if (endWith(_base, L"\\") || endWith(_base, L"/"))
			return _base + _name;

#if MYGUI_PLATFORM == MYGUI_PLATFORM_WIN32
		return _base + L'\\' + _name;
#else
		return _base + L'/' + _name;
#endif
	}

	inline bool isReservedDir(const wchar_t* _fn)
	{
		// if "."
		return (_fn[0] == '.' && _fn[1] == 0);
	}

	inline bool isParentDir(const wchar_t* _fn)
	{
		// if ".."
		return (_fn[0] == '.' && _fn[1] == '.' && _fn[2] == 0);
	}

	inline void getSystemFileList(
		VectorFileInfo& _result,
		const std::wstring& _folder,
		const std::wstring& _mask,
		bool _sorted = true)
	{
#if MYGUI_PLATFORM == MYGUI_PLATFORM_WIN32
		//FIXME add optional parameter?
		bool ms_IgnoreHidden = true;

		intptr_t lHandle;
		int res;
		struct _wfinddata_t tagData;

		// pattern can contain a directory name, separate it from mask
		size_t pos = _mask.find_last_of(L"/\\");
		std::wstring directory;
		if (pos != _mask.npos)
			directory = _mask.substr(0, pos);

		std::wstring full_mask = concatenatePath(_folder, _mask);

		lHandle = _wfindfirst(full_mask.c_str(), &tagData);
		res = 0;
		while (lHandle != -1 && res != -1)
		{
			if ((!ms_IgnoreHidden || (tagData.attrib & _A_HIDDEN) == 0) && !isReservedDir(tagData.name))
			{
				_result.push_back(
					FileInfo(concatenatePath(directory, tagData.name), (tagData.attrib & _A_SUBDIR) != 0));
			}
			res = _wfindnext(lHandle, &tagData);
		}
		// Close if we found any files
		if (lHandle != -1)
			_findclose(lHandle);
#else
		std::string folder = MyGUI::UString(_folder).asUTF8();
		DIR* dir = opendir(folder.c_str());
		struct dirent* dp;

		if (dir == nullptr)
		{
			/* opendir() failed */
			MYGUI_LOG(Error, "Can't open " + folder);
			return;
		}

		rewinddir(dir);

		while ((dp = readdir(dir)) != nullptr)
		{
			if ((fnmatch(MyGUI::UString(_mask).asUTF8_c_str(), dp->d_name, 0) == 0) &&
				!isReservedDir(MyGUI::UString(dp->d_name).asWStr_c_str()))
			{
				struct stat fInfo;
				std::string path = folder + "/" + dp->d_name;
				if (stat(path.c_str(), &fInfo) == -1)
					perror("stat");
				_result.emplace_back(MyGUI::UString(dp->d_name).asWStr_c_str(), (S_ISDIR(fInfo.st_mode)));
			}
		}

		closedir(dir);
#endif
		if (_sorted)
		{
			std::sort(_result.begin(), _result.end(), sortFiles);
		}
	}

	inline std::wstring getSystemCurrentFolder()
	{
#if MYGUI_PLATFORM == MYGUI_PLATFORM_WIN32
		wchar_t buff[MAX_PATH + 1];
		::GetCurrentDirectoryW(MAX_PATH, buff);
		return buff;
#else
	#ifndef PATH_MAX
		#define PATH_MAX 256
	#endif
		char buff[PATH_MAX + 1];
		return getcwd(buff, PATH_MAX) ? MyGUI::UString(buff).asWStr_c_str() : std::wstring();
#endif
	}

	using VectorWString = std::vector<std::wstring>;
	inline void scanFolder(
		VectorWString& _result,
		const std::wstring& _folder,
		bool _recursive,
		const std::wstring& _mask,
		bool _fullpath)
	{
		std::wstring folder = _folder;
		if (!folder.empty() && *folder.rbegin() != '/' && *folder.rbegin() != '\\')
			folder += L"/";

		VectorFileInfo result;
		getSystemFileList(result, folder, _mask);

		for (const auto& item : result)
		{
			if (item.folder)
				continue;

			if (_fullpath)
				_result.push_back(folder + item.name);
			else
				_result.push_back(item.name);
		}

		if (_recursive)
		{
			getSystemFileList(result, folder, L"*");

			for (const auto& item : result)
			{
				if (!item.folder || item.name == L".." || item.name == L".")
					continue;
				scanFolder(_result, folder + item.name, _recursive, _mask, _fullpath);
			}
		}
	}

}

#endif // FILE_SYSTEM_INFO_H_
