/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#include "FsUtils.hpp"
#include "Base/Logger.hpp"
#include <unordered_map>
#include <regex>

namespace fs = std::filesystem;

namespace OpenVulkano
{
	bool FsUtils::DeleteEmptyFilesAndDirs(const std::filesystem::path& dir, const bool recursive)
	{
		bool deleted = false;
		for (auto& p: fs::directory_iterator(dir))
		{
			if (fs::is_directory(p))
			{
				if (recursive)
				{
					deleted |= (DeleteEmptyFilesAndDirs(p, recursive));
				}
				if (fs::is_empty(p))
				{
					fs::remove(p);
					Logger::FILESYS->info("Deleted empty directory: {}", p.path().string());
					deleted = true;
				}
			}
			else if (fs::is_empty(p))
			{
				fs::remove(p);
				Logger::FILESYS->info("Deleted empty file: {}", p.path().string());
				deleted = true;
			}
		}
		return deleted;
	}

	bool FsUtils::DeleteEmptyDirs(const std::filesystem::path& dir, const bool recursive)
	{
		bool deleted = false;
		for (auto& p: fs::directory_iterator(dir))
		{
			if (fs::is_directory(p))
			{
				if (recursive)
				{
					deleted |= (DeleteEmptyDirs(p, recursive));
				}
				if (fs::is_empty(p))
				{
					fs::remove(p);
					Logger::FILESYS->info("Deleted empty directory: {}", p.path().string());
					deleted = true;
				}
			}
		}
		return deleted;
	}

	bool FsUtils::DeleteEmptySubTrees(const std::filesystem::path& dir)
	{
		bool deleted = false;
		for (auto& p: fs::directory_iterator(dir))
		{
			if (fs::is_directory(p))
			{
				if (IsTreeEmpty(p))
				{
					fs::remove_all(p);
					deleted = true;
				}
			}
		}
		return deleted;
	}

	bool FsUtils::IsTreeEmpty(const std::filesystem::path& dir)
	{
		for (auto& p: fs::directory_iterator(dir))
		{
			if (fs::is_directory(p))
			{
				if (!IsTreeEmpty(p))
				{
					return false;
				}
			}
			else
			{
				return false;
			}
		}
		return true;
	}
	
	ByteSize FsUtils::GetDirSize(const std::filesystem::path& dir)
	{
		ByteSize size = 0;
		
		for (auto& p: fs::directory_iterator(dir))
		{
			if (fs::is_directory(p))
			{
				size += GetDirSize(p);
			}
			else
			{
				size += p.file_size();
			}
		}
		
		return size;
	}

	FsCount FsUtils::GetDirFileAndDirCount(const std::filesystem::path& dir)
	{
		FsCount count;
		for (auto& p: fs::directory_iterator(dir))
		{
			if (fs::is_directory(p))
			{
				count += GetDirFileAndDirCount(p);
				count.dirCount++;
			}
			else
			{
				count.fileCount++;
			}
		}
		return count;
	}
	
	std::string FsUtils::ToValidFileName(std::string str)
	{
		// Map of characters to replace with similar looking ones
		static const std::unordered_map<char, std::string> replacements = {
			{'<', "＜"},  // Use full-width less than
			{'>', "＞"},  // Use full-width greater than
			{':', "꞉"},   // Use modifier letter colon
			{'"', "＂"},  // Use full-width quotation mark
			{'/', "⁄"},   // Use fraction slash
			{'\\', "⧹"},  // Use big reverse solidus
			{'|', "｜"},  // Use full-width vertical line
			{'?', "？"},  // Use full-width question mark
			{'*', "∗"},   // Use asterisk operator
		};
		
		// Characters to remove (control characters and others that might cause issues)
		static const std::regex remove_pattern("[\\x00-\\x1F\\x7F~`!@#$%^&(){}\\[\\]=+,;]");
		
		// Maximum length for many filesystems
		static const size_t MAX_LENGTH = 255;
		
		// Replace problematic characters with similar looking ones
		for (const auto& [key, value] : replacements) {
			size_t pos = 0;
			while ((pos = str.find(key, pos)) != std::string::npos) {
				str.replace(pos, 1, value);
				pos += value.length();
			}
		}
		
		// Remove other invalid characters
		str = std::regex_replace(str, remove_pattern, "");
		
		// Trim leading/trailing spaces and periods
		str = std::regex_replace(str, std::regex("^[\\.\\s]+"), "");
		str = std::regex_replace(str, std::regex("[\\.\\s]+$"), "");
		
		// Replace multiple spaces with a single space
		str = std::regex_replace(str, std::regex("\\s+"), " ");
		
		// Ensure the string isn't empty after sanitization
		if (str.empty()) {
			str = "unnamed_file";
		}
		
		// Truncate if too long, being careful not to cut in the middle of a UTF-8 character
		if (str.length() > MAX_LENGTH) {
			size_t len = MAX_LENGTH;
			while (len > 0 && (str[len] & 0xC0) == 0x80) {
				--len;
			}
			str = str.substr(0, len);
		}
		
		return str;
	}
}
