#pragma once
#include "./std_headers.h"
#include <atomic>

struct ChunkyAlloc {
  ChunkyAlloc();
  ~ChunkyAlloc();
  ChunkyAlloc(ChunkyAlloc const &other) = delete;
  ChunkyAlloc(ChunkyAlloc &&other);
  ChunkyAlloc & operator= (ChunkyAlloc &&other);

  U8 *curChunkPtr{nullptr};
  size_t curChunkAvail{0};
  size_t totalChunkAlloc{0};
  vector<void *> chunks;
  vector<shared_ptr<void>> objects;

  bool empty() const { return chunks.empty() && objects.empty(); }
  size_t totalAlloc() const;
  void *allocFull(size_t align, size_t s);

  inline void * operator() (size_t align, size_t s)
  {
    auto misalign = (size_t)curChunkPtr & (align - 1);
    if (misalign) {
      curChunkPtr += align - misalign;
      curChunkAvail -= align - misalign;
    }
    if (s <= curChunkAvail) {
      auto ret = curChunkPtr;
      curChunkPtr += s;
      curChunkAvail -= s;
      return (void *)ret;
    }
    return allocFull(align, s);
  }

  inline void * operator() (size_t s)
  {
    return (*this)(sizeof(void *), s);
  }

  template<typename T, typename ...Args>
  T *mk(Args&& ...args)
  {
    auto p = (*this)(alignof(T), sizeof(T));
    return new((T *)p) T(std::forward<Args>(args)...);
  }

  template<typename T, typename ...Args>
  T *mkExtra(size_t extra, Args&& ...args)
  {
    auto p = (*this)(alignof(T), sizeof(T) + extra);
    return new((T *)p) T(std::forward<Args>(args)...);
  }

  template<typename T, typename ...Args>
  shared_ptr<T> mkObject(Args&& ...args)
  {
    auto ret = make_shared<T>(std::forward<Args>(args)...);
    objects.push_back(ret);
    return ret;
  }

  void addObject(shared_ptr<void> it)
  {
    objects.emplace_back(std::move(it));
  }

  inline char *strdup(char const *s)
  {
    auto len = strlen(s);
    auto ret = (char *)(*this)(len + 1);
    memcpy(ret, s, len + 1);
    return (char *)ret;
  }

  inline char *strdup(string const &s)
  {
    auto ret = (char *)(*this)(s.size() + 1);
    memcpy(ret, s.data(), s.size() + 1);
    ret[s.size()] = 0;
    return (char *)ret;
  }

  inline string_view strdupview(string const &s)
  {
    auto ret = (char *)(*this)(s.size() + 1);
    memcpy(ret, s.data(), s.size() + 1);
    ret[s.size()] = 0;
    return string_view(ret, s.size());
  }

  inline char *strdup(string_view s)
  {
    auto ret = (char *)(*this)(s.size() + 1);
    memcpy(ret, s.data(), s.size());
    ret[s.size()] = 0;
    return (char *)ret;
  }

  inline string_view strdupview(string_view s)
  {
    auto ret = (char *)(*this)(s.size() + 1);
    memcpy(ret, s.data(), s.size() + 1);
    ret[s.size()] = 0;
    return string_view(ret, s.size());
  }

  inline string_view strconcatview(string_view a, string_view b)
  {
    auto ret = (char *)(*this)(a.size() + b.size() + 1);
    memcpy(ret, a.data(), a.size());
    memcpy(ret + a.size(), b.data(), b.size());
    ret[a.size() + b.size()] = 0;
    return string_view(ret, a.size() + b.size());
  }

};

struct AllocTracker {
  AllocTracker(char const *_name);
  ~AllocTracker();
  AllocTracker(AllocTracker const &other) = delete;
  AllocTracker(AllocTracker &&other) = delete;
  AllocTracker & operator= (AllocTracker &&other) = delete;

  static string fmtStats();
  static void printStats(ostream &s = cerr);

  void a(int64_t i = 1)
  {
    nAlloc += i;
    nTotalAlloc += i;
  }

  void f(int64_t i = 1)
  {
    auto newTotal = (nAlloc -= i);
    if (newTotal < 0) barf(newTotal);
  }

  void barf(int64_t newTotal);

  char const *name;
  atomic_int64_t nAlloc{0}, nTotalAlloc{0};
};
