<?php
// Very basic WebSocket server in pure PHP
$host = '0.0.0.0';
$port = 8080;

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($socket, $host, $port);
socket_listen($socket);

$clients = [$socket];

echo "Server started on $host:$port\n";

while (true) {
    $read = $clients;
    $write = $except = null;
    socket_select($read, $write, $except, null);

    if (in_array($socket, $read)) {
        $new_socket = socket_accept($socket);
        $clients[] = $new_socket;
        $header = socket_read($new_socket, 1024);
        perform_handshake($header, $new_socket, $host, $port);
        echo "New client connected\n";
        $key = array_search($socket, $read);
        unset($read[$key]);
    }

    foreach ($read as $client_socket) {
        $data = socket_read($client_socket, 65536);
        if ($data === false || strlen($data) === 0) {
            $key = array_search($client_socket, $clients);
            unset($clients[$key]);
            socket_close($client_socket);
            echo "Client disconnected\n";
            continue;
        }

        $decoded_data = unmask($data);
        if ($decoded_data) {
            $payload = json_decode($decoded_data, true);
            if ($payload) {
                // If it's already a structured message, just broadcast it
                $response = mask($decoded_data);
            } else {
                // Fallback for raw text
                $response = mask(json_encode(['type' => 'message', 'data' => $decoded_data]));
            }
            
            foreach ($clients as $client) {
                if ($client != $socket && $client != $client_socket) {
                    @socket_write($client, $response, strlen($response));
                }
            }
        }
    }
}

function perform_handshake($receved_header, $client_conn, $host, $port) {
    $headers = array();
    $lines = preg_split("/\r\n/", $receved_header);
    foreach ($lines as $line) {
        $line = chop($line);
        if (preg_match('/\A(\S+): (.*)\z/', $line, $matches)) {
            $headers[$matches[1]] = $matches[2];
        }
    }

    if (!isset($headers['Sec-WebSocket-Key'])) return;

    $secKey = $headers['Sec-WebSocket-Key'];
    $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
    $upgrade = "HTTP/1.1 101 Switching Protocols\r\n" .
               "Upgrade: websocket\r\n" .
               "Connection: Upgrade\r\n" .
               "Sec-WebSocket-Accept: $secAccept\r\n\r\n";
    socket_write($client_conn, $upgrade, strlen($upgrade));
}

function unmask($text) {
    if (strlen($text) < 2) return null;
    $length = ord($text[1]) & 127;
    
    if ($length == 126) {
        if (strlen($text) < 8) return null;
        $masks = substr($text, 4, 4);
        $data = substr($text, 8);
    } elseif ($length == 127) {
        if (strlen($text) < 14) return null;
        $masks = substr($text, 10, 4);
        $data = substr($text, 14);
    } else {
        if (strlen($text) < 6) return null;
        $masks = substr($text, 2, 4);
        $data = substr($text, 6);
    }
    
    $decoded = "";
    for ($i = 0; $i < strlen($data); ++$i) {
        $decoded .= $data[$i] ^ $masks[$i % 4];
    }
    return $decoded;
}

function mask($text) {
    $b1 = 0x81; // FIN + Opcode 1 (text)
    $length = strlen($text);
    if ($length <= 125)
        $header = pack('CC', $b1, $length);
    elseif ($length > 125 && $length < 65536)
        $header = pack('CCn', $b1, 126, $length);
    elseif ($length >= 65536)
        $header = pack('CCNN', $b1, 127, $length);
    return $header . $text;
}
