// -*- mode: c++ -*-
#ifndef __IMP_PRELUDE__56107413
#define __IMP_PRELUDE__56107413

#include <array>
#include <chrono>
#include <memory>
#include <string>
#include <cstdint>
#include <fmt/format.h>
#include <fmt/ostream.h>

// Include standard utilities
#include "util/Types"
#include "util/ArrayView"
#include "util/StringView"
#include "util/Guard"

#define STUB(msg) fmt::print("STUB {}: {} ({}:{})\n", __FUNCTION__, msg, __LINE__, __FILE__)

namespace fmt {
  inline void println(CStringRef fmt, ArgList args)
  {
      print(fmt, args);
      print("\n");
  }

  inline void println(std::FILE* fh, CStringRef fmt, ArgList args)
  {
      print(fh, fmt, args);
      print(fh, "\n");
  }

  inline void fatal(CStringRef fmt, ArgList args)
  {
      println(stderr, fmt, args);
      std::terminate();
  }

  FMT_VARIADIC(void, println, CStringRef)
  FMT_VARIADIC(void, println, std::FILE*, CStringRef)
  FMT_VARIADIC(void, fatal, CStringRef)

  template <class T>
  inline void print(const T &x)
  {
      print("{}", x);
  }

  template <class T>
  inline void println(const T &x)
  {
      println("{}", x);
  }
}

namespace imp {
  using String = std::string;
  template <class T, class Deleter = std::default_delete<T>>
  using UniquePtr = std::unique_ptr<T, Deleter>;
  template <class T>
  using SharedPtr = std::shared_ptr<T>;
  template <class T>
  using WeakPtr = std::weak_ptr<T>;
  template <class T>
  using Vector = std::vector<T>;
  template <class T>
  using InitList = std::initializer_list<T>;
  template <class T, size_t N>
  using Array = std::array<T, N>;

  template <class T, class... Args>
  UniquePtr<T> unique(Args&&... args)
  {
      return std::make_unique<T>(std::forward<Args>(args)...);
  }

  template <class T, class... Args>
  SharedPtr<T> shared(Args&&... args)
  {
      return std::make_shared<T>(std::forward<Args>(args)...);
  }

  template <class T>
  decltype(auto) move(T&& x)
  {
      return std::move(std::forward<T>(x));
  }

  // Initialisation for various parts of the engine
  void init_image();
  void init_properties();

  inline std::ostream& operator<<(std::ostream& s, StringView sv)
  {
      return s.write(sv.data(), sv.length());
  }
}

template <class T, class Deleter>
inline std::ostream& operator<<(std::ostream& s, const std::unique_ptr<T, Deleter>& ptr)
{
    return s << static_cast<void*>(ptr.get());
}

template <class T>
inline std::ostream& operator<<(std::ostream& s, const std::shared_ptr<T>& ptr)
{
    return s << static_cast<void*>(ptr.get());
}

template <class T>
inline std::ostream& operator<<(std::ostream& s, const std::weak_ptr<T>& ptr)
{
    if (ptr.expired()) {
        return s << "(expired)";
    } else {
        auto shared = ptr.lock();
        return s << shared;
    }
}

#ifndef IMP_DONT_POLLUTE_GLOBAL_NAMESPACE
using namespace std::string_literals;
using namespace std::chrono_literals;
using namespace imp;

// clang complains about ambiguity with stuff like sprintf and fprintf, since there exist functions with those names
// in both the std and fmt namespaces, so we only forward the few functions we actually need, instead of
// using namespace imp;

template <class... Args>
inline auto print(Args&&... args)
{ return fmt::print(std::forward<Args>(args)...); }

template <class... Args>
inline auto println(Args&&... args)
{ return fmt::println(std::forward<Args>(args)...); }

template <class... Args>
inline auto format(Args&&... args)
{ return fmt::format(std::forward<Args>(args)...); }

template <class... Args>
inline auto fatal(Args&&... args)
{ return fmt::fatal(std::forward<Args>(args)...); }

#endif

#endif //__IMP_PRELUDE__56107413
