/*
* Copyright (c) 2013-2026, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/

#ifndef HTTP_H__
#define HTTP_H__

#include <cstring>
#include <map>
#include <list>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
#include <boost/algorithm/string/predicate.hpp>

namespace i2p
{
namespace http
{
	constexpr std::string_view CRLF = "\r\n";         /**< HTTP line terminator */
	constexpr std::string_view HTTP_EOH = "\r\n\r\n"; /**< HTTP end-of-headers mark */

	// case-insensitive comparator for HTTP header names (RFC 7230)
	struct CaseInsensitiveLess
	{
		using is_transparent = void;

		bool operator() (std::string_view a, std::string_view b) const
		{
			return boost::ilexicographical_compare(a, b);
		}
	};

	struct URL
	{
		std::string schema;
		std::string user;
		std::string pass;
		std::string host;
		unsigned short int port;
		std::string path;
		bool hasquery;
		std::string query;
		std::string frag;
		bool ipv6;

		URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), hasquery(false), query(""), frag(""), ipv6(false) {};

		/**
		 * @brief Tries to parse url from string
		 * @return true on success, false on invalid url
		 */
		bool parse (const char *str, std::size_t len = 0);
		bool parse (std::string_view url);

		/**
		 * @brief Parse query part of url to key/value map
		 * @note Honestly, this should be implemented with std::multimap
		 */
		bool parse_query(std::map<std::string, std::string> & params) const;

		void create_query (const std::map<std::string, std::string>& params);

		/**
		 * @brief Serialize URL structure to url
		 * @note Returns relative url if schema if empty, absolute url otherwise
		 */
		std::string to_string (bool requestTargetOnly = false) const;

		/**
		 * @brief return true if the host is inside i2p
		 */
		bool is_i2p() const;
	};

	struct HTTPMsg
	{
		std::map<std::string, std::string, CaseInsensitiveLess> headers;

		void add_header(const char *name, const std::string & value, bool replace = false);
		void add_header(const char *name, const char *value, bool replace = false);
		void del_header(const char *name);
		std::string get_header(const std::string& name) const;

		/** @brief Returns declared message length or -1 if unknown */
		long int content_length() const;
	};

	struct HTTPReq
	{
		std::list<std::pair<std::string, std::string> > headers;
		std::string version;
		std::string method;
		std::string uri;

		HTTPReq (): version("HTTP/1.0"), method("GET"), uri("/") {};

		/**
		 * @brief Tries to parse HTTP request from string
		 * @return -1 on error, 0 on incomplete query, >0 on success
		 * @note Positive return value is a size of header
		 */
		int parse(const char *buf, size_t len);
		int parse(std::string_view buf);

		/** @brief Serialize HTTP request to string */
		std::string to_string() const;
		void write(std::ostream & o) const;

		void AddHeader (const std::string& name, const std::string& value);
		void UpdateHeader (const std::string& name, const std::string& value);
		void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt
		void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); };
		std::string GetHeader (std::string_view name) const;
		size_t GetNumHeaders (std::string_view name) const;
		size_t GetNumHeaders () const { return headers.size (); };
	};

	struct HTTPRes : HTTPMsg {
		std::string version;
		std::string status;
		unsigned short int code;
		/**
		 * @brief Simplifies response generation
		 *
		 * If this variable is set, on @a to_string() call:
		 *   * Content-Length header will be added if missing,
		 *   * contents of @a body will be included in generated response
		 */
		std::string body;

		HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {}

		/**
		 * @brief Tries to parse HTTP response from string
		 * @return -1 on error, 0 on incomplete query, >0 on success
		 * @note Positive return value is a size of header
		 */
		int parse(const char *buf, size_t len);
		int parse(const std::string_view buf);

		/**
		 * @brief Serialize HTTP response to string
		 * @note If @a version is set to HTTP/1.1, and Date header is missing,
		 *   it will be generated based on current time and added to headers
		 * @note If @a body is set and Content-Length header is missing,
		 *   this header will be added, based on body's length
		 */
		std::string to_string();

		/** @brief Checks that response declared as chunked data */
		bool is_chunked() const ;

		/** @brief Checks that response contains compressed data */
		bool is_gzipped(bool includingI2PGzip = true) const;
	};

	/**
	 * @brief returns HTTP status string by integer code
	 * @param code HTTP code [100, 599]
	 * @return Immutable string with status
	 */
	std::string_view HTTPCodeToStatus(int code);

	/**
	 * @brief Replaces %-encoded characters in string with their values
	 * @param data Source string
	 * @param null If set to true - decode also %00 sequence, otherwise - skip
	 * @return Decoded string
	 */
	std::string UrlDecode(std::string_view url, bool null = false);

	/**
	 * @brief Merge HTTP response content with Transfer-Encoding: chunked
	 * @param in Input stream
	 * @param out Output stream
	 * @return true on success, false otherwise
	 */
	bool MergeChunkedResponse (std::istream& in, std::ostream& out);

	std::string CreateBasicAuthorizationString (const std::string& user, const std::string& pass);

} // http
} // i2p

#endif /* HTTP_H__ */
