/////////////////////////////////////////////////////////
//
// HTTP Implementation.
//
// This is a fairly limited implementation of HTTP.
//
// It only supports HTTP 1.1
//
// It only supports GET, POST, and HEAD.
//
/////////////////////////////////////////////////////////

#pragma once

#include "eng-malloc.hpp"
#include "wrap-string.hpp"
#include "wrap-vector.hpp"
#include "wrap-map.hpp"
#include "luastack.hpp"
#include "keywords.hpp"
#include "streambuffer.hpp"
#include "drivenengine.hpp"
#include <ostream>

using UrlParameters = eng::map<eng::string, eng::string>;

class HttpClientRequest : public eng::nevernew {    
private:
    // Request IDs.
    int64_t request_id_;
    int64_t place_id_;
    int64_t thread_id_;
    
    // When we detect that we generated an invalid
    // outgoing request, the error is stored here.
    eng::string check_fail_;

    // If true, verify the server's certificate.
    // True is the default.
    bool verify_certificate_;

    // Method: GET, HEAD, or POST.  Must be all-caps.
    eng::string method_;

    // The hostname. Not yet url-encoded.
    eng::string host_;

    // Port number.
    int port_;

    // The path.  Not yet url-encoded.  Can not include URL parameters.
    eng::string path_;
    
    // URL parameters to append to the path.  Not yet url-encoded.
    UrlParameters params_;

    // The mime type of the content, only for POST requests.
    eng::string mime_type_;

    // The content as a string, only for POST requests.
    eng::string content_;
    bool content_assigned_;

private:
    void check_fail(std::string_view error);
    void send_internal(StreamBuffer *target, bool debug_string) const;

public:
    // Construct an empty HTTP request.
    // All of the fields have empty values.
    HttpClientRequest();
    
    // Populate an request-related fields one piece at a time.
    // If you pass an invalid value, or if the field is
    // already set, the routine will generate an error message
    // and store it in the error field.  In that case, the set
    // will not happen.  If there's already an error in the error
    // field, it will not be overwritten.
    //
    void set_verify_certificate(bool flag);
    void set_method(const eng::string &method);
    void set_host(const eng::string &host);
    void set_port(int port);
    void set_path(std::string_view path);
    void set_param(const eng::string &key, const eng::string &value);
    void set_url(std::string_view url);
    void set_mime_type(const eng::string &mime_type);
    void set_content(const eng::string &content);
 
    void set_verify_certificate(LuaCoreStack &LS, LuaSlot val);
    void set_method(LuaCoreStack &LS, LuaSlot val);
    void set_host(LuaCoreStack &LS, LuaSlot val);
    void set_port(LuaCoreStack &LS, LuaSlot val);
    void set_path(LuaCoreStack &LS, LuaSlot path);
    void set_param(LuaCoreStack &LS, LuaSlot key, LuaSlot val);
    void set_params(LuaCoreStack &LS, LuaSlot tab);
    void set_url(LuaCoreStack &LS, LuaSlot val);
    void set_mime_type(LuaCoreStack &LS, LuaSlot val);
    void set_content(LuaCoreStack &LS, LuaSlot val);
    void set_jsonvalue(LuaCoreStack &LS, LuaSlot val);

    // Set default values for method and port.
    // This must be done after setting regular values.
    void set_defaults();

    // Populate request-related fields from a Lua table.
    // Doesn't throw errors, instead, returns errors via check(). 
    void configure(LuaKeywordParser &kp);
    
    // Get or Set the request IDs.
    //
    // This class does not use these fields, it just stores
    // them as a convenience.
    //
    int64_t request_id() const { return request_id_; }
    int64_t place_id() const { return place_id_; }
    int64_t thread_id() const { return thread_id_; }
    void set_request_id(int64_t request_id) { request_id_ = request_id; }
    void set_place_id(int64_t place_id) { place_id_ = place_id; }
    void set_thread_id(int64_t thread_id) { thread_id_ = thread_id; }
    
    // Get the network target, eg, "cert:host:port"
    //
    eng::string target() const;

    // Verify that the request is error free and that
    // defaults have been set.
    //
    eng::string check() const;

    // Get the method.
    //
    eng::string method() const { return method_; }

    // Put the request into the stream, assuming HTTP/1.1
    //
    void send(StreamBuffer *target) const { send_internal(target, false); }

    // Serialize and deserialize.
    //
    void serialize(StreamBuffer *sb) const;
    void deserialize(StreamBuffer *sb);

    // Get the request as a debug string.
    //
    eng::string debug_string();
};

class HttpServerResponse {
private:
    // When we detect that our own response is
    // invalid, the error is stored here.
    //
    eng::string check_fail_;

    // The status code to send back to the client
    // as part of the response status line.
    //
    int status_;

    // Maximum age for the cache-control directive.
    //
    int max_age_;

    // Mime type of the content.
    //
    eng::string mime_type_;

    // Content to send to the client.
    //
    eng::string content_;
    bool content_assigned_;

    // If the keep-alive flag is set, then a connection: keep-alive
    // header is sent.  Otherwise, a connection: close is sent.
    // The keep-alive flag cannot be controlled from Lua.  It is
    // designed to be handled automatically by the server code.
    //
    bool keep_alive_;

    // The protocol is taken from the request.
    //
    bool http11_;
        
private:
    void check_fail(std::string_view error);
    void send_internal(StreamBuffer *target, bool debug_string) const;

public:
    // Construct an empty response.
    // All of the fields have empty values.
    //
    HttpServerResponse();

    void set_method(const eng::string &method);
    void set_status(int status);
    void set_max_age(int max_age);
    void set_mime_type(const eng::string &mime_type);
    void set_content(const eng::string &content);
 
    void set_status(LuaCoreStack &LS, LuaSlot val);
    void set_max_age(LuaCoreStack &LS, LuaSlot val);
    void set_mime_type(LuaCoreStack &LS, LuaSlot val);
    void set_content(LuaCoreStack &LS, LuaSlot val);
    void set_jsonvalue(LuaCoreStack &LS, LuaSlot val);

    // Set default values.
    //
    void set_defaults();

    // Populate request-related fields from a Lua table.
    // Doesn't throw errors, instead, returns them via check()
    //
    void configure(LuaKeywordParser &kp);

    // Set the keep_alive flag.
    //
    void set_keep_alive(bool k) { keep_alive_ = k; };

    // Set the protocol.
    //
    void set_http11(bool k) { http11_= k; }
    
    // Store a fail response.
    //
    void fail(int status, const eng::string &message);

    // Verify that the response is error free.
    //
    eng::string check() const;

    // Get the status of the response.
    //
    int status() const { return status_; }
    bool errstatus() const { return (status_ >= 400)&&(status_ <= 599); }

    // Put the response into the stream, assuming HTTP/1.1
    //
    void send(StreamBuffer *target) const { send_internal(target, false); }

    // Get the response as a debug string.
    //
    eng::string debug_string();
};

// HttpParser is used for parsing both requests and responses.
//
class HttpParser : public eng::nevernew {
protected:
    // The request ID is not used by the parser, but
    // you can store a request ID in there for convenience.
    int64_t request_id_;

    // True if this parser parsed a request.  If false,
    // then it parsed a response.
    bool is_request_;

    // The status code, a 3-digit number.
    int status_;

    // If the communication contains an error, the error can
    // be stored here.  In the event of a successful communication,
    // this should always be empty string.
    eng::string error_;

    // Only if content-length header present, otherwise, -1.
    int64_t content_length_;

    // If empty, it means there was no transfer-encoding header.
    eng::string transfer_encoding_;

    // If empty, it means there was no content-encoding header.
    eng::string content_encoding_;

    // Only if location header present.
    eng::string location_;

    // MIME type of the content.
    eng::string mime_type_;

    // Charset of the content before the content was translated to utf-8.
    eng::string charset_;

    // The protocol: true for HTTP/1.1, false for HTTP/1.0
    bool http11_;

    // The method.  In a response, this is assigned before parsing.
    eng::string method_;

    // The URL path, not url-encoded. (only when parsing requests)
    eng::string path_;

    // The URL parameters, not url-encoded (only when parsing requests)
    UrlParameters params_;

    // The content as string.  If it's text, it's been translated to utf-8.
    eng::string content_;

    // The length in bytes of the entire communication.
    // If the response is complete, but the comm_length_
    // is zero, it means we couldn't find the end of 
    // the request.
    int comm_length_;

protected:
    // Store a message indicating that we haven't received enough
    // bytes yet.  If the connection is closed and we still haven't
    // received enough bytes, that's a fatal error.
    //
    void incomplete(bool closed);

    // Store a message indicating that we couldn't parse because
    // something was syntactically malformed, ie, not valid HTTP.
    //
    void syntax(std::string_view detail);

    // Store a message indicating that the content was too large
    // and we refused to download it.
    //
    void oversized();

    // Parse a response status line, such as "HTTP/1.1 200 OK"
    // returns false if we couldn't parse the whole line.
    //
    bool parse_status_line(std::string_view &view, bool closed);

    // Parse a request line, such as "GET /index.html HTTP/1.1"
    // returns false if we couldn't parse the whole line.
    //
    bool parse_request_line(std::string_view &view, bool closed);

    // Parse specific headers.
    //
    void parse_content_encoding(std::string_view value);
    void parse_content_length(std::string_view value);
    void parse_content_type(std::string_view value);
    void parse_location(std::string_view value);
    void parse_transfer_encoding(std::string_view value);

    // Parse a single response header.  Most headers are ignored.
    // If the header contains an error, the error is stored.
    //
    void parse_header(std::string_view header, std::string_view value);

    // Parse all of the headers, including the blank line after
    // the headers.  Return true if the view is at the end of the headers.
    //
    bool parse_headers(std::string_view &view, bool closed);
    
    // parse the content, for different transfer encodings.
    // Returns true if the view is at the end of the content.
    //
    bool parse_content_basic(std::string_view &view, bool closed);
    bool parse_content_chunked(std::string_view &view, bool closed);

    // Parse the content.  Return true if the view is at the end of the
    // content.
    //
    // - Uses the appropriate transfer-encoding specified in the headers.
    // - Decompresses the content using the appropriate content-encoding.
    // - Guesses a mimetype and charset if one was not specified in headers.
    // - If it's text, converts the charset to utf-8.
    //
    bool parse_content(std::string_view &view, bool closed);

public:
    // Construct a blank parser.
    //
    HttpParser();

    // Get the parsed status or error message.
    //
    int status() const { return status_; }
    bool errstatus() const { return (status_ >= 400) && (status_ <= 599); }
    eng::string error() const { return error_; }

    // Return true if the communication was complete.
    //
    bool complete() const { return status_ != 0; }

    // Get the first component of the path.
    // If the path is empty, return defval.
    eng::string first_path_component(std::string_view defval) const;

    // Get the parsed protocol.
    //
    bool http11() const { return http11_; }

    // Store the parsed fields into a lua table.
    //
    void store(LuaCoreStack &LS, LuaSlot tab) const;

    // The parser will not try to parse content longer than this.
    //
    static constexpr int64_t MAX_CONTENT_LENGTH = 1000000;

    // Parse a request or a response.
    //
    // You can only parse once.  If you need to parse again,
    // construct a new parser.
    //
    void parse_request(std::string_view view, bool closed);
    void parse_response(std::string_view view, bool closed, std::string_view method);

    // Store a status code and an error message, and clear the content.
    // This is generally used when the client detects an error,
    // such as a DNS lookup fail, a connection failed, an SSL negotiation
    // failed, or the like.
    //
    void fail(int status, std::string_view error);

    // Get or set the request ID.
    //
    int64_t request_id() const { return request_id_; }
    void set_request_id(int64_t v) { request_id_ = v; }

    // Return a debug string.
    //
    eng::string debug_string() const;

    // Synthesize an error and store it in lua.
    //
    static void store_fail(LuaCoreStack &LS, LuaSlot tab, int status, std::string_view error);

    // Convert a path component into a lua identifier.
    //
    // Returns the empty string if the path component cannot be converted
    // to a lua identifier.
    //
    static eng::string to_lua_identifier(std::string_view pathcomp);
};

class HttpClientRequestMap : public eng::map<int64_t, HttpClientRequest> {
public:
    void serialize(StreamBuffer *sb) const;
    void deserialize(StreamBuffer *sb);
};

using HttpParserVec = eng::vector<HttpParser>;

// This class is used by LpxServer to store the
// incoming and outgoing http channels.
//
class HttpChannel {
public:
    SharedChannel channel_;
    eng::string method_;
    int64_t parsed_bytes_;

    bool marked_for_deletion() const { return channel_ == nullptr; }
    HttpChannel() : parsed_bytes_(0) {}
};

using HttpChannelMap = eng::map<int64_t, HttpChannel>;
using HttpChannelVec = eng::vector<HttpChannel>;
