#include "server.hpp"
#include <utility>
#include <boost/beast/core/basic_stream.hpp>


namespace http::server {
    ConnectionManager::ConnectionManager() = default;

    void ConnectionManager::start(const connection_ptr& c) {
        connections_.insert(c);
        c->start();
    }

    void ConnectionManager::stop(const connection_ptr& c) {
        connections_.erase(c);
        c->stop();
    }

    void ConnectionManager::stop_all() {
        for (auto& c: connections_)
            c->stop();
        connections_.clear();
    }

    Server::Server(const std::string &address, const std::string &port)
        : io_context_(1), signals_(io_context_), acceptor_(io_context_) {
        // Register to handle the signals that indicate when the server should exit.
        // It is safe to register for the same signal multiple times in a program,
        // provided all registration for the specified signal is made through Asio.
        signals_.add(SIGINT);
        signals_.add(SIGTERM);
#if defined(SIGQUIT)
        signals_.add(SIGQUIT);
#endif // defined(SIGQUIT)

        doAwaitStop();

        // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
        boost::asio::ip::tcp::resolver resolver(io_context_);
        boost::asio::ip::tcp::endpoint endpoint =
                *resolver.resolve(address, port).begin();
        acceptor_.open(endpoint.protocol());
        acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
        acceptor_.bind(endpoint);
        acceptor_.listen(128);

        doAccept();
    }

    Server::Server(const std::string &address, const std::string &port, std::shared_ptr<boost::asio::ssl::context> ctx):
        ssl_ctx(std::move(ctx)), io_context_(1), signals_(io_context_), acceptor_(io_context_) {
        // Register to handle the signals that indicate when the server should exit.
        // It is safe to register for the same signal multiple times in a program,
        // provided all registration for the specified signal is made through Asio.
        signals_.add(SIGINT);
        signals_.add(SIGTERM);
#if defined(SIGQUIT)
        signals_.add(SIGQUIT);
#endif // defined(SIGQUIT)

        doAwaitStop();

        // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
        boost::asio::ip::tcp::resolver resolver(io_context_);
        boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(address, port).begin();
        acceptor_.open(endpoint.protocol());
        acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
        acceptor_.bind(endpoint);
        acceptor_.listen(128);

        doAccept();
    }

    void Server::run() {
        // The io_context::run() call will block until all asynchronous operations
        // have finished. While the server is running, there is always at least one
        // asynchronous operation outstanding: the asynchronous accept call waiting
        // for new incoming connections.
        io_context_.run();
    }

    void Server::doAccept() {
        acceptor_.async_accept(
            [this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) {
                // Check whether the server was stopped by a signal before this
                // completion handler had a chance to run.
                if (!acceptor_.is_open()) {
                    return;
                }

                if (!ec) {
                    if (ssl_ctx == nullptr) {
                        connection_manager_.start(std::make_shared<Connection>(std::move(socket), connection_manager_, [this](const auto& req, auto& rep) { this->requestHandler(req, rep); }));
                    } else {
                        connection_manager_.start(std::make_shared<SslConnection>(std::move(socket), connection_manager_, [this](const auto& req, auto& rep) { this->requestHandler(req, rep); }, ssl_ctx));
                    }
                }

                doAccept();
            });
    }

    void Server::doAwaitStop() {
        signals_.async_wait(
            [this](boost::system::error_code /*ec*/, int /*signo*/) {
                // The server is stopped by cancelling all outstanding asynchronous
                // operations. Once all operations have finished the io_context::run()
                // call will exit.
                acceptor_.close();
                connection_manager_.stop_all();
            });
    }

    void Server::requestHandler(const Request &req, reply &rep) {
        // Request path must be absolute and not contain "..".
        if (req.url->path.empty() || req.url->path[0] != '/' || req.url->path.find("..") != std::string::npos) {
            stockReply(bad_request, rep);
            return;
        }

        rep.status = ok;
        rep.headers.clear();
        rep.content.clear();

        for (auto& res: resources) {
            if (res->path != req.url->path) {
                continue;
            }
            res->handle(req, rep);
            return;
        }

        stockReply(not_found, rep);
    }

} // namespace http::Server
