#ifndef WT_WWEBSOCKETRESOURCE_H_
#define WT_WWEBSOCKETRESOURCE_H_

#include "Wt/AsioWrapper/asio.hpp"

#include "Wt/Http/Request.h"
#include "Wt/Http/Response.h"

#include "Wt/WResource.h"

#include <mutex>

namespace http {
  namespace server {
    class Connection;
  }
}

namespace Wt {
class WebSocketConnection;
class WWebSocketConnection;
class WWebSocketResource;

/* Wt internal
 *
 * This resource is used to route the incoming request to the right
 * WWebSocketResource. When a new request comes in, that wishes to set up
 * a new WebSocket connection this will contain some websocket-specific
 * headers. This request is handled in the normal event loop of Wt. This
 * will ensure that WResource::handleRequest() is called.
 *
 * When a WResource is exposed by the WServer or WApplication, its path
 * can be used in requests, and the instance will correctly handle the
 * request to the resource. In this specific case, this will ensure that
 * the related WWebSocketResource is retrieved and can be used to set up
 * a new WWebSocketConnection.
 */
class WT_API WebSocketHandlerResource final : public WResource
{
public:
  explicit WebSocketHandlerResource(WWebSocketResource* resource);
  ~WebSocketHandlerResource();

  void moveSocket(const Http::Request& request, const std::shared_ptr<WebSocketConnection>& socketConnection);

protected:
  void handleRequest(const Http::Request& request, Http::Response& response) final;

private:
  WWebSocketResource* resource_ = nullptr;
};

/*! \class WWebSocketResource Wt/WWebSocketResource.h
 *  \brief A resource that manages a WebSocket endpoint
 *
 * This resource enables the creation of endpoints that can be accessed
 * to set up WebSocket connections. This implementation adheres to
 * <a href="https://datatracker.ietf.org/doc/html/rfc6455">RFC-6455</a>.
 * The implementation does not support Sec-WebSocket-Protocol or
 * Sec-WebSocket-Extension.
 *
 * Like WResource, a WWebSocketResource can be global (aka static) or
 * session-private (aka dynamic).
 * A global WWebSocketResource is deployed at a fixed URL by calling
 * WServer::addResource(). It is accessible by anybody that has access
 * to its URL. It is the responsibility of the developer to implement a
 * suitable access control mechanism if needed.

 * The private version of this is an endpoint unique for a session. It is
 * added to the widget tree of a WApplication instance (and
 * WServer::addResource() is thus not used). The URL is generated by %Wt
 * and access is protected to the session by using a session-specific
 * secret as configured by the 'tracking' configuration option in
 * wt_config.xml. Its path can still be managed, and set to any valid
 * name.
 *
 * This class enables an endpoint to be created (and moved, using
 * setInternalPath()).
 *
 * While it has <code>Resource</code> in its name, it is not actually a
 * WResource, and has no WResource::handleRequest() method.
 *
 * When a new connection comes in to the WWebSocketResource, that is a
 * valid WebSocket request, asking to be upgraded, handleConnect() will
 * be called. This creates a new instance of a WWebSocketConnection. This
 * connection can be used to send and receive data over. Upon creation,
 * all state that is configured on the resource is also put on the
 * connection, meaning the limits on frame and message size, the ping
 * delay and timeout, and whether the update lock is taken on updates.
 *
 * To use this resource, one has to implement the handleConnect()
 * method, which should create a WWebSocketConnection. Or any specialized
 * class that inherits from it.
 *
 * \if cpp
 * \code
 * class MyWebSocketResource final : public Wt::WWebSocketResource
 * {
 * public:
 *   std::unique_ptr<Wt::WWebSocketConnection> handleConnect(const Wt::Http::Request& request) final
 *   {
 *     auto connection = std::make_unique<Wt::WWebSocketConnection>(this, Wt::WServer::instance()->ioService());
 *     newConnectionMade_.emit(connection.get());
 *     return connection;
 *   }
 * };
 * \endcode
 * \endif
 *
 * \note This class is only supported when using the wthttp frontend.
 */
class WT_API WWebSocketResource : public WObject
{
public:
  //! Default constructor
  WWebSocketResource();

  //! Destructor
  virtual ~WWebSocketResource();

  /*! \brief Handles a new connection to the resource
   *
   * When a new connection is being made to the resource, this function
   * is called, to create a new WWebSocketConnection.
   *
   * The connection can be used to interact with the WebSocket stream.
   * The \p request that is passed to it is purely indicative. It allows
   * for inspection of the request and its headers, so that additional
   * information from it may be used further.
   */
  virtual std::unique_ptr<WWebSocketConnection> handleConnect(const Http::Request& request) = 0;

  /*! \brief Shuts down the resource
   *
   * The resource is closed down, meaning that all its active connections
   * are immediately closed. This is not a completely graceful shutdown,
   * in that clients (on the other side of the connection) are not
   * notified of the closing with a close frame. This could potentially
   * lead to the resource having to wait a while for an actual shutdown.
   *
   * Since this function is called in WServer::stop(), in case the %Wt
   * http library is used, we want an immediate shutdown here.
   */
  virtual void shutdown();

  /*! \brief Configures the internal path used for the resource
   *
   * This path is only useful for private resources. It defines the
   * internal path portion of the URL of the resource.
   *
   * If no path is configured, a URL will be generated by the library.
   */
  void setInternalPath(const std::string& path);

  /*! \brief Retrieves the current internal path
   *
   * \sa setInternalPath
   */
  std::string internalPath() const;

  /*! \brief Retrieves the current URL
   *
   * This method returns the full URL to be used to connect to this
   * resource.
   *
   * For global resources, this is the last URL at which the resource is
   * added to WServer. For private resources, this URL is influenced by
   * setInternalPath() and additional query parameters to provide session
   * secrecy.
   *
   * \sa setInternalPath
   */
  std::string url() const;


  /*! \brief Sets the maximum received frame and message sizes
   *
   * A WebSocket frame is a single transaction unit. It contains a header
   * and data. The header itself will contain information on the frame,
   * and specify how long it is. If this exceeds the limit, the received
   * frame is discarded.
   *
   * Likewise, when a received message consists of multiple frames, the
   * message as a whole is discarded if it exceeds the message limit.
   *
   * By default the maximum frame size is 10MB (1024 * 1024 * 10), and
   * the maxmimum message size is 5 times as big (50MB). This can be
   * increased to any arbitrary value, but the absolute maximum frame
   * size of a WebSocket is still enforced, which is 2 ^ 63 bytes.
   */
  void setMaximumReceivedSize(size_t frame, size_t message);

  /*! \brief Sets whether the resource takes the update lock on events
   *
   * Setting this to \p true will only affect private resources. By
   * default it is enabled.
   *
   * If enabled, handleConnect(), WWebSocketResource::handleMessage(),
   * WWebSocketResource::handleError(), WWebSocketResource::closed(), and
   * WWebSocketResource::done() are all called while holding the
   * WApplication::UpdateLock. The widget tree can thus safely be
   * accessed and modified from these functions.
   */
  void setTakesUpdateLock(bool canTake);

  /*! \brief Sets the ping-pong configuration.
   *
   * The ping-pong system is a way to ensure that a connection remains
   * active, and that both parties are still committed to the
   * interaction. Any of both parties can send a ping frame, to which
   * the other has to respond with a pong as soon as reasonable. They do
   * not have to drop current pending frames, but need to ensure that the
   * pong is sent out within a reasonable small amount of time.
   *
   * This function allows the managing of this functionality. The first
   * parameter \p intervalSeconds will set every how often a ping frame
   * is sent out from the server to a connected client. The second
   * parameter \p timeoutSeconds indicates how long the client has to
   * respond. If they do not respond within the set limit, the connection
   * will be considered dropped, and will be terminated. Both of these
   * parameters are in seconds.
   *
   * By default the used values are 30 seconds for the ping interval, and
   * 60 seconds for the timeout.
   *
   * This system can be disabled by setting \p intervalSeconds to 0.
   */
  void setPingTimeout(int intervalSeconds, int timeoutSeconds);

  /*! \brief Returns the maximum size of a single received frame.
   *
   * \sa setMaximumReceivedSize
   */
  size_t maximumReceivedFrameSize() const { return frameSize_; }

  /*! \brief Returns the maximum received size of a single message.
   *
   * \sa setMaximumReceivedSize
   *
   * \note A single message can span multiple frames.
   */
  size_t maximumReceivedMessageSize() const { return messageSize_; }

  /*! \brief Returns whether the application update lock is used.
   *
   * \sa setTakesUpdateLock
   */
  bool takesUpdateLock() const { return takesUpdateLock_; }

  /*! \brief Returns the interval (in seconds) between ping frames sent.
   *
   * \sa setPingTimeout
   */
  int pingInterval() const { return pingInterval_; }

  /*! \brief Returns the maximum duration (in second) between sent ping
   * frames, and received pong frames.
   *
   * \sa setPingTimeout
   */
  int pingTimeout() const { return pingTimeout_; }

  // Wt internal
  std::shared_ptr<WebSocketHandlerResource> handleResource() const { return resource_; }

private:
  std::shared_ptr<WebSocketHandlerResource> resource_;

  std::recursive_mutex clientsMutex_;
  std::vector<std::unique_ptr<WWebSocketConnection>> clients_;

  WWebSocketConnection* registerConnection(std::unique_ptr<WWebSocketConnection> connection);
  void removeConnection(WWebSocketConnection* connection);

  size_t frameSize_;
  size_t messageSize_;

  bool takesUpdateLock_;

  int pingInterval_;
  int pingTimeout_;

  WApplication* app_ = nullptr;

  friend class WebSocketHandlerResource;
  friend class WWebSocketConnection;
};
}
#endif // WT_WWEBSOCKETRESOURCE_H_
