/*
 * Copyright (C) 2024 Igalia S.L.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once

#include "CommandResult.h"
#include "HTTPServer.h"
#include <map>
#include <optional>
#include <vector>
#include <wtf/HashMap.h>
#include <wtf/JSONValues.h>
#include <wtf/RefCountedAndCanMakeWeakPtr.h>
#include <wtf/Vector.h>
#include <wtf/WeakPtr.h>
#include <wtf/text/WTFString.h>

#if USE(SOUP)
#include <wtf/glib/GRefPtr.h>
typedef struct _SoupServer SoupServer;
typedef struct _SoupWebsocketConnection SoupWebsocketConnection;
#endif

namespace WebDriver {

class Session;
class WebDriverService;

struct Command {
    // Defined as js-uint, but in practice opaque for the remote end.
    String id;
    String method;
    RefPtr<JSON::Object> parameters;

    static std::optional<Command> fromData(const char* data, size_t dataLength);
};

// https://w3c.github.io/webdriver-bidi/#websocket-listener
// A WebSocket listener is a network endpoint that is able to accept incoming WebSocket connections.
struct WebSocketListener : public RefCounted<WebSocketListener> {
    static RefPtr<WebSocketListener> create(const String& host, unsigned port, bool secure, const std::vector<String>& resources = { })
    {
        return adoptRef(*new WebSocketListener(host, port, secure, resources));
    }

    String host;
    unsigned port { 0 };
    bool isSecure { false };
    std::vector<String> resources { };

private:
    WebSocketListener(const String& host, unsigned port, bool isSecure, const std::vector<String>& resources)
        : host(host)
        , port(port)
        , isSecure(isSecure)
        , resources(resources)
    {
    }
};


class WebSocketMessageHandler {
public:

#if USE(SOUP)
    using Connection = GRefPtr<SoupWebsocketConnection>;
#endif

    struct Message {
        // Optional connection, as the message might be generated without a connection object available (e.g. inside a method handler).
        // In this case, it gets associated to the connection when sending the message back to the client.
        Connection connection;
        const CString payload;

        static Message fail(CommandResult::ErrorCode, std::optional<Connection>, std::optional<String> errorMessage = std::nullopt, std::optional<int> commandId = std::nullopt);
        static Message reply(const String& type, unsigned id, Ref<JSON::Value>&& result);
    };

    virtual bool acceptHandshake(HTTPRequestHandler::Request&&) = 0;
    virtual void handleMessage(Message&&, Function<void(Message&&)>&& replyHandler) = 0;
    virtual void clientDisconnected(const Connection&) = 0;
private:
};

class WebSocketServer : public RefCountedAndCanMakeWeakPtr<WebSocketServer> {
public:
    static Ref<WebSocketServer> create(WebSocketMessageHandler& messageHandler)
    {
        return adoptRef(*new WebSocketServer(messageHandler));
    }
    virtual ~WebSocketServer() = default;

    std::optional<String> listen(const String& host, unsigned port);
    void disconnect();

    WebSocketMessageHandler& messageHandler() { return m_messageHandler; }

    const RefPtr<WebSocketListener>& listener() const { return m_listener; }

    void addStaticConnection(WebSocketMessageHandler::Connection&&);
    bool isStaticConnection(const WebSocketMessageHandler::Connection&);
    void removeStaticConnection(const WebSocketMessageHandler::Connection&);

    void addConnection(WebSocketMessageHandler::Connection&&, const String& sessionId);
    String sessionID(const WebSocketMessageHandler::Connection&) const;
    std::optional<WebSocketMessageHandler::Connection> connection(const String& sessionId);
    void removeConnection(const WebSocketMessageHandler::Connection&);

    RefPtr<WebSocketListener> startListening(const String& sessionId);
    String getResourceName(const String& sessionId);
    String getWebSocketURL(const RefPtr<WebSocketListener>, const String& sessionId);
    String getSessionID(const String& resource);
    void sendMessage(const String& sessionId, const String& message);
    void sendErrorResponse(const String& sessionId, std::optional<unsigned> commandId, CommandResult::ErrorCode, const String& errorMessage, std::optional<String> stacktrace = std::nullopt);

    // Non-spec method
    void removeResourceForSession(const String& sessionId);
    void disconnectSession(const String& sessionId);

private:
    explicit WebSocketServer(WebSocketMessageHandler&);

    void sendMessage(WebSocketMessageHandler::Connection, const String& message);
    void sendErrorResponse(WebSocketMessageHandler::Connection, std::optional<unsigned> commandId, CommandResult::ErrorCode, const String& errorMessage, std::optional<String> stacktrace = std::nullopt);
    WebSocketMessageHandler& m_messageHandler;
    String m_listenerURL;
    RefPtr<WebSocketListener> m_listener;
    HashMap<WebSocketMessageHandler::Connection, String> m_connectionToSession;

#if USE(SOUP)
    GRefPtr<SoupServer> m_soupServer;
    std::vector<GRefPtr<SoupWebsocketConnection>> m_staticConnections;
#endif
};

}
