#ifndef LANDER_HTTP_LOOP
#define LANDER_HTTP_LOOP

#include <regex.h>

#include "event_loop.h"
#include "http/req.h"
#include "http/res.h"
#include "http/types.h"
#include "trie.h"

// Max amount of steps a route can use
#define HTTP_LOOP_MAX_STEPS 17

#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))

/**
 * Type of a route
 */
typedef enum http_route_type {
  http_route_literal = 0,
  http_route_regex = 1,
} http_route_type;

/**
 * Function describing a step in a route's processing.
 *
 * @param conn connection to process
 * @return whether processing can proceed to the next step without performing
 * I/O first. For a request step, `false` means more data needs to be read
 * before the step can finish its processing. For response steps, `false` means
 * there's new data in the write buffer that needs to be written.
 */
typedef bool (*http_step)(event_loop_conn *conn);

extern const http_step http_default_res_steps[HTTP_LOOP_MAX_STEPS];

/**
 * Struct describing a route a request can take.
 */
typedef struct http_route {
  http_route_type type;
  http_method method;
  char *path;
  // Compiled regex for a regex route. This value gets set at runtime when
  // starting the http loop
  regex_t *regex;
  const http_step steps[HTTP_LOOP_MAX_STEPS];
  const http_step steps_res[HTTP_LOOP_MAX_STEPS];
} http_route;

/**
 * Global context passed to every connection using the same pointer
 */
typedef struct http_loop_gctx {
  http_route *routes;
  size_t route_count;
  void *(*custom_ctx_init)();
  void (*custom_ctx_reset)(void *);
  void (*custom_ctx_free)(void *);
  const char *api_key;
  // Custom global context
  void *c;
} http_loop_gctx;

/**
 * Initialize a new global context
 *
 * @return pointer to the newly allocated object.
 */
http_loop_gctx *http_loop_gctx_init();

/**
 * Invidivual context initialized for every connection
 */
typedef struct http_loop_ctx {
  http_request req;
  http_response res;
  http_route *route;
  size_t current_step;
  http_loop_gctx *g;
  void *c;
} http_loop_ctx;

/**
 * Initialize a context struct
 *
 * @param g global context
 * @return pointer to the newly allocated context
 */
http_loop_ctx *http_loop_ctx_init(http_loop_gctx *g);

/**
 * Resets an already allocated context so that it can be reused for a new
 * request.
 *
 * @param ctx context to reset
 */
void http_loop_ctx_reset(http_loop_ctx *ctx);

/**
 * Free a context struct. Internally this first calls http_loop_ctx_reset.
 *
 * @param ctx context to free
 */
void http_loop_ctx_free(http_loop_ctx *ctx);

/**
 * Represents an HTTP loop
 */
typedef struct event_loop http_loop;

/**
 * Process incoming data as an HTTP request. This is the "handle_data" function
 * for the event loop.
 *
 * @param conn connection to process
 * @return whether another request can be processed immediately.
 */
bool http_loop_handle_request(event_loop_conn *conn);

/**
 * Try to parse the incoming data as an HTTP request.
 *
 * @param conn connection to process
 * @return result of the parse
 */
http_parse_error http_loop_parse_request(event_loop_conn *conn);

/**
 * Try to match the parsed request with one of the defined routes, aka route the
 * request.
 *
 * @param conn connection to process
 */
void http_loop_route_request(event_loop_conn *conn);

/**
 * Advance the processing of the routed request's processing by cycling through
 * the request's various steps.
 *
 * @param conn connection to process
 */
void http_loop_process_request(event_loop_conn *conn);

/**
 * Handles the response processing. This is the `write_data` function for the
 * event loop.
 *
 * @param conn connection to process
 */
void http_loop_handle_response(event_loop_conn *conn);

/**
 * Request step that consumes the request body and stores it in a buffer.
 *
 * @param conn connection to process
 * @return true if the body has been fully received, false otherwise
 */
bool http_loop_step_body_to_buf(event_loop_conn *conn);

/**
 * Request step that consumes the request body and stores it in a file.
 *
 * @param conn connection to process
 * @return true if the body has been fully received, false otherwise
 */
bool http_loop_step_body_to_file(event_loop_conn *conn);

/**
 * Try to parse the Content-Length header.
 *
 * @param conn connection to process
 */
bool http_loop_step_parse_content_length(event_loop_conn *conn);

/**
 * Authenticate the request using the X-Api-Key header.
 *
 * @param conn connection to check
 * @return always true
 */
bool http_loop_step_auth(event_loop_conn *conn);

/**
 * A step that simply sets the connection's state to res.
 *
 * @param conn connection to process
 * @return always true
 */
bool http_loop_step_switch_res(event_loop_conn *conn);

/**
 * Write the HTTP header back to the connection. If `res->head` is not set, a
 * header will be generated for you.
 *
 * @param conn connection to process
 */
bool http_loop_step_write_header(event_loop_conn *conn);

/**
 * Write the HTTP body back to the connection.
 *
 * @param conn connection to process
 */
bool http_loop_step_write_body(event_loop_conn *conn);

/**
 * Initialize a new http loop.
 *
 * @param routes array of routes that should be served
 * @param route_count how many elements are in `routes`
 * @param custom_gctx the application's custom global context; can be NULL
 * @param custom_ctx_init function to initialize a new custom context
 * @param custom_ctx_reset function to reset a custom context
 * @param custom_ctx_free function to free a custom context; will always be run
 * after a reset
 * @return pointer to the newly allocated object
 */
http_loop *http_loop_init(http_route *routes, size_t route_count,
                          void *custom_gctx, void *(*custom_ctx_init)(),
                          void(custom_ctx_reset)(void *),
                          void(custom_ctx_free)(void *));

/**
 * Set the API key the authentication steps should use.
 *
 * @param hl HTTP loop to set key in
 * @param api_key API key to use
 */
void http_loop_set_api_key(http_loop *hl, const char *api_key);

/**
 * Run the HTTP loop. This function never returns.
 *
 * @param el the event loop
 * @param port on what port to listen
 */
void http_loop_run(http_loop *hl, int port);

#endif