//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2024 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#include "server.hpp"
#include "request.hpp"
#include "reply.hpp"

namespace http {
namespace server4 {

server::server(boost::asio::io_context& io_context,
    const std::string& address, const std::string& port,
    std::function<void(const request&, reply&)> request_handler)
  : request_handler_(request_handler)
{
  tcp::resolver resolver(io_context);
  boost::asio::ip::tcp::endpoint endpoint =
    *resolver.resolve(address, port).begin();
  acceptor_.reset(new tcp::acceptor(io_context, endpoint));
}

// Enable the pseudo-keywords reenter, yield and fork.
#include <boost/asio/yield.hpp>

void server::operator()(boost::system::error_code ec, std::size_t length)
{
  // In this example we keep the error handling code in one place by
  // hoisting it outside the coroutine. An alternative approach would be to
  // check the value of ec after each yield for an asynchronous operation.
  if (!ec)
  {
    // On reentering a coroutine, control jumps to the location of the last
    // yield or fork. The argument to the "reenter" pseudo-keyword can be a
    // pointer or reference to an object of type coroutine.
    reenter (this)
    {
      // Loop to accept incoming connections.
      do
      {
        // Create a new socket for the next incoming connection.
        socket_.reset(new tcp::socket(acceptor_->get_executor()));

        // Accept a new connection. The "yield" pseudo-keyword saves the current
        // line number and exits the coroutine's "reenter" block. We use the
        // server coroutine as the completion handler for the async_accept
        // operation. When the asynchronous operation completes, the io_context
        // invokes the function call operator, we "reenter" the coroutine, and
        // then control resumes at the following line.
        yield acceptor_->async_accept(*socket_, *this);

        // We "fork" by cloning a new server coroutine to handle the connection.
        // After forking we have a parent coroutine and a child coroutine. Both
        // parent and child continue execution at the following line. They can
        // be distinguished using the functions coroutine::is_parent() and
        // coroutine::is_child().
        fork server(*this)();

        // The parent continues looping to accept the next incoming connection.
        // The child exits the loop and processes the connection.
      } while (is_parent());

      // Create the objects needed to receive a request on the connection.
      buffer_.reset(new std::array<char, 8192>);
      request_.reset(new request);

      // Loop until a complete request (or an invalid one) has been received.
      do
      {
        // Receive some more data. When control resumes at the following line,
        // the ec and length parameters reflect the result of the asynchronous
        // operation.
        yield socket_->async_read_some(boost::asio::buffer(*buffer_), *this);

        // Parse the data we just received.
        std::tie(parse_result_, std::ignore)
          = request_parser_.parse(*request_,
              buffer_->data(), buffer_->data() + length);

        // An indeterminate result means we need more data, so keep looping.
      } while (parse_result_ == request_parser::indeterminate);

      // Create the reply object that will be sent back to the client.
      reply_.reset(new reply);

      if (parse_result_ == request_parser::good)
      {
        // A valid request was received. Call the user-supplied function object
        // to process the request and compose a reply.
        request_handler_(*request_, *reply_);
      }
      else
      {
        // The request was invalid.
        *reply_ = reply::stock_reply(reply::bad_request);
      }

      // Send the reply back to the client.
      yield boost::asio::async_write(*socket_, reply_->to_buffers(), *this);

      // Initiate graceful connection closure.
      socket_->shutdown(tcp::socket::shutdown_both, ec);
    }
  }

  // If an error occurs then the coroutine is not reentered. Consequently, no
  // new asynchronous operations are started. This means that all shared_ptr
  // references will disappear and the resources associated with the coroutine
  // will be destroyed automatically after this function call returns.
}

// Disable the pseudo-keywords reenter, yield and fork.
#include <boost/asio/unyield.hpp>

} // namespace server4
} // namespace http
