// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.

#pragma once

#include "caf/byte_buffer.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/dictionary.hpp"

#include <cstddef>
#include <string>
#include <string_view>

namespace caf::net::web_socket {

/// Wraps state and algorithms for the WebSocket client handshake as defined in
/// RFC 6455.
class CAF_NET_EXPORT handshake {
public:
  // -- member types -----------------------------------------------------------

  using key_type = std::array<std::byte, 16>;

  // -- constants --------------------------------------------------------------

  /// Maximum size for HTTP request and response messages. A handshake should
  /// usually fit into 200-300 Bytes, so 2KB is more than enough.
  static constexpr uint32_t max_http_size = 2048;

  // -- constructors, destructors, and assignment operators --------------------

  handshake() noexcept;

  handshake(const handshake&) = default;

  handshake(handshake&&) noexcept = default;

  handshake& operator=(const handshake&) = default;

  handshake& operator=(handshake&&) noexcept = default;

  // -- properties -------------------------------------------------------------

  /// Returns the 16-byte `key` for the opening handshake.
  [[nodiscard]] const auto& key() const noexcept {
    return key_;
  }

  /// Sets the 16-byte `key` for the opening handshake.
  void key(key_type new_key) noexcept {
    key_ = new_key;
  }

  /// Tries to assign a key from base64 input.
  bool assign_key(std::string_view base64_key);

  /// Returns the key for the response message, i.e., what the server puts into
  /// the HTTP field `Sec-WebSocket-Accept`.
  std::string response_key() const;

  /// Returns all configured header fields, except the key.
  [[nodiscard]] const auto& fields() const noexcept {
    return fields_;
  }

  /// Checks whether at least one bit in the key is one. A default constructed
  /// header object fills the key with zeros.
  [[nodiscard]] bool has_valid_key() const noexcept;

  /// Checks whether all mandatory fields were provided by the user.
  [[nodiscard]] bool has_mandatory_fields() const noexcept;

  /// Fills the key with pseudo-random numbers generated by `std::minstd_rand`
  /// with a seed chosen from `std::random_device`.
  void randomize_key();

  /// Fills the key with pseudo-random numbers generated by `std::minstd_rand`,
  /// initialized with `seed`.
  void randomize_key(unsigned seed);

  /// Sets a value for the mandatory WebSocket endpoint, i.e., the Request-URI
  /// component of the GET method according to RFC 2616.
  /// @param value Identifies the endpoint that should handle the request.
  void endpoint(std::string value) {
    fields_["_endpoint"] = std::move(value);
  }

  /// Checks whether the handshake has an `endpoint` defined.
  bool has_endpoint() const noexcept {
    return fields_.contains("_endpoint");
  }

  /// Sets a value for the mandatory `Host` field.
  /// @param value The Internet host and port number of the resource being
  ///              requested, as obtained from the original URI given by the
  ///              user or referring resource.
  void host(std::string value) {
    fields_["_host"] = std::move(value);
  }

  /// Checks whether the handshake has an `host` defined.
  bool has_host() const noexcept {
    return fields_.contains("_host");
  }

  /// Sets a value for the optional `Origin` field.
  /// @param value Indicates where the GET request originates from. Usually only
  ///              sent by browser clients.
  void origin(std::string value) {
    fields_["Origin"] = std::move(value);
  }

  /// Sets a value for the optional `Sec-WebSocket-Protocol` field.
  /// @param value A comma-separated list of values indicating which protocols
  ///              the client would like to speak, ordered by preference.
  void protocols(std::string value) {
    fields_["Sec-WebSocket-Protocol"] = std::move(value);
  }

  /// Sets a value for the optional `Sec-WebSocket-Extensions` field.
  /// @param value A comma-separated list of values indicating which extensions
  ///              the client would like to speak, ordered by preference.
  void extensions(std::string value) {
    fields_["Sec-WebSocket-Extensions"] = std::move(value);
  }

  // -- HTTP generation and validation -----------------------------------------

  /// Writes the HTTP 1.1 request message to `buf`.
  /// @pre `has_mandatory_fields()`
  void write_http_1_request(byte_buffer& buf) const;

  /// Writes the HTTP 1.1 response message to `buf`.
  /// @pre `has_valid_key()`
  void write_http_1_response(byte_buffer& buf) const;

  /// Checks whether the `http_response` contains a HTTP 1.1 response to the
  /// generated HTTP GET request. A valid response contains:
  /// - HTTP status code 101 (Switching Protocols).
  /// - An `Upgrade` field  with the value `websocket`.
  /// - A `Connection` field with the value `Upgrade`.
  /// - A `Sec-WebSocket-Accept` field with the value `response_key()`.
  bool is_valid_http_1_response(std::string_view http_response) const;

private:
  // -- utility ----------------------------------------------------------------

  std::string_view lookup(std::string_view field_name) const noexcept;

  // -- member variables -------------------------------------------------------

  key_type key_;
  dictionary<std::string> fields_;
};

} // namespace caf::net::web_socket
