#ifndef AWS_S3_BUFFER_POOL_H
#define AWS_S3_BUFFER_POOL_H

#include <aws/common/ref_count.h>
#include <aws/io/future.h>
#include <aws/s3/s3.h>

/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

/**
 * Generic memory pool interface.
 * Allows consumers of aws-c-s3 to override how buffer allocation for part buffers is done.
 * Refer to docs/memory_aware_request_execution.md for details on how default implementation works.
 * WARNING: this is currently experimental feature and does not provide API stability guarantees so should be used with
 * caution. At highlevel the flow is as follows:
 * - crt scheduler queues up requests to be prepared
 * - requests being prepared will try to reserve mem (i.e. obtain a ticket) and wait until they get it before proceeding
 * - once mem is reserved requests will proceed with the pipeline
 * - request will acquire buffer from the ticket when needed
 * - ticket is released when request is done with the buffer
 * Note: in some cases pipeline can stall if new buffer cannot be allocated (ex. async writes flow).
 * In this case reserve request will indicate that not granting the ticket can block and buffer pool should try to
 * allocate ticket right away (or wait and call waker when mem is allocated for the case of async writes).
 * Note for custom pool implementations: Scheduler keeps track of all outstanding futures and will error them out when
 * request is paused or cancelled. Its still fine for memory pool implementation to deliver ticket (it will just be
 * released by future right away with no side effects) or just ignore the future if its already in error state.
 */

AWS_PUSH_SANE_WARNING_LEVEL
AWS_EXTERN_C_BEGIN
struct aws_s3_buffer_ticket;

/**
 * aws_future<aws_s3_buffer_ticket*>
 * Buffer ticket future used for reservations.
 */
AWS_FUTURE_T_POINTER_WITH_RELEASE_DECLARATION(aws_future_s3_buffer_ticket, struct aws_s3_buffer_ticket, AWS_S3_API)

/**
 * Meta information about ticket reservation request.
 */
struct aws_s3_buffer_pool_reserve_meta {
    /* client reserving the ticket. accounts for buffer pool being shared between clients. */
    struct aws_s3_client *client;

    /* meta request ticket is being reserved for. */
    struct aws_s3_meta_request *meta_request;

    /* size of the buffer to reserve. */
    size_t size;

    /* whether not granting reservation can result in request pipeline being blocked.
     * Note: blocking is currently a terminal condition and that cannot be recovered from,
     * i.e. meta request will be stuck and not make any process.
     * As such buffer pool should either grant or error out reservation in sync.
     * This scenario currently only occurs in the async_write flows. */
    bool can_block;
};

struct aws_s3_buffer_ticket;

struct aws_s3_buffer_ticket_vtable {
    /**
     * Get buffer associated with the ticket.
     * Note: can be called multiple times and the same buffer should be returned. In some cases ticket might not be
     * claimed at all.
     */
    struct aws_byte_buf (*claim)(struct aws_s3_buffer_ticket *ticket);

    /* Implement below for custom ref count behavior. Alternatively set those to null and init the ref count. */
    struct aws_s3_buffer_ticket *(*acquire)(struct aws_s3_buffer_ticket *ticket);
    struct aws_s3_buffer_ticket *(*release)(struct aws_s3_buffer_ticket *ticket);
};

/**
 * Polymorphic ticket.
 */
struct aws_s3_buffer_ticket {
    struct aws_s3_buffer_ticket_vtable *vtable;
    struct aws_ref_count ref_count;
    void *impl;
};

AWS_S3_API struct aws_byte_buf aws_s3_buffer_ticket_claim(struct aws_s3_buffer_ticket *ticket);

AWS_S3_API struct aws_s3_buffer_ticket *aws_s3_buffer_ticket_acquire(struct aws_s3_buffer_ticket *ticket);
AWS_S3_API struct aws_s3_buffer_ticket *aws_s3_buffer_ticket_release(struct aws_s3_buffer_ticket *ticket);

struct aws_s3_buffer_pool;

struct aws_s3_buffer_pool_vtable {
    /* Reserve a ticket. Returns a future that is granted whenever reservation can be made. */
    struct aws_future_s3_buffer_ticket *(
        *reserve)(struct aws_s3_buffer_pool *pool, struct aws_s3_buffer_pool_reserve_meta meta);

    /**
     * Trim the pool. This is mostly a suggestion, which pool can decide to ignore. Triggered by CRT when
     * client has been idle for some time.
     **/
    void (*trim)(struct aws_s3_buffer_pool *pool);

    /**
     * Optimize the buffer pool for allocations of a specific size.
     * Creates a separate list of blocks dedicated to this size for better memory efficiency.
     */
    int (*add_special_size)(struct aws_s3_buffer_pool *pool, size_t buffer_size);

    /**
     * Release all special-sized blocks from the buffer pool.
     * This frees all memory allocated for the special size optimization.
     */
    void (*release_special_size)(struct aws_s3_buffer_pool *pool, size_t buffer_size);

    /**
     * Align a range size to the buffer pool's allocation strategy.
     * Returns the optimal aligned size based on the buffer pool's configuration.
     */
    uint64_t (*derive_aligned_buffer_size)(struct aws_s3_buffer_pool *pool, uint64_t size);

    /* Implement below for custom ref count behavior. Alternatively set those to null and init the ref count. */
    struct aws_s3_buffer_pool *(*acquire)(struct aws_s3_buffer_pool *pool);
    struct aws_s3_buffer_pool *(*release)(struct aws_s3_buffer_pool *pool);
};

/**
 * Polymorphic buffer pool.
 */
struct aws_s3_buffer_pool {
    struct aws_s3_buffer_pool_vtable *vtable;
    struct aws_ref_count ref_count;
    void *impl;
};

AWS_S3_API struct aws_future_s3_buffer_ticket *aws_s3_buffer_pool_reserve(
    struct aws_s3_buffer_pool *buffer_pool,
    struct aws_s3_buffer_pool_reserve_meta meta);
AWS_S3_API void aws_s3_buffer_pool_trim(struct aws_s3_buffer_pool *buffer_pool);

AWS_S3_API struct aws_s3_buffer_pool *aws_s3_buffer_pool_acquire(struct aws_s3_buffer_pool *buffer_pool);
AWS_S3_API struct aws_s3_buffer_pool *aws_s3_buffer_pool_release(struct aws_s3_buffer_pool *buffer_pool);

/**
 * Buffer pool configuration options.
 */
struct aws_s3_buffer_pool_config {
    struct aws_s3_client *client; /* Client creating the pool. */
    size_t part_size;             /* Default part size of the client. */
    size_t max_part_size;         /* Max part size configured on the client. */
    size_t memory_limit;          /* Memory limit set on the client. */
};

/**
 * Factory to construct the pool for the given config. Passes along buffer related info configured on the client, which
 * factory may ignore when considering how to construct pool.
 * This implementation should fail if pool cannot be constructed for some reason (ex. if config params cannot be met),
 * by logging failure reason, returning null and raising aws_error.
 */
typedef struct aws_s3_buffer_pool *(aws_s3_buffer_pool_factory_fn)(struct aws_allocator *allocator,
                                                                   struct aws_s3_buffer_pool_config config,
                                                                   void *user_data);

/**
 * Optimize the buffer pool for allocations of a specific size.
 * Creates a separate list of blocks dedicated to this size for better memory efficiency.
 * Allocations of exactly this size will use these special blocks instead of the regular primary/secondary storage.
 *
 * @param buffer_pool The buffer pool to optimize
 * @param buffer_size The size to optimize for (must be > 0)
 * @return AWS_OP_SUCCESS on success, AWS_OP_ERR on failure
 */
AWS_S3_API
int aws_s3_buffer_pool_add_special_size(struct aws_s3_buffer_pool *buffer_pool, size_t buffer_size);

/**
 * Release the special-sized blocks from the buffer pool.
 * Should be called when done with the special-sized allocations.
 *
 * @param buffer_pool The buffer pool
 * @param buffer_size The special size to release blocks for
 */
AWS_S3_API
void aws_s3_buffer_pool_release_special_size(struct aws_s3_buffer_pool *buffer_pool, size_t buffer_size);

/**
 * Align a range size to the buffer pool's allocation strategy.
 * This function determines the optimal aligned size based on the buffer pool's configuration.
 * For sizes within the primary allocation range, it aligns to chunk boundaries.
 * For larger sizes that go to secondary storage, it returns the size as-is.
 *
 * @param buffer_pool The buffer pool to use for alignment (can be NULL, in which case size is returned unchanged)
 * @param size The size to align
 * @return The aligned size that's optimal for the buffer pool's allocation strategy
 */
AWS_S3_API
uint64_t aws_s3_buffer_pool_derive_aligned_buffer_size(struct aws_s3_buffer_pool *buffer_pool, uint64_t size);

AWS_EXTERN_C_END
AWS_POP_SANE_WARNING_LEVEL

#endif /* AWS_S3_BUFFER_POOL_H */
