/**
 * @file addons/http.h
 * @brief HTTP addon.
 *
 * Minimalistic HTTP server that can receive and reply to simple HTTP requests.
 * The main goal of this addon is to enable remotely connecting to a running
 * Flecs application (for example, with a web-based UI) and request and visualize
 * data from the ECS world.
 *
 * Each server instance creates a single thread used for receiving requests.
 * Received requests are enqueued and handled when the application calls
 * ecs_http_server_dequeue(). This increases the latency of request handling vs.
 * responding directly in the receive thread, but is better suited for
 * retrieving data from ECS applications, as requests can be processed by an ECS
 * system without having to lock the world.
 *
 * This server is intended to be used in a development environment.
 */

#ifdef FLECS_HTTP

/**
 * @defgroup c_addons_http Http
 * @ingroup c_addons
 * Simple HTTP server used for serving up the REST API.
 *
 * @{
 */

#if !defined(FLECS_OS_API_IMPL) && !defined(FLECS_NO_OS_API_IMPL)
#define FLECS_OS_API_IMPL
#endif

#ifndef FLECS_HTTP_H
#define FLECS_HTTP_H

/** Maximum number of headers in a request. */
#define ECS_HTTP_HEADER_COUNT_MAX (32)

/** Maximum number of query parameters in a request. */
#define ECS_HTTP_QUERY_PARAM_COUNT_MAX (32)

#ifdef __cplusplus
extern "C" {
#endif

/** HTTP server. */
typedef struct ecs_http_server_t ecs_http_server_t;

/** A connection manages communication with the remote host. */
typedef struct {
    uint64_t id;                   /**< Connection ID. */
    ecs_http_server_t *server;     /**< Server. */

    char host[128];                /**< Remote host. */
    char port[16];                 /**< Remote port. */
} ecs_http_connection_t;

/** Helper type used for headers and URL query parameters. */
typedef struct {
    const char *key;               /**< Key. */
    const char *value;             /**< Value. */
} ecs_http_key_value_t;

/** Supported request methods. */
typedef enum {
    EcsHttpGet,
    EcsHttpPost,
    EcsHttpPut,
    EcsHttpDelete,
    EcsHttpOptions,
    EcsHttpMethodUnsupported
} ecs_http_method_t;

/** An HTTP request. */
typedef struct {
    uint64_t id;                   /**< Request ID. */

    ecs_http_method_t method;      /**< Request method. */
    char *path;                    /**< Request path. */
    char *body;                    /**< Request body. */
    ecs_http_key_value_t headers[ECS_HTTP_HEADER_COUNT_MAX]; /**< Request headers. */
    ecs_http_key_value_t params[ECS_HTTP_QUERY_PARAM_COUNT_MAX];  /**< Request query parameters. */
    int32_t header_count;          /**< Number of headers. */
    int32_t param_count;           /**< Number of query parameters. */

    ecs_http_connection_t *conn;   /**< Connection. */
} ecs_http_request_t;

/** An HTTP reply. */
typedef struct {
    int code;                   /**< default = 200. */
    ecs_strbuf_t body;          /**< default = "". */
    const char* status;         /**< default = OK. */
    const char* content_type;   /**< default = application/json. */
    ecs_strbuf_t headers;       /**< default = "". */
} ecs_http_reply_t;

/** Default initializer for ecs_http_reply_t. */
#define ECS_HTTP_REPLY_INIT \
    (ecs_http_reply_t){200, ECS_STRBUF_INIT, "OK", "application/json", ECS_STRBUF_INIT}

/** Global HTTP statistics. */
extern int64_t ecs_http_request_received_count;       /**< Total number of HTTP requests received. */
extern int64_t ecs_http_request_invalid_count;        /**< Total number of invalid HTTP requests. */
extern int64_t ecs_http_request_handled_ok_count;     /**< Total number of successful HTTP requests. */
extern int64_t ecs_http_request_handled_error_count;  /**< Total number of HTTP requests with errors. */
extern int64_t ecs_http_request_not_handled_count;    /**< Total number of HTTP requests with an unknown endpoint. */
extern int64_t ecs_http_request_preflight_count;      /**< Total number of preflight HTTP requests received. */
extern int64_t ecs_http_send_ok_count;                /**< Total number of HTTP replies successfully sent. */
extern int64_t ecs_http_send_error_count;             /**< Total number of HTTP replies that failed to send. */
extern int64_t ecs_http_busy_count;                   /**< Total number of HTTP busy replies. */

/** Request callback.
 * Invoked for each valid request. The function should populate the reply and
 * return true. When the function returns false, the server will reply with a
 * 404 (Not found) code. */
typedef bool (*ecs_http_reply_action_t)(
    const ecs_http_request_t* request,
    ecs_http_reply_t *reply,
    void *ctx);

/** Used with ecs_http_server_init(). */
typedef struct {
    ecs_http_reply_action_t callback; /**< Function called for each request. */
    void *ctx;                        /**< Passed to callback (optional). */
    uint16_t port;                    /**< HTTP port. */
    const char *ipaddr;               /**< Interface to listen on (optional). */
    int32_t send_queue_wait_ms;       /**< Send queue wait time when empty. */
    double cache_timeout;             /**< Cache invalidation timeout (0 disables caching). */
    double cache_purge_timeout;       /**< Cache purge timeout (for purging cache entries). */
} ecs_http_server_desc_t;

/** Create a server.
 * Use ecs_http_server_start() to start receiving requests.
 *
 * @param desc Server configuration parameters.
 * @return The new server, or NULL if creation failed.
 */
FLECS_API
ecs_http_server_t* ecs_http_server_init(
    const ecs_http_server_desc_t *desc);

/** Destroy a server.
 * This operation will stop the server if it was still running.
 *
 * @param server The server to destroy.
 */
FLECS_API
void ecs_http_server_fini(
    ecs_http_server_t* server);

/** Start a server.
 * After this operation, the server will be able to accept requests.
 *
 * @param server The server to start.
 * @return Zero if successful, non-zero if failed.
 */
FLECS_API
int ecs_http_server_start(
    ecs_http_server_t* server);

/** Process server requests.
 * This operation invokes the reply callback for each received request. No new
 * requests will be enqueued while processing requests.
 *
 * @param server The server for which to process requests.
 * @param delta_time The time passed since the last call to dequeue.
 */
FLECS_API
void ecs_http_server_dequeue(
    ecs_http_server_t* server,
    ecs_ftime_t delta_time);

/** Stop a server.
 * After this operation, no new requests can be received.
 *
 * @param server The server.
 */
FLECS_API
void ecs_http_server_stop(
    ecs_http_server_t* server);

/** Emulate a request.
 * The request string must be a valid HTTP request. A minimal example:
 *
 *     GET /entity/flecs/core/World?label=true HTTP/1.1
 *
 * @param srv The server.
 * @param req The request.
 * @param len The length of the request (optional).
 * @param reply_out The reply (out parameter).
 * @return Zero if success, non-zero if failed.
 */
FLECS_API
int ecs_http_server_http_request(
    ecs_http_server_t* srv,
    const char *req,
    ecs_size_t len,
    ecs_http_reply_t *reply_out);

/** Convenience wrapper around ecs_http_server_http_request().
 *
 * @param srv The server.
 * @param method The HTTP method (e.g., "GET").
 * @param req The request path.
 * @param body The request body (optional).
 * @param reply_out The reply (out parameter).
 * @return Zero if success, non-zero if failed.
 */
FLECS_API
int ecs_http_server_request(
    ecs_http_server_t* srv,
    const char *method,
    const char *req,
    const char *body,
    ecs_http_reply_t *reply_out);

/** Get context provided in ecs_http_server_desc_t.
 *
 * @param srv The server.
 * @return The context.
 */
FLECS_API
void* ecs_http_server_ctx(
    ecs_http_server_t* srv);

/** Find a header in a request.
 *
 * @param req The request.
 * @param name Name of the header to find.
 * @return The header value, or NULL if not found.
*/
FLECS_API
const char* ecs_http_get_header(
    const ecs_http_request_t* req,
    const char* name);

/** Find a query parameter in a request.
 *
 * @param req The request.
 * @param name The parameter name.
 * @return The decoded parameter value, or NULL if not found.
 */
FLECS_API
const char* ecs_http_get_param(
    const ecs_http_request_t* req,
    const char* name);

#ifdef __cplusplus
}
#endif

/** @} */

#endif // FLECS_HTTP_H

#endif // FLECS_HTTP
