#pragma once

#include <map>
#include <functional>
#include <memory>
#include <string>

namespace hack::utils
{
  template<typename Result, typename... Args>
  auto func_memory(Result (*f)(Args...))
  {
    std::map<std::tuple<Args...>, Result> cache;

    return [f, cache](Args... args) mutable -> Result
    {
      const auto key = std::make_tuple(args...);
      const auto cached = cache.find(key);

      if(cached == cache.end())
      {
        auto result = f(args...);
        cache[key] = result;
        return result;
      }
      return cached->second;
    };
  }

  template<typename T, typename... Args>
  auto func_concat(T t, Args... args)
  {
    if constexpr (sizeof...(args) > 0)
    {
      return [=](auto... params)
      {
        return t(func_concat(args...)(params...));
      };
    }
    else
    {
      return [=](auto... params)
      {
        return t(params...);
      };
    }
  }

  inline std::string unix_cmd(const std::string& cmd)
  {
    std::string result;
    std::array<char, 1024> buffer;
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);

    if (!pipe) 
      throw std::runtime_error("bash cmd failed");
    
    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) 
    {
      result += buffer.data();
    }

    return result;
  }

  template<typename T, T value = 0>
  struct counter 
  {
    inline static T id = value;
  };

  // this function can will use
  // as switch wiht string
  // switch(case_int("key"))
  // case case_int("test"): hack::log()("wow!");
  // case case_int("no_test"): hack::log()("wow!");
  inline constexpr std::uint32_t case_int(const char* str, std::uint32_t hash = 2166136261UL) 
  {
    return *str ? case_int(str + 1, (hash ^ *str) * 16777619ULL) : hash;
  }
}
