// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include "LocalproxyConfig.h"
#include "WebSocketStream.h"

#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>

using std::shared_ptr;
using std::unique_ptr;
using std::function;
using boost::system::error_code;
using tcp = boost::asio::ip::tcp;
using logger = boost::log::sources::severity_logger<boost::log::trivial::severity_level>;
namespace http = boost::beast::http;
namespace ssl = boost::asio::ssl;
using BoostCallbackFunc = function<void(error_code)>;

namespace aws {
    namespace iot {
        namespace securedtunneling {
            /**
             * This class will act as an adapter to do the extra work needed to establish a TCP tunnel and then
             * hand control back to the caller to continue its execution. This class is designed to fit in boost asio's
             * single-threaded event loop model so it's not thread-safe and it's the responsibility of the consumer to
             * synchronize if they decided to access it from different threads.
             */
            class WebProxyAdapter {
            private:
                /**
                 * A pointer the boost logger that will be used by this adapter for logging.
                 */
                logger* log;
                /**
                 * A copy of the localproxy configurations
                 */
                const LocalproxyConfig &localproxy_config;
                /**
                 * A request object that will be used while establishing the TCP tunnel, it needs to be a member field
                 * because all work is done asynchronously so this object needs to outlive the function scope.
                 */
                http::request<http::empty_body> request;
                /**
                 * A response object that will be used while establishing the TCP tunnel, it needs to be a member field
                 * because all work is done asynchronously so this object needs to outlive the function scope.
                 */
                http::response<http::string_body> response;
                /**
                 * A request buffer that will be used while establishing the TCP tunnel, it needs to be a member field
                 * because all work is done asynchronously so this object needs to outlive the function scope.
                 */
                boost::asio::mutable_buffer read_buffer;
                WebSocketStream* websocket_stream = nullptr;
                unique_ptr<BoostCallbackFunc> on_tcp_tunnel = nullptr;
                /**
                 * An async member function that will be invoked internally once a TCP connection is established between
                 * the localproxy and the Web proxy, it's responsible for sending the HTTP CONNECT request to
                 * start the the TCP tunnel.
                 *
                 * @param on_tcp_tunnel The callback that will be invoked once the tcp tunnel is established
                 */
                void on_tcp_connect();
                /**
                 * A async function that will be invoked internally once the the HTTP CONNECT request is sent, it is
                 * responsible for reading and parsing the response of the CONNECT request and handing back control the
                 * the callback provided by the adapter consumer "on_tcp_tunnel" with the appropirate input based on
                 * whether the TCP tunnel was established successfully or not.
                 *
                 * @param on_tcp_tunnel The callback that will be invoked once the tcp tunnel is established
                 */
                void on_http_connect_write();
                void async_ssl_handshake();
            public:
                /**
                 * The public constructor
                 *
                 * @param log A pointer the boost logger that will be used by this adapter for logging.
                 * @param localproxy_config the localproxy configurations
                 */
                WebProxyAdapter(logger* log,
                                  const LocalproxyConfig &localproxy_config);
                /**
                 * An async public method to establish the TCP tunnel
                 *
                 * @param on_tcp_tunnel The callback that will be invoked once the tcp tunnel is established
                 * @param tcp_socket The TCP socket over which the TCP tunnel will be established
                 * @param web_proxy_endpoint The IP of the Web proxy
                 */
                void async_connect(BoostCallbackFunc on_tcp_tunnel,
                                   const shared_ptr<WebSocketStream> &wss,
                                   const tcp::endpoint &web_proxy_endpoint);
            };
        }
    }
}

enum class WebProxyAdapterErrc
{
    Success = 0,
    TcpConnectError = 1,
    HttpWriteRequestError = 2,
    ServerError = 3,
    ClientError = 4,
    RedirectionError = 5,
    OtherHttpError = 6,
    SslHandshakeError = 7,
};

namespace boost
{
    namespace system
    {
        // Tell the C++ 11 STL metaprogramming that enum WebProxyAdapterErrc
        // is registered with the standard error code system
        template <> struct is_error_code_enum<WebProxyAdapterErrc> : std::true_type
        {
        };
    }
}

// Define a custom error code category derived from boost::system::error_category
class WebProxyAdapterErrc_category : public boost::system::error_category
{
public:
    // Return a short descriptive name for the category
    virtual const char *name() const noexcept override final { return "WebProxyAdapterError"; }
    // Return what each enum means in text
    virtual std::string message(int c) const override final
    {
        switch(static_cast<WebProxyAdapterErrc>(c))
        {
            case WebProxyAdapterErrc::Success:
                return "TCP Tunnel established successfully";
            case WebProxyAdapterErrc::ServerError:
                return "The Web proxy server responded with 5xx to the HTTP CONNECT request";
            case WebProxyAdapterErrc::ClientError:
                return "The Web proxy server responded with 4xx to the HTTP CONNECT request";
            case WebProxyAdapterErrc::RedirectionError:
                return "The Web proxy server responded with 3xx to the HTTP CONNECT request";
            case WebProxyAdapterErrc::TcpConnectError:
                return "Failed to establish the TCP connection to the Web proxy";
            case WebProxyAdapterErrc::OtherHttpError:
                return "The Web proxy didn't respond with 200 response code.";
            case WebProxyAdapterErrc::HttpWriteRequestError:
                return "Failed to send to the CONNECT request to the Web proxy";
            case WebProxyAdapterErrc::SslHandshakeError:
                return "Failed to perform the SSL handshake with the Web proxy";
            default:
                return "unknown";
        }
    }
};

// Declare a global function returning a static instance of the WebProxyAdapter Error category
extern inline const WebProxyAdapterErrc_category &WebProxyAdapterErrc_category()
{
    static class WebProxyAdapterErrc_category c;
    return c;
}


// Overload the global make_error_code() free function with our
// custom enum. It will be found via ADL by the compiler if needed.
inline boost::system::error_code make_error_code(WebProxyAdapterErrc e)
{
    return {static_cast<int>(e), WebProxyAdapterErrc_category()};
}