#if defined(SNMALLOC_PASS_THROUGH) || true
/*
 * This test does not make sense with malloc pass-through, skip it.
 */
int main()
{
  return 0;
}
#else
// The decommit strategy is currently a global policy and not per-allocator and
// so we need to tell Windows not to use the lazy strategy for this test.
#  define USE_DECOMMIT_STRATEGY DecommitSuper
#  include <snmalloc.h>

using namespace snmalloc;

namespace
{
  /**
   * Helper for Alloc that never needs lazy initialisation.
   *
   * CapPtr-vs-MSVC triggering; xref CapPtr's constructor
   */
  void no_op_register_clean_up()
  {
    SNMALLOC_CHECK(0 && "Should never be called!");
  }
  /**
   * Sandbox class.  Allocates a memory region and an allocator that can
   * allocate into this from the outside.
   */
  struct Sandbox
  {
    using NoOpPal = PALNoAlloc<DefaultPal>;

    struct ArenaMap
    {
      /**
       * A pointer with authority to the entire sandbox region
       */
      CapPtr<void, CBArena> arena_root;

      /**
       * Amplify using arena_root; that is, exclusively within the sandbox.
       */
      template<typename T = void, typename U, capptr_bounds B>
      SNMALLOC_FAST_PATH CapPtr<T, CBArena> capptr_amplify(CapPtr<U, B> r)
      {
        return Aal::capptr_rebound<T>(arena_root, r);
      }

      /*
       * This class does not implement register_root; there should be no
       * attempts to call that function.
       */
    };

    /**
     * The MemoryProvider for sandbox-memory-backed Allocs, both inside and
     * outside the sandbox proper: no memory allocation operations and
     * amplification confined to sandbox memory.
     */
    using NoOpMemoryProvider = ChunkAllocator<NoOpPal, ArenaMap>;

    /**
     * Type for the allocator that lives outside of the sandbox and allocates
     * sandbox-owned memory.
     * This Allocator, by virtue of having its amplification confined to
     * the sandbox, can be used to free only allocations made from sandbox
     * memory.  It (insecurely) routes messages to in-sandbox snmallocs,
     * though, so it can free any sandbox-backed snmalloc allocation.
     */
    using ExternalCoreAlloc =
      Allocator<NoOpMemoryProvider, SNMALLOC_DEFAULT_CHUNKMAP, false>;

    using ExternalAlloc =
      LocalAllocator<ExternalCoreAlloc, no_op_register_clean_up>;

    /**
     * Proxy class that forwards requests for large allocations to the real
     * memory provider.
     *
     * In a real implementation, these would be cross-domain calls with the
     * callee verifying the arguments.
     */
    struct MemoryProviderProxy
    {
      /**
       * The PAL that allocators using this memory provider should use.
       */
      typedef NoOpPal Pal;
      /**
       * The pointer to the real state.  In a real implementation there would
       * likely be only one of these inside any given sandbox and so this would
       * not have to be per-instance state.
       */
      NoOpMemoryProvider* real_state;

      /**
       * Pop an element from the large stack for the specified size class,
       * proxies to the real implementation.
       *
       * This method must be implemented for `LargeAlloc` to work.
       */
      CapPtr<Largeslab, CBChunk> pop_large_stack(size_t large_class)
      {
        return real_state->pop_large_stack(large_class);
      };

      /**
       * Push an element to the large stack for the specified size class,
       * proxies to the real implementation.
       *
       * This method must be implemented for `LargeAlloc` to work.
       */
      void push_large_stack(CapPtr<Largeslab, CBChunk> slab, size_t large_class)
      {
        real_state->push_large_stack(slab, large_class);
      }

      /**
       * Reserve (and optionally commit) memory for a large sizeclass, proxies
       * to the real implementation.
       *
       * This method must be implemented for `LargeAlloc` to work.
       */
      template<bool committed>
      CapPtr<Largeslab, CBChunk> reserve(size_t large_class) noexcept
      {
        return real_state->template reserve<committed>(large_class);
      }

      /**
       * Amplify by appealing to the real_state, which has our sandbox
       * ArenaMap implementation.
       */
      template<typename T = void, typename U, capptr_bounds B>
      SNMALLOC_FAST_PATH CapPtr<T, CBArena> capptr_amplify(CapPtr<U, B> r)
      {
        return real_state->template capptr_amplify<T>(r);
      }
    };

    /**
     * Type for the allocator that exists inside the sandbox.
     *
     * Note that a real version of this would not have access to the shared
     * pagemap and would not be used outside of the sandbox.
     */
    using InternalCoreAlloc = Allocator<MemoryProviderProxy>;
    using InternalAlloc =
      LocalAllocator<InternalCoreAlloc, no_op_register_clean_up>;

    /**
     * The start of the sandbox memory region.
     */
    void* start;

    /**
     * The end of the sandbox memory region
     */
    void* top;

    /**
     * State allocated in the sandbox that is shared between the inside and
     * outside.
     */
    struct SharedState
    {
      /**
       * The message queue for the allocator that lives outside of the
       * sandbox but allocates memory inside.
       */
      struct RemoteAllocator queue;
    } * shared_state;

    /**
     * The memory provider for this sandbox.
     */
    NoOpMemoryProvider state;

    /**
     * The allocator for callers outside the sandbox to allocate memory inside.
     */
    ExternalAlloc alloc;

    /**
     * An allocator for callers inside the sandbox to allocate memory.
     */
    InternalAlloc* internal_alloc;

    /**
     * Constructor.  Takes the size of the sandbox as the argument.
     */
    Sandbox(size_t sb_size)
    : start(alloc_sandbox_heap(sb_size)),
      top(pointer_offset(start, sb_size)),
      shared_state(new (start) SharedState()),
      state(
        pointer_offset(CapPtr<void, CBChunk>(start), sizeof(SharedState)),
        sb_size - sizeof(SharedState)),
      alloc(state, SNMALLOC_DEFAULT_CHUNKMAP(), &shared_state->queue)
    {
      // Register the sandbox memory with the sandbox arenamap
      state.arenamap().arena_root = CapPtr<void, CBArena>(start);

      auto* state_proxy = static_cast<MemoryProviderProxy*>(
        alloc.alloc(sizeof(MemoryProviderProxy)));
      state_proxy->real_state = &state;
      // In real code, allocators should never be constructed like this, they
      // should always come from an alloc pool.  This is just to test that both
      // kinds of allocator can be created.
      internal_alloc =
        new (alloc.alloc(sizeof(InternalAlloc))) InternalAlloc(*state_proxy);
    }

    Sandbox() = delete;

    /**
     * Predicate function for querying whether an object is entirely within the
     * sandbox.
     */
    bool is_in_sandbox(void* ptr, size_t sz)
    {
      return (ptr >= start) && (pointer_offset(ptr, sz) < top);
    }

    /**
     * Predicate function for querying whether an object is entirely within the
     * region of the sandbox allocated for its heap.
     */
    bool is_in_sandbox_heap(void* ptr, size_t sz)
    {
      return (
        ptr >= pointer_offset(start, sizeof(SharedState)) &&
        (pointer_offset(ptr, sz) < top));
    }

  private:
    template<typename PAL = DefaultPal>
    void* alloc_sandbox_heap(size_t sb_size)
    {
      // Use the outside-sandbox snmalloc to allocate memory, rather than using
      // the PAL directly, so that our out-of-sandbox can amplify sandbox
      // pointers
      return ThreadAlloc::get().alloc(sb_size);
    }
  };
}

int main()
{
  static const size_t sb_size = 128 * 1024 * 1024;

  // Check that we can create two sandboxes
  Sandbox sb1(sb_size);
  Sandbox sb2(sb_size);

  auto check = [](Sandbox& sb, auto& alloc, size_t sz) {
    void* ptr = alloc.alloc(sz);
    SNMALLOC_CHECK(sb.is_in_sandbox_heap(ptr, sz));
    ThreadAlloc::get().dealloc(ptr);
  };
  auto check_with_sb = [&](Sandbox& sb) {
    // Check with a range of sizes
    check(sb, sb.alloc, 32);
    check(sb, *sb.internal_alloc, 32);
    check(sb, sb.alloc, 240);
    check(sb, *sb.internal_alloc, 240);
    check(sb, sb.alloc, 513);
    check(sb, *sb.internal_alloc, 513);
    check(sb, sb.alloc, 10240);
    check(sb, *sb.internal_alloc, 10240);
  };
  check_with_sb(sb1);
  check_with_sb(sb2);

  return 0;
}
#endif
