// license:BSD-3-Clause
// copyright-holders:Miodrag Milanovic
/***************************************************************************

    http.h

    HTTP server handling

***************************************************************************/

#pragma once

#ifndef __EMU_H__
#error Dont include this file directly; include emu.h instead.
#endif

#ifndef MAME_EMU_HTTP_H
#define MAME_EMU_HTTP_H

#include <mutex>
#include <thread>
#include <ctime>
#include "server_http.hpp"
#include "server_ws.hpp"

//**************************************************************************
//    TYPE DEFINITIONS
//**************************************************************************

// ======================> http_manager
namespace asio
{
	class io_context;
}

class http_manager
{
	DISABLE_COPYING(http_manager);
public:

	/** An HTTP Request. */
	struct http_request
	{
		/** Retrieves the requested resource. */
		virtual const std::string get_resource() = 0; // The entire resource: path, query and fragment.

		/** Returns the path part of the requested resource. */
		virtual const std::string get_path() = 0;

		/** Returns the query part of the requested resource. */
		virtual const std::string get_query() = 0;

		/** Returns the fragment part of the requested resource. */
		virtual const std::string get_fragment() = 0;

		/** Retrieves a header from the HTTP request. */
		virtual const std::string get_header(const std::string &header_name) = 0;

		/** Retrieves a header from the HTTP request. */
		virtual const std::list<std::string> get_headers(const std::string &header_name) = 0;

		/** Returns the body that was submitted with the HTTP request. */
		virtual const std::string get_body() = 0;
	};
	typedef std::shared_ptr<http_request> http_request_ptr;

	/** An HTTP response. */
	struct http_response
	{
		/** Sets the HTTP status to be returned to the client. */
		virtual void set_status(int status) = 0;

		/** Sets the HTTP content type to be returned to the client. */
		virtual void set_content_type(const std::string &type) = 0;

		/** Sets the body to be sent to the client. */
		virtual void set_body(const std::string &body) = 0;

		/** Appends something to the body to be sent to the client. */
		virtual void append_body(const std::string &body) = 0;
	};
	typedef std::shared_ptr<http_response> http_response_ptr;

	/** Identifies a Websocket connection. */
	struct websocket_connection
	{
		/** Sends a message to the client that is connected on the other end of this Websocket connection. */
		virtual void send_message(const std::string &payload, int opcode) = 0;

		/** Closes this open Websocket connection. */
		virtual void close() = 0;

		virtual ~websocket_connection() { }
	};
	typedef std::shared_ptr<websocket_connection> websocket_connection_ptr;

	/** Handles opening an incoming Websocket connection. */
	typedef std::function<void(websocket_connection_ptr connection)> websocket_open_handler;

	/** Handles an incoming message on an open Websocket connection. */
	typedef std::function<void(websocket_connection_ptr connection, const std::string &payload, int opcode)> websocket_message_handler;

	/** Handles when the client has closed a websocket connection. */
	typedef std::function<void(websocket_connection_ptr connection, int status, const std::string& reason)> websocket_close_handler;

	/** Handles when there has been an error on a websocket connection. */
	typedef std::function<void(websocket_connection_ptr connection, const std::error_code& error_code)> websocket_error_handler;

	/** Handles an incoming HTTP request, generating an HTTP response. */
	typedef std::function<void(http_request_ptr, http_response_ptr)> http_handler;

	struct websocket_endpoint : public std::enable_shared_from_this<websocket_endpoint> {
		websocket_open_handler on_open;
		websocket_message_handler on_message;
		websocket_close_handler on_close;
		websocket_error_handler on_error;
	};
	typedef std::shared_ptr<websocket_endpoint> websocket_endpoint_ptr;

	/** Substitutes one string with another, and returns whether the substitution should be performed.
	 * Used when evaluating a template. */
	typedef std::function<bool(std::string &)> substitution;

	http_manager(bool active, short port, const char *root);
	virtual ~http_manager();
	void clear();

	/** Adds a template to the web server. When the path is requested, the template will be read and parsed:
	 * strings between each pair of <init, term> characters will be passed to the substitute function and the result
	 * will be used instead.
	 */
	void add_template(const std::string &path, substitution substitute, char init, char term);

	/** Removes a template from the web server. */
	void remove_template(const std::string &path);

	/** Serves a template at an explicit path, which may be diffrent from the request path, under the document root.
	 * The template will be read and parsed:
	 * strings between each pair of <init, term> characters will be passed to the substitute function and the result
	 * will be used instead.
	 */
	void serve_template(http_request_ptr request, http_response_ptr response, const std::string &path, substitution substitute, char init, char term);

	void serve_document(http_request_ptr request, http_response_ptr response, const std::string &path);

	/** Adds an HTTP handler. When the specified path is requested, the specified HTTP handler will be called. */
	void add_http_handler(const std::string &path, http_handler handler);

	/** Removes the HTTP handler at the specified path. */
	void remove_http_handler(const std::string &path);

	/** Retrieves a websocket endpoint, possibly adding it if it does not already exist. */
	websocket_endpoint_ptr add_endpoint(const std::string &path, websocket_open_handler on_open, websocket_message_handler on_message, websocket_close_handler on_close, websocket_error_handler on_error);

	/** Removes the websocket endpoint at the specified path. */
	void remove_endpoint(const std::string &path);

	bool is_active() {
		return m_active;
	}

private:
	void on_open(http_manager::websocket_endpoint_ptr endpoint, std::shared_ptr<webpp::Connection> connection);

	void on_message(http_manager::websocket_endpoint_ptr endpoint, std::shared_ptr<webpp::Connection> connection, const std::string& payload, int opcode);

	void on_close(http_manager::websocket_endpoint_ptr endpoint, std::shared_ptr<webpp::Connection> connection, int status, const std::string& reason);

	void on_error(http_manager::websocket_endpoint_ptr endpoint, std::shared_ptr<webpp::Connection> connection, const std::error_code& error_code);

	bool read_file(std::ostream &os, const std::string &path);

	bool m_active;

	std::shared_ptr<asio::io_context>   m_io_context;
	std::unique_ptr<webpp::http_server> m_server;
	std::unique_ptr<webpp::ws_server>   m_wsserver;
	std::thread                         m_server_thread;
	std::string                         m_root;

	std::unordered_map<std::string, http_handler> m_handlers;
	std::mutex                                    m_handlers_mutex;

	std::unordered_map<std::string, websocket_endpoint_ptr> m_endpoints;
	std::mutex                                              m_endpoints_mutex;

	std::unordered_map<void *, websocket_connection_ptr> m_connections;  // the keys are really webpp::ws_server::Connection pointers
	std::mutex                                           m_connections_mutex;

};


#endif  /* MAME_EMU_HTTP_H */
