#pragma once

#include "Types.h"
#include "BaseObj.h"
#include "Util.h"
#include "Vector.h"

#include <utility>

namespace ehs
{
    enum class SearchPattern
    {
        LEFT_RIGHT,
        RIGHT_LEFT
    };

    enum class IndexResult
    {
        BEGINNING,
        ENDING
    };

    /// A helper class for C-style strings.
    /// @tparam T The character's data type to use.
    /// @tparam N The number data type to use.
	template<typename T = Char_8, typename N = UInt_64>
	class Str : public BaseObj
	{
	private:
		N size;
		T* data;

	public:
	    /// Frees any data created on the heap.
        ~Str() override
		{
            delete[] data;
		}

		/// Default members initialization.
		Str()
			: size(0), data(nullptr)
		{
			AddType("Str");
		}

		/// Initializes members with given C-style string.
		/// @param [in] str The C-style string.
		/// @param [in] size The size of the given C-style string.
		Str(const T* const str, const N size)
			: size((size) ? size : Len(str)), data(nullptr)
		{
			data = new T[this->size + 1];

            Util::Copy(data, str, Size(true));

			data[this->size] = 0;

			AddType("Str");
		}

		/// Initializes members with given C-style string.
		/// @param [in] str The C-style string.
		Str(const T* const str)
			: size(Len(str)), data(nullptr)
		{
			data = new T[size + 1];

            Util::Copy(data, str, Size(true));

			data[size] = 0;

			AddType("Str");
		}

		/// Initializes string with the given size.
		/// @param [in] size The size.
		Str(const N size)
			: size(size), data(new T[size + 1])
		{
			data[size] = 0;

			AddType("Str");
		}

		/// A move constructor.
		Str(Str&& str) noexcept
			: BaseObj((BaseObj&&)str), size(str.size), data(str.data)
		{
			str.size = 0;
			str.data = nullptr;
		}

		/// Copies all members from the given string object.
		/// @param [in] str The string object to copy from.
		Str(const Str& str)
			: BaseObj(str), size(str.size), data(new T[size + 1])
		{
            Util::Copy(data, str.data, Size(true));

			data[size] = 0;
		}

		Str& operator=(Str&& str) noexcept
		{
			if (this == &str)
				return *this;

			BaseObj::operator=((BaseObj&&)str);

			size = str.size;
			delete[] data;
			data = str.data;

			str.size = 0;
			str.data = nullptr;

			return *this;
		}

		/// Copies all members from the given string object.
		/// @param [in] str The string object to copy from.
		/// @returns The string that has been assigned to.
		Str& operator=(const Str& str)
		{
	        if (&str == this)
	            return *this;

			BaseObj::operator=(str);

			size = str.size;

            delete[] data;
			data = new T[size + 1];
            Util::Copy(data, str.data, Size(true));
			data[size] = 0;

			return *this;
		}

        /// Copies the given C-style string and retrieves the size.
        /// @param [in] str The C-style string to copy from.
        /// @returns The string object that has been assigned to.
		Str<T, N>& operator=(const T* const str)
		{
			size = Len(str);

            delete[] data;

			data = new T[size + 1];

            Util::Copy(data, str, Size(true));

			data[size] = 0;

			return *this;
		}

		/// Concatenates with the given C-style string.
		/// @param [in] str The given C-style string.
		/// @returns The resulting string object.
		Str<T, N>& operator+=(const T* const str)
		{
			N inputSize = Len(str);

			T* result = new T[size + inputSize + 1];

            Util::Copy(result, data, Size(true));

            delete[] data;

            Util::Copy(&result[size], str, inputSize * sizeof(T));

			result[size + inputSize] = 0;
			size += inputSize;
			data = result;

			return *this;
		}

        /// Concatenates with the given string object.
        /// @param [in] str The given string object.
        /// @returns The resulting string object.
		Str<T, N>& operator+=(const Str<T, N>& str)
		{
			T* result = new T[size + str.size + 1];

            Util::Copy(result, data, Size(true));

            delete[] data;

            Util::Copy(&result[size], str, str.Size(true));

			result[size + str.size] = 0;
			size += str.Size();
			data = result;

			return *this;
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to concatenate.
		/// @returns The resulting string object.
		Str<T, N>& operator+=(const SInt_64 num)
		{
			return operator+=(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to concatenate.
		/// @returns The resulting string object.
		Str<T, N>& operator+=(const UInt_64 num)
		{
			return operator+=(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to concatenate.
		/// @returns The resulting string object.
		Str<T, N>& operator+=(const SInt_32 num)
		{
			return operator+=(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to concatenate.
		/// @returns The resulting string object.
		Str<T, N>& operator+=(const UInt_32 num)
		{
			return operator+=(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to concatenate.
		/// @returns The resulting string object.
		Str<T, N>& operator+=(const SInt_16 num)
		{
			return operator+=(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to concatenate.
		/// @returns The resulting string object.
		Str<T, N>& operator+=(const UInt_16 num)
		{
			return operator+=(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to concatenate.
		/// @returns The resulting string object.
		Str<T, N>& operator+=(const SInt_8 num)
		{
			return operator+=(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to concatenate.
		/// @returns The resulting string object.
		Str<T, N>& operator+=(const UInt_8 num)
		{
			return operator+=(FromNum(num));
		}

		#ifdef EHS_OS_WINDOWS
			/// Concatenates with the given number.
			/// @param [in] num The given number to concatenate.
			/// @returns The result.
			Str<T, N>& operator+=(const DWORD num)
			{
				return operator+=(FromNum(num));
			}

			/// Concatenates with the given number.
			/// @param [in] num The given number to concatenate.
			/// @returns The result.
			Str<T, N>& operator+=(const HRESULT num)
			{
				return operator+=(FromNum(num));
			}
		#endif

		/// Concatenates with the given number.
		/// @param [in] num The given number to concatenate.
		/// @returns The resulting string object.
		Str<T, N>& operator+=(const float num)
		{
			return operator+=(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to concatenate.
		/// @returns The resulting string object.
		Str<T, N>& operator+=(const double num)
		{
			return operator+=(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to concatenate.
		/// @returns The resulting string object.
		Str<T, N>& operator+=(const long double num)
		{
			return operator+=(FromNum(num));
		}

        /// Concatenates with the given C-style string.
        /// @param [in] str The given C-style string.
        /// @returns The resulting string object.
		Str<T, N> operator+(const T* const str) const
		{
			N inSize = Len(str);

			Str<T, N> result(size + inSize);

            Util::Copy(result.data, data, Size(true));

            Util::Copy(&result.data[size], str, inSize * sizeof(T));

			result.data[size + inSize] = 0;

			return result;
		}

        /// Concatenates with the given string object.
        /// @param [in] str The given string object.
        /// @returns The resulting string object.
		Str<T, N> operator+(const Str<T, N>& str) const
		{
			Str<T, N> result(size + str.size);

            Util::Copy(result.data, data, Size(true));

            Util::Copy(&result.data[size], str.data, str.Size(true));

			result.data[size + str.size] = 0;

			return result;
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to Concatenate.
		/// @returns The resulting string object.
		Str<T, N> operator+(const SInt_64 num) const
		{
			return operator+(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to Concatenate.
		/// @returns The resulting string object.
		Str<T, N> operator+(const UInt_64 num) const
		{
			return operator+(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to Concatenate.
		/// @returns The resulting string object.
		Str<T, N> operator+(const SInt_32 num) const
		{
			return operator+(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to Concatenate.
		/// @returns The resulting string object.
		Str<T, N> operator+(const UInt_32 num) const
		{
			return operator+(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to Concatenate.
		/// @returns The resulting string object.
		Str<T, N> operator+(const SInt_16 num) const
		{
			return operator+(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to Concatenate.
		/// @returns The resulting string object.
		Str<T, N> operator+(const UInt_16 num) const
		{
			return operator+(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to Concatenate.
		/// @returns The resulting string object.
		Str<T, N> operator+(const SInt_8 num) const
		{
			return operator+(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to Concatenate.
		/// @returns The resulting string object.
		Str<T, N> operator+(const UInt_8 num) const
		{
			return operator+(FromNum(num));
		}

		#ifdef EHS_OS_WINDOWS
			/// Concatenates with the given number.
			/// @param [in] str The given number to Concatenate.
			/// @returns The result.
			Str<T, N> operator+(const DWORD num) const
			{
				return operator+(FromNum(num));
			}

			/// Concatenates with the given number.
			/// @param [in] str The given number to Concatenate.
			/// @returns The result.
			Str<T, N> operator+(const HRESULT num) const
			{
				return operator+(FromNum(num));
			}
		#endif

		/// Concatenates with the given number.
		/// @param [in] num The given number to Concatenate.
		/// @returns The resulting string object.
		Str<T, N> operator+(const float num) const
		{
			return operator+(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to Concatenate.
		/// @returns The resulting string object.
		Str<T, N> operator+(const double num) const
		{
			return operator+(FromNum(num));
		}

		/// Concatenates with the given number.
		/// @param [in] num The given number to Concatenate.
		/// @returns The resulting string object.
		Str<T, N> operator+(const long double num) const
		{
			return operator+(FromNum(num));
		}

		/// Compares with a another string. First comparing sizes.
		/// @param [in] str The string object to compare with.
		/// @returns Whether or not they are equal.
        bool operator==(T* str) const
        {
            if (size != Len(str))
                return false;

            return Util::Compare(data, str, Size(true));
        }

		/// Compares with a C-style string. First comparing sizes.
		/// @param [in] str The C-style string to compare with.
		/// @returns Whether or not they are equal.
		bool operator==(const T* const str) const
		{
			if (size != Len(str))
				return false;

			return Util::Compare(data, str, Size(true));
		}

        /// Compares with a string object. First comparing sizes.
        /// @param [in] str The string object to compare with.
        /// @returns Whether or not they are equal.
		bool operator==(const Str<T, N>& str) const
		{
			if (size != str.size)
				return false;

			return Util::Compare(data, str, Size(true));
		}

        /// Compares with a C-style string. First comparing sizes.
        /// @param [in] str The C-style string to compare with.
        /// @returns Whether or not they are equal.
		bool operator!=(const T* const str) const
		{
			if (size != Len(str))
				return true;

			return !Util::Compare(data, str, Size(true));
		}

        /// Compares with a string object. First comparing sizes.
        /// @param [in] str The string to compare with.
        /// @returns Whether or not they are equal.
		bool operator!=(const Str<T, N>& str) const
		{
			if (size != str.size)
				return true;

			return !Util::Compare(data, str, Size(true));
		}

        /// Retrieves the raw C-style string from casting a string object.
		operator T* () const
		{
			return data;
		}

        /// Resizes the string.
        /// @param [in] newSize The size to change to.
		void Resize(const N newSize)
		{
			if (newSize == size)
				return;

			T* result = new T[newSize + 1];

			if (newSize > size)
                Util::Copy(result, data, Size(true));
			else
                Util::Copy(result, data, newSize * sizeof(T));

			size = newSize;

            delete[] data;

			data = result;
			data[size] = 0;
		}

		/// Retrieves the size of the string.
		/// @param [in] inBytes To retrieve the size in bytes.
		/// @returns The resulting size.
		N Size(bool inBytes = false) const
		{
			if (inBytes)
				return sizeof(T) * size;
			else
				return size;
		}

		/// Finds the null terminator in the string and makes it the exact size if greater than.
		void ExactSize()
		{
			size = Len(data);

			T* result = new T[size + 1];
            Util::Copy(result, data, Size(true));

            delete[] data;

			result[size] = 0;
			data = result;
		}

        /// Copies a string object to the referenced string object.
        /// @param dstOffset The offset index to copy to.
        /// @param src The source string object to copy from.
		void Copy(const N dstOffset, const Str<T, N>& src)
        {
            if (dstOffset + src.size > size)
            {
                //Log_8 log(__FUNCSIG__, LogType::ERR, 0, "The parameters \"dstOffset\" (" + Str_8::FromNum(dstOffset) + ") + \"srcSize\" (" + Str_8::FromNum(src.Size()) + ") is greater than the referenced string object's size (" + Str_8::FromNum(size) + ").");
                return;
            }

            Util::Copy(data[dstOffset], src, src.Size(true));
        }

        /// Copies a C-style string to the referenced string object.
        /// @param dstOffset The offset index to copy to.
        /// @param src The source C-style string to copy from.
        void Copy(const N dstOffset, const T* const src)
        {
	        N srcSize = Len(src);

            if (dstOffset + srcSize > size)
            {
                //Log_8 log(__FUNCSIG__, LogType::ERR, 0, "The parameters \"dstOffset\" (" + Str_8::FromNum(dstOffset) + ") + \"srcSize\" (" + Str_8::FromNum(srcSize) + ") is greater than the referenced string object's size (" + Str_8::FromNum(size) + ").");
                return;
            }

            Util::Copy(data[dstOffset], src, srcSize * sizeof(T));
        }

		/// Copies a C-style string to the referenced string object.
		/// @param dstOffset The offset index to copy to.
		/// @param src The source C-style string to copy from.
		/// @param srcSize The size of the source C-style string.
		void Copy(const N dstOffset, const T* const src, const N srcSize)
        {
	        if (dstOffset + srcSize > size)
            {
	            //Log_8 log(__FUNCSIG__, LogType::ERR, 0, "The parameters \"dstOffset\" (" + Str_8::FromNum(dstOffset) + ") + \"srcSize\" (" + Str_8::FromNum(srcSize) + ") is greater than the referenced string object's size (" + Str_8::FromNum(size) + ").");
	            return;
            }

            Util::Copy(data[dstOffset], src, srcSize * sizeof(T));
        }

		/// Inserts a string at a specified index.
		/// @param [in] index The index to insert the string at.
		/// @param [in] value The string to insert.
		void Insert(const N index, const Str& value)
        {
			if (!value.size)
				return;

			N newSize = size + value.size;

        	T* result = new T[newSize + 1];

        	Util::Copy(result, data, index * sizeof(T));

        	Util::Copy(&result[index], value.data, value.Size(true));

			Util::Copy(&result[index + value.size], &data[index], size - index);

        	result[newSize] = 0;

        	delete[] data;
        	data = result;
        	size = newSize;
        }

		/// Inserts a character at a specified index.
		/// @param [in] index The index to insert the character at.
		/// @param [in] value The character to insert.
		void Insert(const N index, const T value)
        {
        	N newSize = 0;
        	if (index > size - 1)
        		newSize = size + ((index + 1) - size);
        	else
        		newSize = size + 1;

        	T* result = new T[newSize + 1];

        	for (N i = 0; i < index; ++i)
        		result[i] = data[i];

        	result[index] = value;

        	for (N i = index; i < size; ++i)
        		result[i + 1] = data[i];

        	result[newSize] = 0;

        	delete[] data;
        	data = result;
        	size = newSize;
        }

		/// Removes characters withing the given range.
		/// @param [in] start The index to start.
		/// @param [in] end The index to end.
		/// @returns The removed string object.
		Str Remove(const N start, const N end)
        {
			if (!size || start >= size || end > size || end <= start)
				return {};

        	Str popped(&data[start], end - start);

        	N newSize = size - popped.size;
        	T* result = new T[newSize + 1];

        	Util::Copy(result, data, start * sizeof(T));

        	Util::Copy(&result[start], &data[end], (size - end) * sizeof(T));

        	result[newSize] = 0;

        	delete[] data;
        	data = result;
        	size = newSize;

        	return popped;
        }

		/// Removes a character at the given index.
		/// @param [in] index The index to remove a character.
		/// @returns The character removed.
		T Remove(const N index)
        {
        	T popped = {};

        	if (!size || index >= size)
        		return popped;

        	popped = data[index];

        	N newSize = size - 1;
        	T* result = new T[newSize];

        	for (N i = 0; i < index; ++i)
        		result[i] = data[i];

        	for (N i = index + 1; i < size; ++i)
        		result[i - 1] = data[i];

        	delete[] data;
        	data = result;
        	size = newSize;

        	return popped;
        }

        /// Adds a value at the end of the string.
        /// @param [in] value The character to push to the end of the string.
        void Push(const Str<T, N> &value)
        {
            T* result = new T[size + value.size + 1];
            Util::Copy(result, data, Size(true));
            Util::Copy(&result[size], value, value.Size(true));

            result[size + value.Size()] = 0;

            delete[] data;

            data = result;
            size += value.size;
        }

        /// Adds a value at the end of the string.
        /// @param [in] value The C-style string to push to the end of the string.
        /// @param [in] size The size of the given C-style string.
        void Push(const T* const value, const N size)
        {
            T* result = new T[this->size + size + 1];
            Util::Copy(result, data, Size(true));
            Util::Copy(&result[this->size], value, size * sizeof(T));

            result[this->size + size] = 0;

            delete[] data;

            data = result;
            this->size += size;
        }

        /// Adds a value at the end of the string.
        /// @param [in] value The C-style string to push to the end of the string.
        void Push(const T* const value)
        {
            N inSize = Len(value);

            T* result = new T[size + inSize + 1];
            Util::Copy(result, data, Size(true));
            Util::Copy(result[size], value, inSize * sizeof(T));

            result[size + inSize] = 0;

            delete[] data;

            data = result;
            size += inSize;
        }

		/// Adds a value at the end of the string.
		/// @param [in] value The character to push to the end of the string.
		void Push(const T value)
		{
			T* result = new T[size + 2];

            Util::Copy(result, data, Size(true));

			result[size] = value;
			result[size + 1] = 0;

            delete[] data;

			data = result;
			++size;
		}

        /// Removes the value at the end of the array.
        /// @returns The value that was popped.
		T Pop()
        {
            T* result = new T[size--];

            T value = data[size];

            Util::Copy(result, data, Size(true));

            result[size] = 0;

            delete[] data;

            data = result;

            return value;
        }

		/// Retrieves the string in bytes for serialization.
		/// @returns The string in bytes.
		const Byte* ToBytes() const
		{
			return (Byte*)data;
		}

		/// Retrieves the string in bytes for serialization.
		/// @returns The string in bytes.
		Byte* ToBytes()
		{
			return (Byte*)data;
		}

		/// Converts all upper-case ASCII characters to lower-case.
		void ToLower()
		{
			for (N i = 0; i < size; ++i)
				if (data[i] > 64 && data[i] < 91)
					data[i] += 32;
		}

        /// Converts all upper-case ASCII characters to lower-case.
        /// @returns The resulting string object.
		Str<T, N> GetLower() const
		{
			Str<T, N> result(size);

			for (N i = 0; i < size; ++i)
				if (data[i] > 64 && data[i] < 91)
					result[i] = data[i] + 32;
				else
					result[i] = data[i];

			return result;
		}

        /// Converts all lower-case ASCII characters to upper-case.
		void ToUpper()
		{
			for (N i = 0; i < size; ++i)
				if (data[i] > 96 && data[i] < 123)
					data[i] -= 32;
		}

        /// Converts all lower-case ASCII characters to upper-case.
        /// @returns The resulting string object.
		Str<T, N> GetUpper() const
		{
			Str<T, N> result(size);

			for (N i = 0; i < size; ++i)
				if (data[i] > 96 && data[i] < 123)
					result[i] = data[i] - 32;
				else
					result[i] = data[i];

			return result;
		}

		/// Reverses the entire string object.
        void Reverse()
        {
	        if (size <= 1 || !data)
	            return;

	        T* r = new T[size + 1];

            for (N i = 0; i < size; ++i)
                r[i] = data[size - 1 - i];

            r[size] = 0;

            delete[] data;

            data = r;
        }

        /// Reverses the entire string object.
        /// @returns The resulting string object.
        Str<T, N> GetReverse()
        {
            if (size <= 1 || !data)
                return *this;

            Str<T, N> r(size);

            for (N i = 0; i < size; ++i)
                r[i] = data[size - 1 - i];

            return r;
        }

		/// Clips the string at the given index and with the given size.
		/// @param [in] index The index to clip at.
		/// @param [in] size The size for the clip starting from the index.
		/// @returns The resulting string object.
		Str<T, N> Sub(const N index, const N size = 0) const
		{
			if (index >= this->size)
			{
				//Log_8 log(__FUNCSIG__, LogType::ERR, 0, "The given index parameter, \"" + Str<T, N>::FromNum(index) + "\" is greater than the string's size, \"" + Str<T, N>::FromNum(this->size) + "\".");

				return Str<T, N>();
			}

			if (size)
			{
				if (size > this->size)
				{
					//Log_8 log(__FUNCSIG__, LogType::ERR, 1, "The given size parameter, \"" + Str<T, N>::FromNum(size) + "\" is greater than the string's size, \"" + Str<T, N>::FromNum(this->size) + "\", at the index, \"" + Str<T, N>::FromNum(index) + "\".");

					return Str<T, N>();
				}

				Str<T, N> result(size);

                Util::Copy(result, &data[index], result.Size(true));

				return result;
			}
			else
			{
				Str<T, N> result(this->size - index);

                Util::Copy(result, &data[index], result.Size(true));

				return result;
			}
		}

		/// Splits a string into a Vector with the given separator.
		/// @param [in] ide The given string as the separator.
		/// @param [in] max The max amount of times to split the string.
		/// @returns The resulting string object.
		Vector<Str<T, N>, N> Split(const Str<T, N>& ide, const N max = 0) const
		{
			Vector<Str<T, N>, N> result(0, 5);

			N b = 0;

			for (N i = 0, c = 0; i < size; ++i)
			{
				if (data[i] == ide[c])
				{
					if (++c == ide.Size())
					{
					    N r = i - (c - 1) - b;
					    if (!r)
                            result.Push(Str<T, N>());
					    else
						    result.Push(Str<T, N>(&data[b], r));

						b = i + 1;
						c = 0;

						if (max && result.Size() == max)
							break;
					}
				}
				else
				{
					c = 0;
				}
			}

			if (b < size)
				result.Push(Str<T, N>(&data[b], size - b));

			return result;
		}

		/// Removes all instances of the ide.
		/// @param [in] ide The string to look for.
		/// @returns The resulting string object.
		Str<T, N> RemoveAll(const Str<T, N>& ide) const
		{
			Str<T, N> result(size);

			for (N i = 0, b = 0, c = 0; i < size; ++i)
			{
				if (data[i] == ide[c])
				{
					if (++c == ide.Size())
						c = 0;
				}
				else
				{
					if (c)
						for (N d = c; d < 0; --d)
							result[b++] = data[i - d];
					else
						result[b++] = data[i];

					c = 0;
				}
			}

			return result;
		}

		/// Replaces all instances of ide with the replacer.
		/// @param [in] ide The string to look for.
		/// @param [in] replacer The string placed.
		/// @returns The resulting string object.
		Str ReplaceAll(const Str& ide, const Str& replacer) const
		{
			Str<T, N> result;

			for (N i = 0, b = 0; i < size; ++i)
			{
				if (data[i] == ide[b])
				{
					if (++b == ide.Size())
					{
						result.Push(replacer);
						b = 0;
					}
				}
				else
				{
					result.Push(data[i]);
				}
			}

			return result;
		}

		/// Finds the first instance of the given string object.
		/// @param [in] ide The string to look for.
		/// @param [out] index The index of the string found. Can be a nullptr.
		/// @param [in] pattern The search pattern for optimization.
		/// @param [in] result What index to return where the first instance is found.
		/// @returns The index where the instance was found with the result varying from the result parameter.
		bool Find(const Str<T, N>& ide, N* const index = nullptr, const SearchPattern pattern = SearchPattern::LEFT_RIGHT, const IndexResult result = IndexResult::BEGINNING) const
		{
			if (pattern == SearchPattern::LEFT_RIGHT)
			{
				for (N i = 0, c = 0; i < size; ++i)
				{
					if (data[i] == ide[c])
					{
						if (++c == ide.Size())
						{
							if (result == IndexResult::BEGINNING)
                            {
                                if (index)
                                    *index = i - (c - 1);

                                return true;
                            }
							else
                            {
                                if (index)
                                    *index = i;

                                return true;
                            }
						}
					}
				}
			}
			else if (pattern == SearchPattern::RIGHT_LEFT)
			{
				for (N i = size, c = ide.Size(); i > 0; --i)
				{
					if (data[i - 1] == ide[c - 1])
					{
						if (--c == 0)
						{
							if (result == IndexResult::BEGINNING)
                            {
                                if (index)
                                    *index = i - (ide.Size() - 1);

                                return true;
                            }
							else
                            {
                                if (index)
                                    *index = i;

                                return true;
                            }
						}
					}
				}
			}

			return false;
		}

		/// Checks if the current string contains the given ide.
		/// @param [in] ide The given ide to check for.
		/// @param [in] pattern The search pattern to use.
		/// @returns True if the current string does contain the ide.
        bool Contains(const Str<T, N>& ide, const SearchPattern pattern = SearchPattern::LEFT_RIGHT) const
        {
            if (pattern == SearchPattern::LEFT_RIGHT)
            {
                for (N i = 0, c = 0; i < size; ++i)
                {
                    if (data[i] == ide[c])
                    {
                        if (++c == ide.Size())
                        {
                            return true;
                        }
                    }
                }
            }
            else if (pattern == SearchPattern::RIGHT_LEFT)
            {
                for (N i = size, c = ide.Size(); i > 0; --i)
                {
                    if (data[i - 1] == ide[c - 1])
                    {
                        if (--c == 0)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

		/// Checks if the string represents a number. Must not have any alphabetical characters.
		/// @returns The result.
		bool IsNum() const
		{
            if (!size)
                return false;

            if ((data[0] < '0' || data[0] > '9') && data[0] != '-' && data[0] != '.')
                return false;

			for (N i = 1; i < size; ++i)
				if ((data[i] < '0' || data[i] > '9') && data[i] != '.')
					return false;

			return true;
		}

		/// Converts a number into hexadecimal string representation.
		/// @tparam I The data type of the number given.
		/// @param [in] num The number to convert.
		/// @returns The resulting hexadecimal.
		template<typename I = int>
		static Str NumToHex(const I num)
		{
			static const T hex[] = "0123456789ABCDEF";

			Str result(sizeof(I) * 2);

			for (UInt_8 i = 0; i < sizeof(I); ++i)
			{
				result[i * 2] = hex[((Byte*)&num)[i] / 16];
				result[i * 2 + 1] = hex[((Byte*)&num)[i] % 16];
			}

			return result;
		}

		/// Converts a string hexadecimal into a number.
		/// @tparam I The data type of the number outputted.
		/// @param [in] in The string to convert.
		/// @returns The resulting number.
		template<typename I = int>
		static I HexToNum(const Str& in)
		{
			N offset = 0;
			bool neg = false;

			if (in[offset] == 45)
			{
				neg = true;
				++offset;
			}
			else if (in[offset] == 43)
				++offset;

			if (in[offset] == 48 && (in[offset + 1] == 88 || in[offset + 1] == 120))
				offset += 2;

			N acc = 0;

			for (N i = offset; i < in.Size(); ++i)
			{
				I value = 0;

				if (in[i] > 47 && in[i] < 58)
					value = in[i] - 48;
				else if (in[i] > 64 && in[i] < 71)
					value = in[i] - 55;
				else if (in[i] > 96 && in[i] < 103)
					value = in[i] - 87;

				if (value >= 16)
					return 0;

				acc *= 16;
				acc += value;
			}

			return neg ? -acc : acc;
		}

		/// Converts the current string from hexadecimal into a number.
		/// @tparam I The data type of the number outputted.
		/// @returns The resulting number.
		template<typename I = int>
		I HexToNum() const
        {
            N offset = 0;
            bool neg = false;

            if (data[offset] == 45)
            {
                neg = true;
                ++offset;
            }
            else if (data[offset] == 43)
                ++offset;

            if (data[offset] == 48 && (data[offset + 1] == 88 || data[offset + 1] == 120))
                offset += 2;

            N acc = 0;

            for (N i = offset; i < size; ++i)
            {
                I value = 0;

                if (data[i] > 47 && data[i] < 58)
                    value = data[i] - 48;
                else if (data[i] > 64 && data[i] < 71)
                    value = data[i] - 55;
                else if (data[i] > 96 && data[i] < 103)
                    value = data[i] - 87;

                if (value >= 16)
                    return 0;

                acc *= 16;
                acc += value;
            }

            return neg ? -acc : acc;
        }

		/// Converts the string into a number.
		/// @tparam I The resulting number's data type.
		/// @returns The result.
		/// @note Use "IsNum" before this if the string object is not guaranteed to be a number.
		template<typename I = N>
		I ToDecimal() const
		{
            I r = 0;

            if (!size)
                return r;

	        for (N i = data[0] == '-' ? 1 : 0; i < size; ++i)
	            r = r * 10 + data[i] - '0';

            if (data[0] == '-')
                r *= -1;

			return r;
		}

		/// Converts the string into a floating point number.
		/// @returns The resulting float.
		/// @note Use "IsNum" before this if the string object is not guaranteed to be a number.
        float ToFloat() const
        {
            N decPoint = size;
            Find(".", &decPoint);

            float result = 0.0f;
            float fraction = 0.0f;
            float scale = 1.0f;

            for (N i = 0; i < decPoint; ++i)
                result = result * 10.0f + data[i] - '0';

            for (N i = decPoint + 1; i < size; ++i)
            {
                fraction = fraction * 10.0f + data[i] - '0';
                scale *= 10.0f;
            }

            result += fraction / scale;

            return result;
        }

		/// Converts the string into a double floating point number.
		/// @returns The resulting double.
		/// @note Use "IsNum" before this if the string object is not guaranteed to be a number.
        double ToDouble() const
        {
            N decPoint = size;
            Find(".", &decPoint);

            double result = 0.0f;
            double fraction = 0.0f;
            double scale = 1.0f;

            for (N i = 0; i < decPoint; ++i)
                result = result * 10.0f + data[i] - '0';

            for (N i = decPoint + 1; i < size; ++i)
            {
                fraction = fraction * 10.0f + data[i] - '0';
                scale *= 10.0f;
            }

            result += fraction / scale;

            return result;
        }

		/// Converts the string into a long double floating point number.
		/// @returns The resulting long double.
		/// @note Use "IsNum" before this if the string object is not guaranteed to be a number.
        long double ToLDouble() const
        {
            N decPoint = size;
            Find(".", &decPoint);

            long double result = 0.0f;
            long double fraction = 0.0f;
            long double scale = 1.0f;

            for (N i = 0; i < decPoint; ++i)
                result = result * 10.0f + data[i] - '0';

            for (N i = decPoint + 1; i < size; ++i)
            {
                fraction = fraction * 10.0f + data[i] - '0';
                scale *= 10.0f;
            }

            result += fraction / scale;

            return result;
        }

		/// Converts the given number into a string.
		/// @param [in] num The given number to convert.
		/// @returns The resulting string representation.
		static Str<T, N> FromNum(const SInt_64 num)
		{
			if (num == 0)
			{
				Str<T, N> z(1);
				z[0] = 48;
				return z;
			}

			Str<T, N> r(21);

			SInt_64 nonNeg;
			if (num < 0)
				nonNeg = -num;
			else
				nonNeg = num;

			N i = 0;

			while (nonNeg != 0)
			{
				r[i++] = 48 + nonNeg % 10;
				nonNeg /= 10;
			}

			if (num < 0)
				r[i++] = 45;

			r.Resize(i);

			return r.GetReverse();
		}

        /// Converts the given number into a string.
        /// @param [in] num The given number to convert.
		/// @returns The resulting string representation.
		static Str<T, N> FromNum(const UInt_64 num)
		{
            if (num == 0)
            {
                Str<T, N> z(1);
                z[0] = 48;
                return z;
            }

            Str<T, N> r(21);

            UInt_64 nonNeg = num;

            N i = 0;

            while (nonNeg != 0)
            {
                r[i++] = 48 + nonNeg % 10;
                nonNeg /= 10;
            }

            r.Resize(i);

            return r.GetReverse();
		}

		/// Converts the given number into a string.
		/// @param [in] num The given number to convert.
		/// @returns The resulting string representation.
		static Str<T, N> FromNum(const SInt_32 num)
		{
			if (num == 0)
			{
				Str<T, N> z(1);
				z[0] = 48;
				return z;
			}

			Str<T, N> r(11);

			SInt_32 nonNeg;
			if (num < 0)
				nonNeg = -num;
			else
				nonNeg = num;

			N i = 0;

			while (nonNeg != 0)
			{
				r[i++] = 48 + nonNeg % 10;
				nonNeg /= 10;
			}

			if (num < 0)
				r[i++] = 45;

			r.Resize(i);

			return r.GetReverse();
		}

		/// Converts the given number into a string.
		/// @param [in] num The given number to convert.
		/// @returns The resulting string representation.
		static Str<T, N> FromNum(const UInt_32 num)
		{
			if (num == 0)
			{
				Str<T, N> z(1);
				z[0] = 48;
				return z;
			}

			Str<T, N> r(11);

			UInt_32 nonNeg = num;

			N i = 0;

			while (nonNeg != 0)
			{
				r[i++] = 48 + nonNeg % 10;
				nonNeg /= 10;
			}

			r.Resize(i);

			return r.GetReverse();
		}

		/// Converts the given number into a string.
		/// @param [in] num The given number to convert.
		/// @returns The resulting string representation.
		static Str<T, N> FromNum(const SInt_16 num)
		{
			if (num == 0)
			{
				Str<T, N> z(1);
				z[0] = 48;
				return z;
			}

			Str<T, N> r(6);

			SInt_16 nonNeg;
			if (num < 0)
				nonNeg = -num;
			else
				nonNeg = num;

			N i = 0;

			while (nonNeg != 0)
			{
				r[i++] = 48 + nonNeg % 10;
				nonNeg /= 10;
			}

			if (num < 0)
				r[i++] = 45;

			r.Resize(i);

			return r.GetReverse();
		}

		/// Converts the given number into a string.
		/// @param [in] num The given number to convert.
		/// @returns The resulting string representation.
		static Str<T, N> FromNum(const UInt_16 num)
		{
			if (num == 0)
			{
				Str<T, N> z(1);
				z[0] = 48;
				return z;
			}

			Str<T, N> r(6);

			UInt_16 nonNeg = num;

			N i = 0;

			while (nonNeg != 0)
			{
				r[i++] = 48 + nonNeg % 10;
				nonNeg /= 10;
			}

			r.Resize(i);

			return r.GetReverse();
		}

		/// Converts the given number into a string.
		/// @param [in] num The given number to convert.
		/// @returns The resulting string representation.
		static Str<T, N> FromNum(const SInt_8 num)
		{
			if (num == 0)
			{
				Str<T, N> z(1);
				z[0] = 48;
				return z;
			}

			Str<T, N> r(4);

			SInt_8 nonNeg;
			if (num < 0)
				nonNeg = -num;
			else
				nonNeg = num;

			N i = 0;

			while (nonNeg != 0)
			{
				r[i++] = 48 + nonNeg % 10;
				nonNeg /= 10;
			}

			if (num < 0)
				r[i++] = 45;

			r.Resize(i);

			return r.GetReverse();
		}

		/// Converts the given number into a string.
		/// @param [in] num The given number to convert.
		/// @returns The resulting string representation.
		static Str<T, N> FromNum(const UInt_8 num)
		{
			if (num == 0)
			{
				Str<T, N> z(1);
				z[0] = 48;
				return z;
			}

			Str<T, N> r(4);

			UInt_8 nonNeg = num;

			N i = 0;

			while (nonNeg != 0)
			{
				r[i++] = 48 + nonNeg % 10;
				nonNeg /= 10;
			}

			r.Resize(i);

			return r.GetReverse();
		}

		#ifdef EHS_OS_WINDOWS
			/// Converts the given number into a string.
			/// @returns The result.
			static Str<T, N> FromNum(const DWORD num)
			{
				if (num == 0)
				{
					Str<T, N> z(1);
					z[0] = 48;
					return z;
				}

				Str<T, N> r(11);

				DWORD nonNeg = num;

				N i = 0;

				while (nonNeg != 0)
				{
					r[i++] = 48 + nonNeg % 10;
					nonNeg /= 10;
				}

				r.Resize(i);

				return r.GetReverse();
			}

			/// Converts the given number into a string.
			/// @returns The result.
			static Str<T, N> FromNum(const HRESULT num)
			{
				if (num == 0)
				{
					Str<T, N> z(1);
					z[0] = 48;
					return z;
				}

				Str<T, N> r(11);

				HRESULT nonNeg;
				if (num < 0)
					nonNeg = -num;
				else
					nonNeg = num;

				N i = 0;

				while (nonNeg != 0)
				{
					r[i++] = 48 + nonNeg % 10;
					nonNeg /= 10;
				}

				if (num < 0)
					r[i++] = 45;

				r.Resize(i);

				return r.GetReverse();
			}
		#endif

		/// Converts the given floating point into a string.
		/// @param [in] num The given floating point to convert.
		/// @param [in] maxDecimals The max decimal places to add.
		/// @returns The resulting string representation.
        static Str<T, N> FromNum(const float num, const UInt_8 maxDecimals = 5)
        {
			SInt_64 whole = (SInt_64)num;

			Str<T, N> result;
			if (whole < 0)
				result += "-";

			result += Str<T, N>::FromNum(whole);

			UInt_64 power = 10;
			for (UInt_64 i = 0; i < (UInt_64)maxDecimals - 1; ++i)
				power *= 10;

			SInt_64 fraction = (SInt_64)((num - (float)whole) * (float)power);
			if (!fraction)
				return result;

			result += ".";

			Str<T, N> fResult(maxDecimals);
			N i = 0;

			while (fraction)
			{
				fResult[i++] = 48 + fraction % 10;
				fraction /= 10;
			}

			while (i < maxDecimals)
				fResult[i++] = 48;

			fResult.Reverse();
			result += fResult;

			return result;
        }

		/// Converts the given double floating point into a string.
		/// @param [in] num The given double floating point to convert.
		/// @param [in] maxDecimals The max decimal places to add.
		/// @returns The resulting string representation.
        static Str<T, N> FromNum(const double num, const UInt_8 maxDecimals = 5)
        {
			SInt_64 whole = (SInt_64)num;

			Str<T, N> result;
			if (whole < 0)
				result += "-";

			result += Str<T, N>::FromNum(whole);

			UInt_64 power = 10;
			for (UInt_64 i = 0; i < (UInt_64)maxDecimals - 1; ++i)
				power *= 10;

			SInt_64 fraction = (SInt_64)((num - (double)whole) * (double)power);
			if (!fraction)
				return result;

			result += ".";

			Str<T, N> fResult(maxDecimals);
			N i = 0;

			while (fraction)
			{
				fResult[i++] = 48 + fraction % 10;
				fraction /= 10;
			}

			while (i < maxDecimals)
				fResult[i++] = 48;

			fResult.Reverse();
			result += fResult;

			return result;
        }

		/// Converts the given long double floating point into a string.
		/// @param [in] num The given long double floating point to convert.
		/// @param [in] maxDecimals The max decimal places to add.
		/// @returns The resulting string representation.
        static Str<T, N> FromNum(const long double num, const UInt_8 maxDecimals = 5)
        {
			SInt_64 whole = (SInt_64)num;

			Str<T, N> result;
			if (whole < 0)
				result += "-";

			result += Str<T, N>::FromNum(whole);

			UInt_64 power = 10;
			for (UInt_64 i = 0; i < (UInt_64)maxDecimals - 1; ++i)
				power *= 10;

			SInt_64 fraction = (SInt_64)((num - (long double)whole) * (long double)power);
			if (!fraction)
				return result;

			result += ".";

			Str<T, N> fResult(maxDecimals);
			N i = 0;

			while (fraction)
			{
				fResult[i++] = 48 + fraction % 10;
				fraction /= 10;
			}

			while (i < maxDecimals)
				fResult[i++] = 48;

			fResult.Reverse();
			result += fResult;

			return result;
        }

		/// A 32-bit FNV-1a hash algorithm.
		/// @param [in] str The string to hash.
		/// @returns The resulting hash. Zero if string does not contain any characters.
		static UInt_32 Hash_32(const Str<T, N>& str)
		{
			if (!str.Size())
				return 0;

		    const Byte* const bytes = str.ToBytes();

		    UInt_32 hash = 2166136261ul;

		    for (N i = 0; i < str.Size(true); ++i)
		        hash = (hash ^ bytes[i]) * 16777619;

		    return hash;
		}

		/// A 32-bit FNV-1a hash algorithm.
		/// @returns The resulting hash. Zero if string does not contain any characters.
        UInt_32 Hash_32() const
        {
			if (!size)
				return 0;

            const Byte* const bytes = ToBytes();

            UInt_32 hash = 2166136261ul;

            for (N i = 0; i < Size(true); ++i)
                hash = (hash ^ bytes[i]) * 16777619;

            return hash;
        }

		/// A 64-bit FNV-1a hash algorithm.
		/// @param [in] str The string to hash.
		/// @returns The resulting hash. Zero if string does not contain any characters.
		static UInt_64 Hash_64(const Str<T, N>& str)
		{
			if (!str.Size())
				return 0;

		    const Byte* const bytes = str.ToBytes();

		    UInt_64 hash = 14695981039346656037ull;

		    for (N i = 0; i < str.Size(true); ++i)
		        hash = (hash ^ bytes[i]) * 1099511628211;

		    return hash;
		}

		/// A 64-bit FNV-1a hash algorithm.
		/// @returns The resulting hash. Zero if string does not contain any characters.
        UInt_64 Hash_64() const
        {
			if (!size)
				return 0;

            const Byte* const bytes = ToBytes();

            UInt_64 hash = 14695981039346656037ull;

            for (N i = 0; i < Size(true); ++i)
                hash = (hash ^ bytes[i]) * 1099511628211;

            return hash;
        }

		/// Calculates the length of a C-Style string.
		/// @param [in] str The C-Style string to calculate.
		/// @returns The character count.
        static N Len(const T* const str)
        {
            N count = 0;
            while (str[count])
                ++count;
            return count;
        }

		/// Compares two C-style string with each other.
		/// @param [in] a The first C-style string to compare.
		/// @param [in] b The second C-style string to compare.
		/// @returns True if both C-style strings are equal.
        static bool Cmp(const T* const a, const T* const b)
        {
            N aSize = Len(a);
            N bSize = Len(b);

            if (aSize != bSize)
                return false;

        	return Util::Compare(a, b, aSize);
        }
	};

	typedef Str<Char_32, UInt_64> Str_32;
	typedef Str<Char_16, UInt_64> Str_16;
	typedef Str<Char_8, UInt_64> Str_8;
}

template<typename T = ehs::Char_8, typename N = ehs::UInt_64>
bool operator==(const T* const first, const ehs::Str<T, N>& second)
{
    N inSize = ehs::Str<T, N>::Len(first);
    if (second.Size() != inSize)
        return false;

    return ehs::Util::Compare(first, second, second.Size(true));
}

template<typename T = ehs::Char_8, typename N = ehs::UInt_64>
bool operator!=(const T* const first, const ehs::Str<T, N>& second)
{
	N inSize = ehs::Str<T, N>::Len(first);
	if (second.Size() != inSize)
		return true;

	return !ehs::Util::Compare(first, second, second.Size(true));
}

/// Concatenates a C-style string with a string.
/// @param [in] first The given C-style string.
/// @param [in] second The given string.
/// @returns The result.
template<typename T = ehs::Char_8, typename N = ehs::UInt_64>
ehs::Str<T, N> operator+(const T* const first, const ehs::Str<T, N>& second)
{
    N inSize = ehs::Str<T, N>::Len(first);

    ehs::Str<T, N> result(inSize + second.Size());

    ehs::Util::Copy(result, first, inSize * sizeof(T));
    ehs::Util::Copy(&result[inSize], &second[0], second.Size(true));

    result[inSize + second.Size()] = 0;

    return result;
}