typedef enum { arena__alloc, arena__reset, arena__save, arena__restore, arena__release } arena__action;

typedef closure(void*, arena__action, uword, uword, void*, const char*, sword) Arena;

#define arena_new(A, T)      cast(T*, A(arena__alloc, sizeof(T), ualign_of(T), 0, __FILE__, __LINE__))
#define arena_make(A, C, T)  cast(T*, A(arena__alloc, sizeof(T) * (C), ualign_of(T), 0, __FILE__, __LINE__))
#define arena_reset(A)       A(arena__reset, 0, 0, 0, __FILE__, __LINE__)
#define arena_release(A)     A(arena__release, 0, 0, 0, __FILE__, __LINE__)
#define arena_save(A, SP)    A(arena__save, 0, 0, (SP), __FILE__, __LINE__)
#define arena_restore(A, SP) A(arena__restore, 0, 0, (SP), __FILE__, __LINE__)

// tiny macro to make arenas easier to declare
#define $(a1, a2, a3, a4, a5, a6) ^void* (arena__action a1, uword a2, uword a3, void* a4, const char* a5, sword a6)

static Arena
Static(u8* data, uword count) {
   memset(data, 0, count);

   capture uword offset = 0;
   return persist($(action, size, align, savepoint, _2, _3) {
      switch (action) {
         default: {
         } break;

         case arena__alloc: {
            offset = (offset + align - 1) & ~(align - 1);
            if (offset + size > count)
               { return 0; }

            void* ptr = data + offset;
            offset += size;

            return ptr;
         } break;

         case arena__reset: {
            offset = 0;
         } break;

         case arena__save: {
            assert(savepoint != NULL && "Static: savepoint was null");
            *cast(uword*, savepoint) = offset;
         } break;

         case arena__restore: {
            assert(savepoint != NULL && "Static: savepoint was null");
            offset = *cast(uword*, savepoint);
         } break;
      }

      return 0;
   });
}

static Arena
Linear(uword max_memory) {
   void* ptr = malloc(max_memory);
   Arena backing = Static(ptr, max_memory);
   return persist($(action, size, align, savepoint, file, line) {
      if (action == arena__release) {
         free(ptr);
         return 0;
      }

      return backing(action, size, align, savepoint, file, line);
   });
}

static Arena
Logger(Arena arena) {
   return persist($(action, size, align, savepoint, file, line) {
      char* action_str = "unknown";
      switch (action) {
         case arena__alloc:   action_str = "alloc";   break;
         case arena__reset:   action_str = "reset";   break;
         case arena__release: action_str = "release"; break;
         case arena__save:    action_str = "save";    break;
         case arena__restore: action_str = "restore"; break;
      }

      printf("[ARENA] %s:%d: %s (%d, %d)\n", file, cast(int, line), action_str, cast(int, size), cast(int, align));
      return arena(action, size, align, savepoint, file, line);
   });
}

static Arena
Region(Arena arena) {
   capture uword savepoint = 0;
   arena_save(arena, &savepoint);

   return persist($(action, size, align, _, file, line) {
      if (action == arena__reset || action == arena__restore) {
         return arena(arena__restore, size, align, &savepoint, file, line);
      }

      return arena(action, size, align, &savepoint, file, line);
   });
}

#undef $
