/*
 * Copyright 2024 Rive
 */

#pragma once

#include <memory>
#include <string>
#include <stdint.h>

#ifdef __EMSCRIPTEN__
#include <emscripten/websocket.h>
#include <arpa/inet.h>
#include <deque>
#define SOCKET EMSCRIPTEN_WEBSOCKET_T
#elif defined(_WIN32)
#include "WinSock2.h"
#else
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define SOCKET int
#endif

// Simple utility for connecting a tool to its python harness via TCP socket.
class TCPClient
{
public:
    constexpr static uint32_t HANDSHAKE_TOKEN = 0xfee1600d;
    constexpr static uint32_t SHUTDOWN_TOKEN = 0xfee1dead;

    static std::unique_ptr<TCPClient> Connect(
        const char* serverAddress /*server:port*/);
    ~TCPClient();

    const char* serverAddress() const { return m_serverAddress.c_str(); }
    std::unique_ptr<TCPClient> clone() const;

    void sendall(const void* data, size_t byteCount);
    void recvall(void* data, size_t byteCount);

    // Send a 4-byte uint32_t to the server.
    void send4(uint32_t);

    // Receive a 4-byte uint32_t to the server.
    uint32_t recv4();

    // Send "HANDSHAKE_TOKEN" to the server.
    void sendHandshake();

    // Read 4 bytes from the server and verify they are "HANDSHAKE_TOKEN".
    void recvHandshake();

    void sendString(const std::string& str);

    std::string recvString();

private:
    TCPClient(const char* serverAddress /*server:port*/, bool* success);

    uint32_t send(const char* data, uint32_t size);
    uint32_t recv(char* buff, uint32_t size);

#ifdef _WIN32
    static int GetLastError() { return WSAGetLastError(); }
#else
    static int GetLastError() { return errno; }
#endif

    const std::string m_serverAddress;
    SOCKET m_sockfd;

#ifdef __EMSCRIPTEN__
    static EM_BOOL OnWebSocketOpen(int eventType,
                                   const EmscriptenWebSocketOpenEvent* e,
                                   void* userData);
    static EM_BOOL OnWebSocketMessage(int eventType,
                                      const EmscriptenWebSocketMessageEvent*,
                                      void* userData);
    static EM_BOOL OnWebSocketError(int eventType,
                                    const EmscriptenWebSocketErrorEvent*,
                                    void* userData);

    enum class WebSocketStatus
    {
        pendingConnection,
        open,
        error,
    };

    WebSocketStatus m_webSocketStatus = WebSocketStatus::pendingConnection;
    std::deque<uint8_t> m_serverMessages;
#endif
};
