#ifndef AWS_IO_PIPE_H
#define AWS_IO_PIPE_H

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

#include <aws/common/byte_buf.h>
#include <aws/io/io.h>

struct aws_event_loop;

struct aws_pipe_read_end {
    void *impl_data;
};

struct aws_pipe_write_end {
    void *impl_data;
};

/**
 * Callback for when the pipe is readable (edge-triggered), or an error has occurred.
 * Afer subscribing, the callback is invoked when the pipe has data to read, or the pipe has an error.
 * The readable callback is invoked again any time the user reads all data, and then more data arrives.
 * Note that it will not be invoked again if the pipe still has unread data when more data arrives.
 * `error_code` of AWS_ERROR_SUCCESS indicates a readable event, and otherwise contains the value of the error.
 * `user_data` corresponds to the `user_data` passed into aws_pipe_subscribe_to_read_events().
 * This callback is always invoked on the read-end's event-loop thread.
 */
typedef void(aws_pipe_on_readable_fn)(struct aws_pipe_read_end *read_end, int error_code, void *user_data);

/**
 * Callback for when the asynchronous aws_pipe_write() operation has either completed or failed.
 * `write_end` will be NULL if this callback is invoked after the the write-end has been cleaned up,
 * this does not necessarily mean that the write operation failed.
 * `error_code` will be AWS_ERROR_SUCCESS if all data was written, or a code corresponding to the error.
 * `src_buffer` corresponds to the buffer passed into aws_pipe_write()
 * `user_data` corresponds to the `user_data` passed into aws_pipe_write().
 * This callback is always invoked on the write-end's event-loop thread.
 */
typedef void(aws_pipe_on_write_completed_fn)(
    struct aws_pipe_write_end *write_end,
    int error_code,
    struct aws_byte_cursor src_buffer,
    void *user_data);

AWS_EXTERN_C_BEGIN

/**
 * Opens an OS specific bidirectional pipe.
 * The read direction is stored in read_end. Write direction is stored in write_end.
 * Each end must be connected to an event-loop, and further calls to each end must happen on that event-loop's thread.
 */
AWS_IO_API
int aws_pipe_init(
    struct aws_pipe_read_end *read_end,
    struct aws_event_loop *read_end_event_loop,
    struct aws_pipe_write_end *write_end,
    struct aws_event_loop *write_end_event_loop,
    struct aws_allocator *allocator);

/**
 * Clean up the read-end of the pipe.
 * This must be called on the thread of the connected event-loop.
 */
AWS_IO_API
int aws_pipe_clean_up_read_end(struct aws_pipe_read_end *read_end);

/**
 * Clean up the write-end of the pipe.
 * This must be called on the thread of the connected event-loop.
 */
AWS_IO_API
int aws_pipe_clean_up_write_end(struct aws_pipe_write_end *write_end);

/**
 * Get the event-loop connected to the read-end of the pipe.
 * This may be called on any thread.
 */
AWS_IO_API
struct aws_event_loop *aws_pipe_get_read_end_event_loop(const struct aws_pipe_read_end *read_end);

/**
 * Get the event-loop connected to the write-end of the pipe.
 * This may be called on any thread.
 */
AWS_IO_API
struct aws_event_loop *aws_pipe_get_write_end_event_loop(const struct aws_pipe_write_end *write_end);

/**
 * Initiates an asynchrous write from the source buffer to the pipe.
 * The data referenced by `src_buffer` must remain in memory until the operation completes.
 * `on_complete` is called on the event-loop thread when the operation has either completed or failed.
 * The callback's pipe argument will be NULL if the callback is invoked after the pipe has been cleaned up.
 * This must be called on the thread of the connected event-loop.
 */
AWS_IO_API
int aws_pipe_write(
    struct aws_pipe_write_end *write_end,
    struct aws_byte_cursor src_buffer,
    aws_pipe_on_write_completed_fn *on_completed,
    void *user_data);

/**
 * Read data from the pipe into the destination buffer.
 * Attempts to read enough to fill all remaining space in the buffer, from `dst_buffer->len` to `dst_buffer->capacity`.
 * `dst_buffer->len` is updated to reflect the buffer's new length.
 * `num_bytes_read` (optional) is set to the total number of bytes read.
 * This function never blocks. If no bytes could be read without blocking, then AWS_OP_ERR is returned and
 * aws_last_error() code will be AWS_IO_READ_WOULD_BLOCK.
 * This must be called on the thread of the connected event-loop.
 */
AWS_IO_API
int aws_pipe_read(struct aws_pipe_read_end *read_end, struct aws_byte_buf *dst_buffer, size_t *num_bytes_read);

/**
 * Subscribe to be notified when the pipe becomes readable (edge-triggered), or an error occurs.
 * `on_readable` is invoked on the event-loop's thread when the pipe has data to read, or the pipe has an error.
 * `on_readable` is invoked again any time the user reads all data, and then more data arrives.
 * Note that it will not be invoked again if the pipe still has unread data when more data arrives.
 * This must be called on the thread of the connected event-loop.
 */
AWS_IO_API
int aws_pipe_subscribe_to_readable_events(
    struct aws_pipe_read_end *read_end,
    aws_pipe_on_readable_fn *on_readable,
    void *user_data);

/**
 * Stop receiving notifications about events on the read-end of the pipe.
 * This must be called on the thread of the connected event-loop.
 */
AWS_IO_API
int aws_pipe_unsubscribe_from_readable_events(struct aws_pipe_read_end *read_end);

#if defined(_WIN32)
/**
 * Generate a unique pipe name.
 * The suggested dst_size is 256.
 */
AWS_IO_API
int aws_pipe_get_unique_name(char *dst, size_t dst_size);
#endif

AWS_EXTERN_C_END

#endif /* AWS_IO_PIPE_H */
