#pragma once

/*
	GameEngine and Editor
	by John Ryland
	Copyright (c) 2023
*/

////////////////////////////////////////////////////////////////////////////////////
//	Utilities

#include <string>
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <iostream>
#include <fstream>
#include <algorithm>

namespace Utilities {

struct SizedArray
{
    const uint8_t* data;
    size_t         size;
};

template <size_t N>
SizedArray make_sized_array(const uint8_t (&array)[N])
{
    return SizedArray{array, N};
}

template <typename ...T>
void fail(const char* fmt, T... args)
{
    printf(fmt, args...);
    exit(-1);
}

template <typename T>
T clamp(T val, T min, T max)
{
    return std::min(std::max(val, min), max);
}

// https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path
template<class T>
T base_name(T const & path, T const & delims = "/\\")
{
    return path.substr(path.find_last_of(delims) + 1);
}

template<class T>
T file_extension(T const & filename)
{
    typename T::size_type const p(filename.find_last_of('.'));
    return p > 0 && p != T::npos ? filename.substr(p) : T();
}

template<class T>
T remove_extension(T const & filename)
{
    typename T::size_type const p(filename.find_last_of('.'));
    return p > 0 && p != T::npos ? filename.substr(0, p) : filename;
}

template<class T>
uint32_t to_uint32(T const & str)
{
    return std::stoul(str, nullptr, 0);
}

template<class T>
int32_t to_int32(T const & str)
{
    return std::stol(str, nullptr, 0);
}

template<class T>
uint64_t to_uint64(T const & str)
{
    return std::stoull(str, nullptr, 0);
}

template<class T>
int64_t to_int64(T const & str)
{
    return std::stoll(str, nullptr, 0);
}

template<class T>
float to_float(T const & str)
{
    return std::stof(str, nullptr);
}

template<class T>
double to_double(T const & str)
{
    return std::stod(str, nullptr);
}

// N is size without the NUL terminator
template <size_t N>
class FixedString
{
public:
    FixedString()
        : m_len(0)
    {
    }

    FixedString(const char (&array)[N+1])
        : m_len(N)
    {
        ::memcpy(m_array, array, N);
        m_array[N] = 0;
    }

    size_t size()
    {
        assert(m_len <= N);
        return m_len; // return the size of the string without NUL terminator
    }

    const char* c_str()
    {
        return m_array;
    }

    template <size_t M>
    FixedString<N+M-1> operator+(const char (&array)[M])
    {
        FixedString<N+M-1> ret;
        ::memcpy(ret.m_array, m_array, m_len);
        ::memcpy(ret.m_array + m_len, array, M-1);
        ret.m_len = m_len + M - 1;
        ret.m_array[ret.m_len] = 0;
        return ret;
    }

    template <size_t M>
    FixedString<N+M> operator+(FixedString<M> str)
    {
        FixedString<N+M> ret;
        ::memcpy(ret.m_array, m_array, m_len);
        ::memcpy(ret.m_array + m_len, str.m_array, M);
        ret.m_len = m_len + M;
        ret.m_array[N+M] = 0;
        return ret;
    }

private:
    template <size_t M>
    friend class FixedString;

    size_t  m_len;
    char    m_array[N+1]; // +1 for space to add a NUL terminator
};

template <size_t N>
FixedString<N-1> make_fixed_string(const char (&array)[N])
{
    return FixedString<N-1>(array);
}

template <typename K, typename T>
void GetKeys(std::unordered_set<K>& outSet, const std::unordered_map<K,T>& inMap)
{
    outSet.clear();
    for (const auto& pair : inMap)
        outSet.insert(pair.first);
}

template <typename T>
void Union(std::unordered_set<T> &outSet,
        const std::unordered_set<T> &inSet1,
        const std::unordered_set<T> &inSet2)
{
    outSet.clear();
    outSet.insert(inSet1.begin(), inSet1.end());
    outSet.insert(inSet2.begin(), inSet2.end());
}

template <typename T>
void Intersection(std::unordered_set<T>& outSet, const std::unordered_set<T> &inSet1, const std::unordered_set<T> &inSet2)
{
    if (inSet2.size() < inSet1.size())
        return Intersection(outSet, inSet2, inSet1);
    outSet.clear();
    for (auto& item : inSet1)
        if (inSet2.find(item) != inSet2.end())
            outSet.insert(item);
}

template <typename T>
void Difference(std::unordered_set<T>& outSet, const std::unordered_set<T> &inSet1, const std::unordered_set<T> &inSet2)
{
    outSet.clear();
    for (auto& item : inSet1)
        if (inSet2.find(item) == inSet2.end())
            outSet.insert(item);
}

static inline
std::vector<uint8_t> ReadFile(const std::string& filename)
{
    std::vector<uint8_t> buffer;
    std::ifstream file(filename, std::ios::ate | std::ios::binary);
    if (file.is_open())
    {
        buffer.resize((size_t)file.tellg());
        file.seekg(0);
        file.read((char*)buffer.data(), buffer.size());
        file.close();
    }
    else
    {
        fprintf(stderr, "failed to open file!");
    }
    return buffer;
}

} // namespace Utilities
