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

#include <aws/io/channel.h>
#include <aws/io/io.h>

AWS_PUSH_SANE_WARNING_LEVEL

enum aws_socket_domain {
    AWS_SOCKET_IPV4,
    AWS_SOCKET_IPV6,
    /* Unix domain sockets (or at least something like them) */
    AWS_SOCKET_LOCAL,
    /* VSOCK used in inter-VM communication */
    AWS_SOCKET_VSOCK,
};

enum aws_socket_type {
    /* A streaming socket sends reliable messages over a two-way connection.
     * This means TCP when used with IPV4/6, and Unix domain sockets, when used with
     * AWS_SOCKET_LOCAL*/
    AWS_SOCKET_STREAM,
    /* A datagram socket is connectionless and sends unreliable messages.
     * This means UDP when used with IPV4/6.
     * LOCAL and VSOCK sockets are not compatible with DGRAM.*/
    AWS_SOCKET_DGRAM,
};

#define AWS_NETWORK_INTERFACE_NAME_MAX 16

struct aws_socket_options {
    enum aws_socket_type type;
    enum aws_socket_domain domain;
    uint32_t connect_timeout_ms;
    /* Keepalive properties are TCP only.
     * Set keepalive true to periodically transmit messages for detecting a disconnected peer.
     * If interval or timeout are zero, then default values are used. */
    uint16_t keep_alive_interval_sec;
    uint16_t keep_alive_timeout_sec;
    /* If set, sets the number of keep alive probes allowed to fail before the connection is considered
     * lost. If zero OS defaults are used. On Windows, this option is meaningless until Windows 10 1703.*/
    uint16_t keep_alive_max_failed_probes;
    bool keepalive;

    /**
     * THIS IS AN EXPERIMENTAL AND UNSTABLE API
     * (Optional)
     * This property is used to bind the socket to a particular network interface by name, such as eth0 and ens32.
     * If this is empty, the socket will not be bound to any interface and will use OS defaults. If the provided name
     * is invalid, `aws_socket_init()` will error out with AWS_IO_SOCKET_INVALID_OPTIONS. This option is only
     * supported on Linux, macOS, and platforms that have either SO_BINDTODEVICE or IP_BOUND_IF. It is not supported on
     * Windows. `AWS_ERROR_PLATFORM_NOT_SUPPORTED` will be raised on unsupported platforms.
     */
    char network_interface_name[AWS_NETWORK_INTERFACE_NAME_MAX];
};

struct aws_socket;
struct aws_event_loop;

/**
 * Called in client mode when an outgoing connection has succeeded or an error has occurred.
 * If the connection was successful error_code will be AWS_ERROR_SUCCESS and the socket has already been assigned
 * to the event loop specified in aws_socket_connect().
 *
 * If an error occurred error_code will be non-zero.
 */
typedef void(aws_socket_on_connection_result_fn)(struct aws_socket *socket, int error_code, void *user_data);

/**
 * Called by a listening socket when either an incoming connection has been received or an error occurred.
 *
 * In the normal use-case, this function will be called multiple times over the lifetime of a single listening socket.
 * new_socket is already connected and initialized, and is using the same options and allocator as the listening socket.
 * A user may want to call aws_socket_set_options() on the new socket if different options are desired.
 *
 * new_socket is not yet assigned to an event-loop. The user should call aws_socket_assign_to_event_loop() before
 * performing IO operations.
 *
 * When error_code is AWS_ERROR_SUCCESS, new_socket is the recently accepted connection.
 * If error_code is non-zero, an error occurred and you should aws_socket_close() the socket.
 *
 * Do not call aws_socket_clean_up() from this callback.
 */
typedef void(aws_socket_on_accept_result_fn)(
    struct aws_socket *socket,
    int error_code,
    struct aws_socket *new_socket,
    void *user_data);

/**
 * Callback for when the data passed to a call to aws_socket_write() has either completed or failed.
 * On success, error_code will be AWS_ERROR_SUCCESS.
 */
typedef void(
    aws_socket_on_write_completed_fn)(struct aws_socket *socket, int error_code, size_t bytes_written, void *user_data);
/**
 * Callback for when socket is either readable (edge-triggered) or when an error has occurred. If the socket is
 * readable, error_code will be AWS_ERROR_SUCCESS.
 */
typedef void(aws_socket_on_readable_fn)(struct aws_socket *socket, int error_code, void *user_data);

#ifdef _WIN32
#    define AWS_ADDRESS_MAX_LEN 256
#else
#    include <sys/un.h>
#    define AWS_ADDRESS_MAX_LEN sizeof(((struct sockaddr_un *)0)->sun_path)
#endif
struct aws_socket_endpoint {
    char address[AWS_ADDRESS_MAX_LEN];
    uint32_t port;
};

struct aws_socket {
    struct aws_allocator *allocator;
    struct aws_socket_endpoint local_endpoint;
    struct aws_socket_endpoint remote_endpoint;
    struct aws_socket_options options;
    struct aws_io_handle io_handle;
    struct aws_event_loop *event_loop;
    struct aws_channel_handler *handler;
    int state;
    aws_socket_on_readable_fn *readable_fn;
    void *readable_user_data;
    aws_socket_on_connection_result_fn *connection_result_fn;
    aws_socket_on_accept_result_fn *accept_result_fn;
    void *connect_accept_user_data;
    void *impl;
};

struct aws_byte_buf;
struct aws_byte_cursor;

/* These are hacks for working around headers and functions we need for IO work but aren't directly includable or
   linkable. these are purposely not exported. These functions only get called internally. The awkward aws_ prefixes are
   just in case someone includes this header somewhere they were able to get these definitions included. */
#ifdef _WIN32
typedef void (*aws_ms_fn_ptr)(void);

void aws_check_and_init_winsock(void);
aws_ms_fn_ptr aws_winsock_get_connectex_fn(void);
aws_ms_fn_ptr aws_winsock_get_acceptex_fn(void);
#endif

AWS_EXTERN_C_BEGIN

/**
 * Initializes a socket object with socket options. options will be copied.
 */
AWS_IO_API int aws_socket_init(
    struct aws_socket *socket,
    struct aws_allocator *alloc,
    const struct aws_socket_options *options);

/**
 * Shuts down any pending operations on the socket, and cleans up state. The socket object can be re-initialized after
 * this operation. This function calls aws_socket_close. If you have not already called aws_socket_close() on the
 * socket, all of the rules for aws_socket_close() apply here. In this case it will not fail if you use the function
 * improperly, but on some platforms you will certainly leak memory.
 *
 * If the socket has already been closed, you can safely, call this from any thread.
 */
AWS_IO_API void aws_socket_clean_up(struct aws_socket *socket);

/**
 * Connects to a remote endpoint. In UDP, this simply binds the socket to a remote address for use with
 * `aws_socket_write()`, and if the operation is successful, the socket can immediately be used for write operations.
 *
 * In TCP, LOCAL and VSOCK this function will not block. If the return value is successful, then you must wait on the
 * `on_connection_result()` callback to be invoked before using the socket.
 *
 * If an event_loop is provided for UDP sockets, a notification will be sent on
 * on_connection_result in the event-loop's thread. Upon completion, the socket will already be assigned
 * an event loop. If NULL is passed for UDP, it will immediately return upon success, but you must call
 * aws_socket_assign_to_event_loop before use.
 */
AWS_IO_API int aws_socket_connect(
    struct aws_socket *socket,
    const struct aws_socket_endpoint *remote_endpoint,
    struct aws_event_loop *event_loop,
    aws_socket_on_connection_result_fn *on_connection_result,
    void *user_data);

/**
 * Binds the socket to a local address. In UDP mode, the socket is ready for `aws_socket_read()` operations. In
 * connection oriented modes, you still must call `aws_socket_listen()` and `aws_socket_start_accept()` before using the
 * socket. local_endpoint is copied.
 */
AWS_IO_API int aws_socket_bind(struct aws_socket *socket, const struct aws_socket_endpoint *local_endpoint);

/**
 * Get the local address which the socket is bound to.
 * Raises an error if no address is bound.
 */
AWS_IO_API int aws_socket_get_bound_address(const struct aws_socket *socket, struct aws_socket_endpoint *out_address);

/**
 * TCP, LOCAL and VSOCK only. Sets up the socket to listen on the address bound to in `aws_socket_bind()`.
 */
AWS_IO_API int aws_socket_listen(struct aws_socket *socket, int backlog_size);

/**
 * TCP, LOCAL and VSOCK only. The socket will begin accepting new connections. This is an asynchronous operation. New
 * connections or errors will arrive via the `on_accept_result` callback.
 *
 * aws_socket_bind() and aws_socket_listen() must be called before calling this function.
 */
AWS_IO_API int aws_socket_start_accept(
    struct aws_socket *socket,
    struct aws_event_loop *accept_loop,
    aws_socket_on_accept_result_fn *on_accept_result,
    void *user_data);

/**
 * TCP, LOCAL and VSOCK only. The listening socket will stop accepting new connections.
 * It is safe to call `aws_socket_start_accept()` again after
 * this operation. This can be called from any thread but be aware,
 * on some platforms, if you call this from outside of the current event loop's thread, it will block
 * until the event loop finishes processing the request for unsubscribe in it's own thread.
 */
AWS_IO_API int aws_socket_stop_accept(struct aws_socket *socket);

/**
 * Calls `close()` on the socket and unregisters all io operations from the event loop. This function must be called
 * from the event-loop's thread unless this is a listening socket. If it's a listening socket it can be called from any
 * non-event-loop thread or the event-loop the socket is currently assigned to. If called from outside the event-loop,
 * this function will block waiting on the socket to close. If this is called from an event-loop thread other than
 * the one it's assigned to, it presents the possibility of a deadlock, so don't do it.
 */
AWS_IO_API int aws_socket_close(struct aws_socket *socket);

/**
 * Calls `shutdown()` on the socket based on direction.
 */
AWS_IO_API int aws_socket_shutdown_dir(struct aws_socket *socket, enum aws_channel_direction dir);

/**
 * Sets new socket options on the underlying socket. This is mainly useful in context of accepting a new connection via:
 * `on_incoming_connection()`. options is copied.
 */
AWS_IO_API int aws_socket_set_options(struct aws_socket *socket, const struct aws_socket_options *options);

/**
 * Assigns the socket to the event-loop. The socket will begin receiving read/write/error notifications after this call.
 *
 * Note: If you called connect for TCP or Unix Domain Sockets and received a connection_success callback, this has
 * already happened. You only need to call this function when:
 *
 * a.) This socket is a server socket (e.g. a result of a call to start_accept())
 * b.) This socket is a UDP socket.
 */
AWS_IO_API int aws_socket_assign_to_event_loop(struct aws_socket *socket, struct aws_event_loop *event_loop);

/**
 * Gets the event-loop the socket is assigned to.
 */
AWS_IO_API struct aws_event_loop *aws_socket_get_event_loop(struct aws_socket *socket);

/**
 * Subscribes on_readable to notifications when the socket goes readable (edge-triggered). Errors will also be recieved
 * in the callback.
 *
 * Note! This function is technically not thread safe, but we do not enforce which thread you call from.
 * It's your responsibility to either call this in safely (e.g. just don't call it in parallel from multiple threads) or
 * schedule a task to call it. If you call it before your first call to read, it will be fine.
 */
AWS_IO_API int aws_socket_subscribe_to_readable_events(
    struct aws_socket *socket,
    aws_socket_on_readable_fn *on_readable,
    void *user_data);

/**
 * Reads from the socket. This call is non-blocking and will return `AWS_IO_SOCKET_READ_WOULD_BLOCK` if no data is
 * available. `read` is the amount of data read into `buffer`.
 *
 * Attempts to read enough to fill all remaining space in the buffer, from `buffer->len` to `buffer->capacity`.
 * `buffer->len` is updated to reflect the buffer's new length.
 *
 *
 * Use aws_socket_subscribe_to_readable_events() to receive notifications of when the socket goes readable.
 *
 * NOTE! This function must be called from the event-loop used in aws_socket_assign_to_event_loop
 */
AWS_IO_API int aws_socket_read(struct aws_socket *socket, struct aws_byte_buf *buffer, size_t *amount_read);

/**
 * Writes to the socket. This call is non-blocking and will attempt to write as much as it can, but will queue any
 * remaining portion of the data for write when available. written_fn will be invoked once the entire cursor has been
 * written, or the write failed or was cancelled.
 *
 * NOTE! This function must be called from the event-loop used in aws_socket_assign_to_event_loop
 *
 * For client sockets, connect() and aws_socket_assign_to_event_loop() must be called before calling this.
 *
 * For incoming sockets from a listener, aws_socket_assign_to_event_loop() must be called first.
 */
AWS_IO_API int aws_socket_write(
    struct aws_socket *socket,
    const struct aws_byte_cursor *cursor,
    aws_socket_on_write_completed_fn *written_fn,
    void *user_data);

/**
 * Gets the latest error from the socket. If no error has occurred AWS_OP_SUCCESS will be returned. This function does
 * not raise any errors to the installed error handlers.
 */
AWS_IO_API int aws_socket_get_error(struct aws_socket *socket);

/**
 * Returns true if the socket is still open (doesn't mean connected or listening, only that it hasn't had close()
 * called.
 */
AWS_IO_API bool aws_socket_is_open(struct aws_socket *socket);

/**
 * Raises AWS_IO_SOCKET_INVALID_ADDRESS and logs an error if connecting to this port is illegal.
 * For example, port must be in range 1-65535 to connect with IPv4.
 * These port values would fail eventually in aws_socket_connect(),
 * but you can use this function to validate earlier.
 */
AWS_IO_API int aws_socket_validate_port_for_connect(uint32_t port, enum aws_socket_domain domain);

/**
 * Raises AWS_IO_SOCKET_INVALID_ADDRESS and logs an error if binding to this port is illegal.
 * For example, port must in range 0-65535 to bind with IPv4.
 * These port values would fail eventually in aws_socket_bind(),
 * but you can use this function to validate earlier.
 */
AWS_IO_API int aws_socket_validate_port_for_bind(uint32_t port, enum aws_socket_domain domain);

/**
 * Assigns a random address (UUID) for use with AWS_SOCKET_LOCAL (Unix Domain Sockets).
 * For use in internal tests only.
 */
AWS_IO_API void aws_socket_endpoint_init_local_address_for_test(struct aws_socket_endpoint *endpoint);

AWS_EXTERN_C_END
AWS_POP_SANE_WARNING_LEVEL

#endif /* AWS_IO_SOCKET_H */
