Source: room.js

const { v4: uuidv4 } = require('uuid');
const {Status} = require('./status.js')

/**
 * Room class, manages user connections, and user communication with app, so app doesn't need to implement any socket/room_managment logic.
 */

class Room{
    
    /**
     * Creates a new instance of Room.
     * @param {Object} socket - Founding socket. 
     * @param {number} maxMembers - Max number of members, that can be present in a room.
     * @param {Object} app - Custom app, determines behaviour of a room. App has to implement onConnect, and recieveData methods, otherwise error is thrown.
     * @param {Function} onRoomIsEmpty - Callback function called, when a room gets empty. 
     * @param {Function} onLeaderLeave - Callback function called, when leader leaves a room.
     */
    constructor(socket, app, maxMembers, onRoomIsEmpty, onLeaderLeave){
        /**
         * @property {string} id - Unique identifier of room.
         */
        this.id = uuidv4()
        /**
         * @property {Object} roomLeader - socket.io socket with expanded permissions (kick, change settings etc.).
         */
        this.roomLeader = socket
        /**
         * @property {Array<Object>} members - socket.io sockets array, constaining all connected clients.
         */
        this.members = [socket]
        /**
         * @property {number} maxMembers - Maximum number of connected clients.
         */
        this.maxMembers = maxMembers
        /**
         * @property {Object} app - Custom app, determines behaviour of a room. App has to implement onConnect, and recieveData methods, otherwise error is thrown.
         */
        this.app = app
        /**
         * @property {Function} onRoomIsEmpty - Callback function called, when room gets empty. 
         */
        this.onRoomIsEmpty = onRoomIsEmpty
        /**
         * @property {Function} onLeaderLeave - Callback function called, when leader leaves a room.
         */
        this.onLeaderLeave = onLeaderLeave


        //check if app has required propreties
        if(!this.app.onConnect) throw new Error("App doesn't provide mandatory function - onConnect.")
        if(!this.app.recieveData) throw new Error("App doesn't provide mandatory function - recieveData.")

        this.app.broadcast = (data) => {
            this.broadcast("app_data", data);
        };

        //connect founding socket
        this.app.onConnect(this.roomLeader.id);
        this.roomLeader.data.roomId = this.id;
    }

    /**
     * Gets called, when a socket connects succesfully. 
     * Calls app.onConnect(socket.id) with socket id as a parameter, letting app know that a new member has joined. 
     * Sets socket.data.id to room id , retaining this information for future emits.
     * @param {Object} socket - Connecting socket. 
     * @returns {void}
     */

    onConnect(socket) {
        // call app onConnect function 
        this.app.onConnect(socket.id);
        // set socket io data.roomId for future request to server 
        socket.data.roomId = this.id;
    }

    /**
     * Try to connect socket to this room 
     * @param {Object} socket - Connecting socket. 
     * @returns {Status} Status object. Data property contains room.id.
     */
    connect(socket) {
        //check if socket is already connected
        for (const memberSocket of this.members) {
            if (memberSocket === socket) {
                return new Status("already_member", this.id);
            }
        }
        //check if room is full
        if (this.members.length >= this.maxMembers) {
            return new Status("room_is_full", this.id );
        }
        //connect socket
        this.members.push(socket);
        this.onConnect(socket);
        return new Status("room_joined", this.id );
    }
    /**
     * No implemented yet.
     * Called when member leaves. 
     * Calls app.onLeave(socket.id) with socket id as a parameter, letting app know that a new member has left. 
     * @param {Object} socket - Socket, that disconnected.
     * @returns {void}
     */
    onLeave(socket) {
        this.app.onLeave(socket.id);
    }


    /**
     * No implemented yet.
     * Disconnect socket from this room.
     * Removes socket from members array. Calls this.onRoomIsEmpty() if no members are left. Calls this.onLeaderLeave() when leader leaves. 
     * @param {Object} socket - Socket, that should disconnect.
     * @returns {Status} Status object with no data property. 
    */
    leave(socket) {
        //remove from member
        this.members.splice(this.members.indexOf(socket), 1);
        if (members.length === 0)
            this.onRoomIsEmpty();

        if (this.roomLeader === socket) {
            this.roomLeader = this.members[0];
            this.onLeaderLeave();
        }
        this.onLeave(socket);
        return new Status("room_left" );

    }

    /**
     * This function is assigned to app.broadcast to enable app to send data to all of it's members. -> this.app.broadcast = (data) => {room.broadcast("app_data", data)}
     * @param {string} emit - Emit name.
     * @param {*} data - Data sent.
     */
    broadcast(emit, data) {
        for (const memberSocket of this.members) {
            this.roomLeader.to(memberSocket.id).emit(emit, data);
        }
        this.roomLeader.emit(emit, data);
    }
    
    /**
     * This function gets called when data for app arrive. Calls this.app.recieveData(senderId, data).
     * @param {Object} socket - Socket, that sent data. 
     * @param {*} data - Arriving data.
     */
    recieveData(socket, data) {
        this.app.recieveData(socket.id, data);
    }


    /**
     * Returns all relevant propreties of a this room in an object.
     * @returns {Object} {id, roomLeader, members, maxMembers}
     */
    getRoomOptions() {
        const id = this.id;
        const roomLeader = this.roomLeader.id;
        const members = this.members.map(socket => socket.id);
        const maxMembers = this.maxMembers;
        return {
            id: id,
            roomLeader: roomLeader,
            members: members,
            maxMembers: maxMembers
        };
    }
}




module.exports = {Room};