CTCPSocket      *tcp = TCPSocket(AF_INET);
CTCPTreeQueue   *connections = CAlloc(sizeof(CTCPTreeQueue)); // head
U8              *buffer = CAlloc(ETHERNET_FRAME_SIZE);

QueueInit(connections);

U0 ChatServerKill()
{
    CTCPTreeQueue   *conn = connections->next;
    CTCPTreeQueue   *next_conn;

    while (conn != connections)
    {
        "\nClosing socket @ 0x%0X\n", conn->socket;
        TCPSocketClose(conn->socket);
        next_conn = conn->next;
        Free(conn);
        conn = next_conn;
    }

    "\nClosing listening socket.\n";
    TCPSocketClose(tcp);

    return;
}

U0 ChatServerBroadcast(CTCPSocket *tcp_socket, I64 length)
{ // Broadcast length bytes of msg in buffer to all but original socket.
    CTCPTreeQueue   *conn = connections->next;
    CTCPSocket      *dest_socket;
    U8              *ip_string;
    CIPV4Address     addr;
    U8              *message;
    U8              *message_prefix;

    while (conn != connections)
    {
        dest_socket = conn->socket;

        if (dest_socket != tcp_socket)
        {
            addr.address = EndianU32(dest_socket->destination_address(CSocketAddressIPV4).address.address);

            ip_string = NetworkToPresentation(AF_INET, &addr);
            // TODO: is NetworkToPresentation backwards? or, do socket addrs store BE or LE ?

            "\nBroacasting msg to %s.\n", ip_string;

            addr.address = EndianU32(tcp_socket->destination_address(CSocketAddressIPV4).address.address);

            ip_string = NetworkToPresentation(AF_INET, &addr);
            // TODO: is NetworkToPresentation backwards? or, do socket addrs store BE or LE ?

            message_prefix = MStrPrint("$BG,PURPLE$$BLACK$<%s>$FG$$BG$ %%0%dts", ip_string, length);

            message = MStrPrint(message_prefix, buffer);

            TCPSocketSendString(dest_socket, message);

            Free(message);
            Free(message_prefix);
            Free(ip_string);

        }

        conn = conn->next;
    }

    return;
}

U0 ChatServerBroadcastDisconnect()
{
    CTCPTreeQueue   *conn = connections->next;
    CTCPSocket      *conn_socket;
    U8              *message = MStrPrint("$BG,LTGRAY$$DKGRAY$Client disconnected. Connected clients: %d$FG$$BG$",
                                            QueueCount(connections));
    while (conn != connections)
    {
        conn_socket = conn->socket;

        TCPSocketSendString(conn_socket, message);

        conn = conn->next;
    }

    Free(message);

}

U0 ChatServerReceive()
{
    CTCPTreeQueue   *conn = connections->next;
    CTCPTreeQueue   *next_conn;
    CTCPSocket      *socket;
    I64              message_len;
    U8              *ip_string;
    CIPV4Address     addr;


    while (conn != connections)
    {
        socket = conn->socket;
        message_len = TCPSocketReceive(socket, buffer, ETHERNET_FRAME_SIZE);

        if (message_len == 0)
        {
            "\nClosing a connection.\n";
            socket->timeout = TCP_TIMEOUT;
            TCPSocketClose(socket);

            next_conn = conn->next;
            QueueRemove(conn);
            Free(conn);
            conn = next_conn;

            ChatServerBroadcastDisconnect();

        }
        else if (message_len > 0)
        {
            addr.address = EndianU32(socket->destination_address(CSocketAddressIPV4).address.address);

            ip_string = NetworkToPresentation(AF_INET, &addr);
            // TODO: is NetworkToPresentation backwards? or, do socket addrs store BE or LE ?

            "\nBroadcasting %d byte msg from %s: %Q\n", message_len, ip_string, buffer;

            //ClassRep(socket);
            ChatServerBroadcast(socket, message_len);
            MemSet(buffer, 0, ETHERNET_FRAME_SIZE);
            conn = conn->next;
        }
        else
        {
            //"\nReceived -1 [error], trying next connection.\n";
            conn = conn->next;
        }
    }

    return;

}

U0 ChatServer()
{
    CSocketAddressIPV4   socket_addr;
    U8                  *port_string = StrGet("Server Port: ");
    I64                  port = Str2I64(port_string);
    CTCPSocket          *new_socket;
    CTCPTreeQueue       *new_conn;
    U8                  *join_msg;
    CTCPTreeQueue       *conn;
    CTCPSocket          *conn_socket;


    Free(port_string);

    socket_addr.port            = EndianU16(port);
    socket_addr.family          = AF_INET;
    socket_addr.address.address = INADDR_ANY;

    tcp->timeout = 0.3 * JIFFY_FREQ;

    "\nTrying to bind socket.\n";
    if (TCPSocketBind(tcp, &socket_addr) == 0)
        "\nSocket bound.\n";
    else
    {
        "\nFailed to bind socket.\n";
        ChatServerKill;
        return;
    }

    "\nTrying to listen on socket.\n";
    if (TCPSocketListen(tcp, 5) == 0)
        "\nSocket now listening.\n";
    else
    {
        "\nFailed to listen on socket.\n";
        ChatServerKill;
        return;
    }

    while (CharScan != CH_SHIFT_ESC)
    {
        new_socket = TCPSocketAccept(tcp);

        if (new_socket > 0)
        {
            "\nNew connection.\n";
            new_conn = CAlloc(sizeof(CTCPTreeQueue));
            new_conn->socket = new_socket;
            new_socket->timeout = 0; // Set new connection non-blocking.

            join_msg = MStrPrint("$BG,LTGRAY$$DKGRAY$Connected clients: %d$FG$$BG$", QueueCount(connections) + 1);

            TCPSocketSendString(new_socket, join_msg);
            Free(join_msg);

            join_msg = MStrPrint("$BG,LTGRAY$$DKGRAY$New connection. Connected clients: %d$FG$$BG$",
                                    QueueCount(connections) + 1);

            conn = connections->next;

            while (conn != connections)
            {
                "\nNotifying clients of new connection.\n";
                conn_socket = conn->socket;
                TCPSocketSendString(conn_socket, join_msg);
                conn = conn->next;
            }
            Free(join_msg);

            QueueInsertRev(new_conn, connections);
        }

        ChatServerReceive; // Receive & Broadcast
        Sleep(50);
    }


    ChatServerKill;
}

ChatServer;