/*
 * Copyright (c) 2021, Dex♪ <dexes.ttp@gmail.com>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/ByteBuffer.h>
#include <AK/RefCounted.h>
#include <AK/URL.h>
#include <AK/Weakable.h>
#include <LibCore/Object.h>
#include <LibWeb/Bindings/WindowObject.h>
#include <LibWeb/Bindings/Wrappable.h>
#include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/DOM/ExceptionOr.h>
#include <LibWeb/Forward.h>

#define ENUMERATE_WEBSOCKET_EVENT_HANDLERS(E) \
    E(onerror, HTML::EventNames::error)       \
    E(onclose, HTML::EventNames::close)       \
    E(onopen, HTML::EventNames::open)         \
    E(onmessage, HTML::EventNames::message)

namespace Protocol {
class WebSocketClient;
class WebSocket;
}

namespace Web::WebSockets {

class WebSocketClientManager : public Core::Object {
    C_OBJECT_ABSTRACT(WebSocketClientManager)
public:
    static WebSocketClientManager& the();

    RefPtr<Protocol::WebSocket> connect(const AK::URL&, String const& origin);

private:
    static ErrorOr<NonnullRefPtr<WebSocketClientManager>> try_create();
    WebSocketClientManager(NonnullRefPtr<Protocol::WebSocketClient>);

    RefPtr<Protocol::WebSocketClient> m_websocket_client;
};

class WebSocket final
    : public RefCounted<WebSocket>
    , public Weakable<WebSocket>
    , public DOM::EventTarget
    , public Bindings::Wrappable {
public:
    enum class ReadyState : u16 {
        Connecting = 0,
        Open = 1,
        Closing = 2,
        Closed = 3,
    };

    using WrapperType = Bindings::WebSocketWrapper;

    static NonnullRefPtr<WebSocket> create(HTML::Window& window, AK::URL& url)
    {
        return adopt_ref(*new WebSocket(window, url));
    }

    static DOM::ExceptionOr<NonnullRefPtr<WebSocket>> create_with_global_object(Bindings::WindowObject& window, const String& url);

    virtual ~WebSocket() override;

    using RefCounted::ref;
    using RefCounted::unref;

    String url() const { return m_url.to_string(); }

#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name)                  \
    void set_##attribute_name(Optional<Bindings::CallbackType>); \
    Bindings::CallbackType* attribute_name();
    ENUMERATE_WEBSOCKET_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE

    ReadyState ready_state() const;
    String extensions() const;
    String protocol() const;

    const String& binary_type() { return m_binary_type; };
    void set_binary_type(const String& type) { m_binary_type = type; };

    DOM::ExceptionOr<void> close(u16 code, const String& reason);
    DOM::ExceptionOr<void> send(const String& data);

private:
    virtual void ref_event_target() override { ref(); }
    virtual void unref_event_target() override { unref(); }
    virtual JS::Object* create_wrapper(JS::GlobalObject&) override;

    void on_open();
    void on_message(ByteBuffer message, bool is_text);
    void on_error();
    void on_close(u16 code, String reason, bool was_clean);

    explicit WebSocket(HTML::Window&, AK::URL&);

    NonnullRefPtr<HTML::Window> m_window;

    AK::URL m_url;
    String m_binary_type { "blob" };
    RefPtr<Protocol::WebSocket> m_websocket;
};

}