/*
 * 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/.
 */

#pragma once

#include <vector>
#include <string>
#include <set>
#include <algorithm>
#include <array>
#include <bit>
#include <cassert>
#include <cinttypes>
#include <atomic>
#include "Data/Containers/Array.hpp"

namespace OpenVulkano
{
	class Utils
	{
		static std::atomic_int uniqueTypeID;

	public:
		static void SetThreadName(const std::string& name);
		static std::string GetThreadName();
		static uint64_t GetThreadId();

		static inline std::vector<const char*> toCString(const std::vector<std::string>& values)
		{
			std::vector<const char*> result;
			result.reserve(values.size());
			for (const auto& string : values) {
				result.push_back(string.c_str());
			}
			return result;
		}

		static inline std::vector<const char*> toCString(const std::set<std::string>& values)
		{
			std::vector<const char*> result;
			result.reserve(values.size());
			for (const auto& string : values) {
				result.push_back(string.c_str());
			}
			return result;
		}

		template<typename T, typename... N>
		static auto MakeStdArray(N&&... args) -> std::array<T, sizeof...(args)>
		{
			return { std::forward<N>(args)... };
		}

		template<typename T, typename U> 
		static bool GetFlag(T var, U flag)
		{ 
			return var & flag;
		}

		template<typename T, typename U> 
		static void SetFlag(T& var, U flag) 
		{ 
			var |= flag; 
		}

		template<typename T, typename U> 
		static void ResetFlag(T& var, U flag)
		{ 
			var &= ~flag; 
		}

		template <typename T>
		static inline bool Contains(std::vector<T>& vec, const T& element)
		{
			return (std::find(vec.begin(), vec.end(), element) != vec.end());
		}

		template <typename T>
		static inline void Remove(std::vector<T>& vec, const T& element)
		{
			vec.erase(std::remove(vec.begin(), vec.end(), element), vec.end());
		}

		template <typename Enumeration>
		static inline auto EnumAsInt(Enumeration const value)
			-> typename std::underlying_type<Enumeration>::type
		{
			return static_cast<typename std::underlying_type<Enumeration>::type>(value);
		}

		static inline constexpr size_t Align(size_t size, size_t alignment)
		{
			return (size + alignment - 1) & ~(alignment - 1);
		}

		static inline constexpr size_t AlignPage(size_t size)
		{
			return Align(size, 4096); //TODO detect system page size instead of relying on hardcoded value
		}

		template<typename T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
		static inline constexpr bool IsPow2(T i)
		{
			return ((i - 1) & i) == 0;
		}

		template<typename T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
		static inline constexpr T Log2OfPow2(T n)
		{
			assert(n != 0);
			assert(IsPow2(n));

			T log = 0;
			while(true)
			{
				n >>= 1;
				if (n == 0)
				{
					break;
				}
				log++;
			}
			return log;
		}

		static constexpr int64_t OctToInt(std::string_view string)
		{
			int64_t result = 0;
			for(int i = 0; i < static_cast<int>(string.length()); i++)
			{
				char c = string[i];
				if (c == 0) break;
				if (c == ' ') continue;
				if (c < '0' || c > '7') return -1;
				result = result * 8 + c - '0';
			}
			return result;
		}

		[[deprecated]]
		static constexpr bool IsLittleEndian()
		{
			constexpr bool isLittleEndian = std::endian::native == std::endian::little;
			return isLittleEndian;
		}

		static constexpr bool StartsWith(std::string_view str, std::string_view prefix)
		{
			return str.size() >= prefix.size() && 0 == str.compare(0, prefix.size(), prefix);
		}

		static constexpr bool EndsWith(std::string_view str, std::string_view suffix)
		{
			return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix);
		}

		static std::pair<std::string, std::string> SplitAtLastOccurrence(const std::string& str, char splitAt)
		{
			size_t pos = str.rfind(splitAt);
			if (pos == std::string::npos)  return {str, ""};
			else  return {str.substr(0, pos), str.substr(pos + 1)};
		}

		static std::vector<std::string> Split(const std::string_view& str, char separator)
		{
			std::vector<std::string> subs;
			size_t startPos = 0, pos;

			while ((pos = str.find(separator, startPos)) != std::string::npos)
			{
				if (pos != startPos)
				{
					subs.emplace_back(str.substr(startPos, pos - startPos));
				}
				startPos = pos + 1;
			}

			if (startPos < str.size())
			{
				subs.emplace_back(str.substr(startPos));
			}

			return subs;
		}

		template<typename T>
		static Array<char> ReadFile(const T& filePath, bool emptyOnMissing = false,
		                            bool nullTerminateString = false);

		template<class T>
		static int GetUniqueTypeId()
		{
			static const int id = uniqueTypeID++;
			return id;
		}
	};
}
