Source: Period.js

const Player = require('./Player.js');
const Group = require('./Group.js');
const Utils = require('./Utils.js');
const fs        = require('fs-extra');
const path      = require('path');

/**
* Period object - test link {@link Session} {@link Session.clients}.
* @param  {App} app The app that this period belongs to.
*/
class Period {

    constructor(id, app) {
        /**
         * Unique identifier for this participant.
         * @type {String}
         */
        this.id = id;

        /**
         * @type {App}
         */
        this.app = app;

        /**
         * List of groups for this period.
         * @type Array
         * @default []
         */
        this.groups = [];

        /**
         * 'outputHide' fields are not included in output
         * @type {String[]}
         */
        this.outputHide = ['stage', 'status', 'this', 'curAppId', 'periodTemp',
        'periodPerm', 'periodPermAuto', 'outputHide', 'app', 'groups', 'type',
        'stageTimerStart','stageTimerDuration','stageTimerTimeLeft','stageTimerStageIndex','stageTimerCallback','periodId','appIndex', 'gIds'];
    }

    roomId() {
        return this.app.roomId() + '_period_' + this.id;
    }

    /**
     * participantEnd - description
     *
     * Called from:
     * - {@link App#participantMoveToNextPeriod}
     *
     * @param  {type} participant description
     * @return {type}             description
     */
    participantEnd(participant) {
        for (var i=0; i<participant.clients.length; i++) {
            var client = participant.clients[i];
            client.socket.leave(this.roomId());
            client.socket.leave(participant.player.roomId());
            client.socket.leave(participant.player.group.roomId());
        }
        participant.period = null;
        participant.player = null;
    }

    playerByParticipantId(id) {
        for (var g in this.groups) {
            if (this.groups[g].playerByParticipantId(id) !== null) {
                return this.groups[g].playerByParticipantId(id);
            }
        }
        return null;
    }

    numGroups() {
        var ng = null;
        if (this.app.groupSize !== undefined) {
            ng = Math.floor((Object.keys(this.session().participants).length - 1) / this.app.groupSize) + 1;
        } else if (this.app.numGroups != null) {
            ng = this.app.numGroups;
        } else {
            ng = 1; // by default, everyone in same group
        }
        return ng;
    }

    maxPlayersPerGroup() {
        //var this.app.session.participants.length;
    }

    /**
     * participantBegin - description
     *
     * Called from:
     * - {@link participantBeginPeriod}
     *
     * @param  {type} participant description
     * @return {type}             description
     */
    participantBegin(participant) {
        var groupId = this.getParticipantGroupId(participant);
        if (groupId === null) {
            console.log('Error: no group defined for ' + participant.id + ' in ' + this.roomId());
        }

        var gr = null;
        for (var g in this.groups) {
            var group = this.groups[g];
            if (group.id === groupId) {
                gr = group;
                break;
            }
        }
        if (gr === null) {
            gr = new Group.new(groupId, this);
            gr.save();
            this.groups.push(gr);
        }
        var player = gr.playerWithParticipant(participant);
        if (player === null) {
            // create player
            player = new Player.new(participant.id, participant, gr, gr.players.length+1);
            participant.players.push(player);
            player.save();
            participant.save();
            gr.players.push(player);
            //            if (gr.players.length this.)
        }
        participant.setPlayer(player);

        if (participant.player === null) {
            console.log('APP: error assigning group for participant ' + participant.id);
        }

        participant.player.stageIndex = 0;
        participant.player.stage = this.app.stages[0];
        participant.player.status = 'ready';
        participant.player.startStage(participant.player.stage);
    }

    getParticipantGroupId(participant) {
        if (this.groups.length !== this.numGroups()) {
            this.createGroups();
        }
        for (var g in this.groups) {
            var group = this.groups[g];
            if (group.playerWithParticipant(participant) !== null) {
                return group.id;
            }
        }
        return null;
    }

    // splits players into groups.
    // participants: list of participants, player.group variable points to a list of players in the current group
    // numGroups: number of groups into which players are split
    createGroups() {
        const app = this.app;
        const participants = app.session.participants;
        const gIds = app.getGroupIdsForPeriod(this);

        // Create groups
        var pIds = [];
        for (var p in participants) {
            pIds.push(p);
        }

        let numGroups = this.numGroups();
        if (gIds[0].length != null) {
            numGroups = gIds.length;
        } else {
            for (let i in gIds) {
                numGroups = Math.max(numGroups, gIds[i]);
            }
        }
        for (var g=this.groups.length; g<numGroups; g++) {
            var group = new Group.new(g+1, this);
            group.save();
            this.groups.push(group);

            if (gIds[g].length != null) {
                // Label format
                // [['P1', 'P2'], ['P3', 'P4'], ...]
                for (var i=0; i<gIds[g].length; i++) {
                    var pId = gIds[g][i];
                    var participant = participants[pId];
                    var player = new Player.new(pId, participant, group, i+1);
                    participant.players.push(player);
                    player.save();
                    participant.save();
                    group.players.push(player);
                }
                group.allPlayersCreated = true;
                group.save();    
            } else {
                // Numerical format
                // [['P1', 'P2'], ['P3', 'P4'], ...]
                for (var i=0; i<gIds.length; i++) {
                    if (gIds[i] == group.id) {
                        var participant = participants[pIds[i]];
                        var player = new Player.new(pIds[i], participant, group, group.players.length+1);
                        participant.players.push(player);
                        player.save();
                        participant.save();
                        group.players.push(player);
                    }
                }
                group.allPlayersCreated = true;
                group.save();    
            }
        }

    }

    groupIds() {
        var gIds = [];
        for (var g=0; g<this.groups.length; g++) {
            gIds.push([]);
            var group = this.groups[g];
            for (var i=0; i<group.players.length; i++) {
                gIds[g].push(group.players[i].id);
            }
        }
        return gIds;
    }

    prevPeriod() {
        if (this.id < 2) {
            return null;
        }
        else {
            return this.app.periods[this.id-2];
        }
    }

    getStrangerMatching(numGroups, pIds, gIds, m, numCurrentGroups) {
        for (var g=numCurrentGroups; g<numGroups; g++) {
            for (var i=0; i<m; i++) {
                if (pIds.length < 1) {
                    return;
                }
                var rand = Utils.randomInt(0, pIds.length);
                gIds[g].push(pIds[rand]);
                pIds.splice(rand, 1);
            }
        }
    }

    addClient(client) {
        client.socket.join(this.roomId());
    }

    session() {
        return this.app.session;
    }

    outputFields() {
        var fields = [];
        for (var prop in this) {
            if (
                !Utils.isFunction(this[prop]) &&
                !this.outputHide.includes(prop)
            )
            fields.push(prop);
        }
        return fields;
    }

    server() {
        return this.session().jt;
    }

    getOutputDir() {
        return this.app.getOutputFN() + '/periods/' + this.id;
    }

    // autoSave() {
    //     Utils.writeJSON(this.app.getOutputFN() + '/periods/' + id + '/period.json', this.shell());
    //     for (var i in this.groups) {
    //         this.groups[i].autoSave();
    //     }
    // }

    shellWithParent() {
        var out = {};
        var fields = this.outputFields();
        for (var f in fields) {
            var field = fields[f];
            out[field] = this[field];
        }
        out.app = this.app.shellWithParent();
        return out;
    }

    shellWithChildren() {
        var out = {};
        var fields = this.outputFields();
        for (var f in fields) {
            var field = fields[f];
            out[field] = this[field];
        }
        out.app = this.app.id;
        out.groups = [];
        for (var i in this.groups) {
            out.groups[i] = this.groups[i].shellWithChildren();
        }
        return out;
    }

    shell() {
        var out = {};
        var fields = this.outputFields();
        for (var f in fields) {
            var field = fields[f];
            out[field] = this[field];
        }
        out.appIndex = this.app.indexInSession();
        return out;
    }

    /**
     * save - description
     *
     */
    save() {
        try {
            this.server().log('Period.save: ' + this.id);
            var toSave = this.shell();
            this.session().saveDataFS(toSave, 'PERIOD');
        } catch (err) {
            console.log('Error saving period ' + this.id + ': ' + err);
        }
    }

    /**
     * @static load - description
     *
     * CALLED FROM:
     * - {@link Session#load}
     *
     * @param  {type} json    description
     * @param  {type} session description
     * @return {type}         description
     */
    static load(json, session) {
        var id = json.id;
        var app = session.apps[json.appIndex-1];
        var newPeriod = new Period(id, app);
        if (app.periods.length > id-1) {
            var curPeriod = app.periods[id-1];
            newPeriod.groups = curPeriod.groups;
        }
        for (var j in json) {
            newPeriod[j] = json[j];
        }
        app.periods[id-1] = newPeriod;
    }

}

var exports = module.exports = {};
exports.new = Period;
exports.load = Period.load;