#pragma once

// C++
#include <stdint.h>
#include <random>
#include <time.h>
#include <string>
#include <type_traits>
#include <functional>
#include <type_traits>
#include <stdexcept>
#include <BitStream.h>

#include "Game.h"

/*!
  \file GeneralUtils.hpp
  \brief A namespace containing general utility functions
 */

//! The general utils namespace
namespace GeneralUtils {
    //! Converts a plain ASCII string to a UTF-16 string
    /*!
      \param string The string to convert
      \param size A size to trim the string to. Default is -1 (No trimming)
      \return An UTF-16 representation of the string
     */
    std::u16string ASCIIToUTF16(const std::string& string, size_t size = -1);

    //! Converts a UTF-16 string to a UTF-8 string
    /*!
      \param string The string to convert
      \param size A size to trim the string to. Default is -1 (No trimming)
      \return An UTF-8 representation of the string
     */
    std::string UTF16ToWTF8(const std::u16string& string, size_t size = -1);

    /**
     * Compares two basic strings but does so ignoring case sensitivity
     * \param a the first string to compare against the second string
     * \param b the second string to compare against the first string
     * @return if the two strings are equal
     */
    bool CaseInsensitiveStringCompare(const std::string& a, const std::string& b);

    // MARK: Bits

    // MARK: Bits

    //! Sets a bit on a numerical value
    template <typename T>
    void SetBit(T& value, size_t index) {
        static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
        
        if (index > (sizeof(T) * 8) - 1) {
            return;
        }
        
        value |= static_cast<T>(1) << index;
    }
    
    //! Clears a bit on a numerical value
    template <typename T>
    void ClearBit(T& value, size_t index) {
        static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
        
        if (index > (sizeof(T) * 8 - 1)) {
            return;
        }
        
        value &= ~(static_cast<T>(1) << index);
    }
    
    //! Sets a specific bit in a signed 64-bit integer
    /*!
      \param value The value to set the bit for
      \param index The index of the bit
     */
    int64_t SetBit(int64_t value, uint32_t index);
    
    //! Clears a specific bit in a signed 64-bit integer
    /*!
      \param value The value to clear the bit from
      \param index The index of the bit
     */
    int64_t ClearBit(int64_t value, uint32_t index);
    
    //! Checks a specific bit in a signed 64-bit integer
    /*!
      \parma value The value to check the bit in
      \param index The index of the bit
      \return Whether or not the bit is set
     */
    bool CheckBit(int64_t value, uint32_t index);

    // MARK: Random Number Generation

    //! Generates a random number
    /*!
      \param min The minimum the generate from
      \param max The maximum to generate to
     */
    template <typename T>
    inline T GenerateRandomNumber(std::size_t min, std::size_t max) {
        // Make sure it is a numeric type
        static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");

        if constexpr (std::is_integral_v<T>) {  // constexpr only necessary on first statement
            std::uniform_int_distribution<T> distribution(min, max);
            return distribution(Game::randomEngine);
        }
        else if (std::is_floating_point_v<T>) {
            std::uniform_real_distribution<T> distribution(min, max);
            return distribution(Game::randomEngine);
        }

        return T();
    }
    
	bool ReplaceInString(std::string& str, const std::string& from, const std::string& to);

    std::u16string ReadWString(RakNet::BitStream *inStream);

	std::vector<std::wstring> SplitString(std::wstring& str, wchar_t delimiter);

	std::vector<std::u16string> SplitString(std::u16string& str, char16_t delimiter);

	std::vector<std::string> SplitString(const std::string& str, char delimiter);

    template <typename T>
    T Parse(const char* value);

	template <>
    inline int32_t Parse(const char* value)
	{
        return std::stoi(value);
	}

    template <>
    inline int64_t Parse(const char* value)
    {
        return std::stoll(value);
    }

    template <>
    inline float Parse(const char* value)
    {
        return std::stof(value);
    }

    template <>
    inline double Parse(const char* value)
    {
        return std::stod(value);
    }
	
    template <>
    inline uint32_t Parse(const char* value)
    {
        return std::stoul(value);
    }

    template <>
    inline uint64_t Parse(const char* value)
    {
        return std::stoull(value);
    }
	
    template <typename T>
    bool TryParse(const char* value, T& dst)
    {
        try
        {
            dst = Parse<T>(value);

            return true;
        }
        catch (...)
        {
            return false;
        }
    }

    template <typename T>
    T Parse(const std::string& value)
    {
        return Parse<T>(value.c_str());
    }

    template <typename T>
    bool TryParse(const std::string& value, T& dst)
    {
        return TryParse<T>(value.c_str(), dst);
    }

    template<typename T>
    std::u16string to_u16string(T value)
    {
        return GeneralUtils::ASCIIToUTF16(std::to_string(value));
    }

    // From boost::hash_combine
    template <class T>
    void hash_combine(std::size_t& s, const T& v)
    {
        std::hash<T> h;
        s ^= h(v) + 0x9e3779b9 + (s << 6) + (s >> 2);
    }
}