<commit_before>
const RG = require('./rg');
RG.Game = require('./game');

const OW = require('./overworld.map');

/* Object for converting serialized JSON objects to game objects. Note that all
 * actor/level ID info is stored between uses. If you call restoreLevel() two
 * times, all data from 1st is preserved. Call reset() to clear data. */
RG.Game.FromJSON = function() {

    let _dungeonLevel = 1;

    // Lookup table for mapping level ID to Map.Level object
    const id2level = {};
    const id2entity = {};
    const id2EntityJson = {};

    // Stores connection information for stairs
    const stairsInfo = {};

    /* Resets internal data of this object. */
    this.reset = function() {
        id2level = {};
        id2entity = {};
        id2EntityJson = {};
        stairsInfo = {};
    };

    this.getDungeonLevel = function() {
        return _dungeonLevel;
    };

    /* Handles creation of restored player from JSON.*/
    this.restorePlayer = function(obj) {
        const player = new RG.Actor.Rogue(obj.name);
        player.setIsPlayer(true);
        player.setType('player');
        player.setID(obj.id);
        id2entity[obj.id] = player;
        // id2EntityJson[obj.id] = obj;
        _dungeonLevel = obj.dungeonLevel;

        this.addCompsToEntity(player, obj.components);
        this.createInventory(obj, player);
        this.createEquipment(obj, player);
        if (obj.fovRange) {
            player.setFOVRange(obj.fovRange);
        }
        return player;
    };

    /* Restores all data for already created entity. */
    this.restoreEntity = function(obj, entity) {
        this.addCompsToEntity(entity, obj.components);
        this.createInventory(obj, entity);
        this.createEquipment(obj, entity);
        this.createBrain(obj.brain, entity);
        if (obj.fovRange) {
            entity.setFOVRange(obj.fovRange);
        }
        if (obj.spellbook) {
            this.createSpells(obj, entity);
        }
        return entity;
    };

    this.addCompsToEntity = function(ent, comps) {
        for (const name in comps) {
            if (name) {
                const comp = comps[name];
                const newCompObj = new RG.Component[name]();
                for (const fname in comp) {
                    if (typeof newCompObj[fname] === 'function') {
                        newCompObj[fname](comp[fname]);
                    }
                    else {
                        const json = JSON.stringify(comp);
                        RG.err('FromJSON', 'addCompsToEntity',
                            `${fname} not function in ${name}. Comp: ${json}`);

                    }
                }
                ent.add(name, newCompObj);
            }
        }
    };

    this.createBrain = function(brainJSON, ent) {
        const type = brainJSON.type;
        if (RG.Brain[type]) {
            const brainObj = new RG.Brain[type](ent);
            const memObj = brainObj.getMemory();
            const memJSON = brainJSON.memory;
            ent.setBrain(brainObj);

            if (memJSON) {
                memJSON.enemyTypes.forEach(type => {
                    brainObj.addEnemyType(type);
                });

                if (memJSON.lastAttackedID) {
                    const entity = id2entity[memJSON.lastAttackedID];
                    memObj.setLastAttacked(entity);
                }

                memJSON.enemies.forEach(enemyID => {
                    const enemy = id2entity[enemyID];
                    memObj.addEnemy(enemy);
                });
            }
            else if (type === 'Rogue') {
                brainObj.getMemory().addEnemyType('player');
            }
            // TODO reconstruct memory
        }
        else {
            RG.err('FromJSON', 'createBrain',
                `Cannot find RG.Brain.${type}, JSON: ${brainJSON}`);
        }
    };

    this.createSpells = function(json, entity) {
        entity._spellbook = new RG.Spell.SpellBook();
        json.spellbook.spells.forEach(spell => {
            const spellObj = new RG.Spell[spell.new]();
            spellObj.setPower(spell.power);
            if (spell.range) {
                spellObj.setRange(spell.range);
            }
            // If spell has damage/duration etc dice, restore them
            if (spell.dice) {
                const dice = [];
                spell.dice.forEach(die => {
                    dice.push(RG.FACT.createDie(die));
                });
                spellObj.setDice(dice);
            }
            entity._spellbook.addSpell(spellObj);
        });
    };

    this.createItem = function(obj) {
        const item = obj;
        const typeCapitalized = this.getItemObjectType(item);
        const newObj = new RG.Item[typeCapitalized]();
        for (const func in item) {
            if (func === 'setSpirit') {
                newObj[func](this.createSpirit(item[func]));
            }
            else if (func === 'components') {
                this.addCompsToEntity(newObj, obj.components);
            }
            else {
                newObj[func](item[func]); // Use setter
            }
        }
        return newObj;
    };

    this.createSpirit = function(obj) {
        const newObj = new RG.Actor.Spirit(obj.name);
        this.addCompsToEntity(newObj, obj.components);
        return newObj;
    };

    this.createInventory = function(obj, player) {
        if (obj.hasOwnProperty('inventory')) {
            const itemObjs = obj.inventory;
            for (let i = 0; i < itemObjs.length; i++) {
                const newObj = this.createItem(itemObjs[i]);
                player.getInvEq().addItem(newObj);
            }
        }
    };

    this.createEquipment = function(obj, player) {
        if (obj.hasOwnProperty('equipment')) {
            const equipObjs = obj.equipment;
            for (let i = 0; i < equipObjs.length; i++) {
                const newObj = this.createItem(equipObjs[i]);
                player.getInvEq().addItem(newObj);
                player.getInvEq().equipItem(newObj);
            }

        }
    };

    this.getItemObjectType = function(item) {
        if (item.setType === 'spiritgem') {return 'SpiritGem';}
        if (item.setType === 'goldcoin') {return 'GoldCoin';}
        if (item.setType === 'missileweapon') {return 'MissileWeapon';}
        if (!RG.isNullOrUndef([item])) {
            if (!RG.isNullOrUndef([item.setType])) {
                return item.setType.capitalize();
            }
            else {
                const itemJSON = JSON.stringify(item);
                RG.err('Game.Save', 'getItemObjectType',
                    'item.setType is undefined. item: ' + itemJSON);
            }
        }
        else {
            RG.err('Game.Save', 'getItemObjectType',
                'item is undefined');
        }
        return null;
    };

    /* Creates a Map.Level object from a json object. NOTE: This method cannot
    * connect stairs to other levels, but only create the stairs elements. */
    this.restoreLevel = function(json) {
        const level = new RG.Map.Level();
        level.setID(json.id);
        level.setLevelNumber(json.levelNumber);

        const mapObj = this.createCellList(json.map);
        level.setMap(mapObj);

        // Create actors
        json.actors.forEach(actor => {
            const actorObj = this.createActor(actor.obj);
            if (actorObj !== null) {
                level.addActor(actorObj, actor.x, actor.y);
            }
            else {
                RG.err('FromJSON', 'restoreLevel',
                    `Actor ${JSON.stringify(actor)} returned null`);
            }
        });

        // Create elements such as stairs
        json.elements.forEach(elem => {
            const elemObj = this.createElement(elem);
            if (elemObj !== null) {
                level.addElement(elemObj, elem.x, elem.y);
            }
            else {
                RG.err('FromJSON', 'restoreLevel',
                    `Elem ${JSON.stringify(elem)} returned null`);
            }
        });

        // Create items
        json.items.forEach(item => {
            const itemObj = this.createItem(item.obj);
            if (itemObj !== null) {
                level.addItem(itemObj, item.x, item.y);
            }
            else {
                RG.err('FromJSON', 'restoreLevel',
                    `Actor ${JSON.stringify(item)} returned null`);
            }
        });

        // Duplicate level IDs are very bad
        if (!id2level.hasOwnProperty(json.id)) {
            id2level[json.id] = level;
        }
        else {
            RG.err('FromJSON', 'restoreLevel',
                `Duplicate level ID detected ${json.id}`);
        }
        return level;
    };

    /* Creates elements such as stairs, doors and shop. */
    this.createElement = function(elem) {
        const elemObj = elem.obj;
        const type = elemObj.type;
        if (/stairs/.test(type)) {
            return this.createUnconnectedStairs(elem);
        }
        else if (type === 'passage') {
            return this.createUnconnectedStairs(elem);
        }
        else if (type === 'shop') {
            const shopElem = new RG.Element.Shop();
            let shopkeeper = null;
            if (!RG.isNullOrUndef([elemObj.shopkeeper])) {
                shopkeeper = id2entity[elemObj.shopkeeper];
                if (shopkeeper) {
                    shopElem.setShopkeeper(shopkeeper);
                }
                else {
                    RG.err('Game.FromJSON', 'createElement',
                        `Shopkeeper with ID ${elemObj.shopkeeper} not found`);
                }
            }
            shopElem.setCostFactor(elemObj.costFactorBuy,
                elemObj.costFactorSell);
            return shopElem;
        }
        else if (type === 'door') {
            return new RG.Element.Door(elemObj.closed);
        }
        return null;
    };

    /* Creates the actor and sets entity ID refs, but does not restore all
     * entity data. */
    this.createActor = function(obj) {
        if (obj.type === null) {
            RG.err('FromJSON', 'restoreEntity',
                `obj.type null, obj: ${JSON.stringify(obj)}`);
        }

        let entity = null;
        switch (obj.type) {
            case 'spirit': entity = new RG.Actor.Spirit(obj.name); break;
            default: entity = new RG.Actor.Rogue(obj.name);
        }
        entity.setType(obj.type);
        entity.setID(obj.id);
        id2entity[entity.getID()] = entity;
        id2EntityJson[obj.id] = obj;
        return entity;
    };

    /* Tricky one. The target level should exist before connections. The object
     * returned by this method is not complete stairs, but has placeholders for
     * targetLevel (level ID) and targetStairs (x, y coordinates).
     */
    this.createUnconnectedStairs = function(elem) {
        const x = elem.x;
        const y = elem.y;
        const id = elem.obj.srcLevel;
        const stairsId = `${id},${x},${y}`;
        const elemObj = elem.obj;
        const sObj = new RG.Element.Stairs(elemObj.isDown);
        stairsInfo[stairsId] = {targetLevel: elemObj.targetLevel,
            targetStairs: elemObj.targetStairs};
        return sObj;
    };

    this.createCellList = function(map) {
        const mapObj = new RG.Map.CellList(map.cols, map.rows);
        map.cells.forEach((col, x) => {
            col.forEach((cell, y) => {
                const baseElem = this.createBaseElem(cell);
                mapObj.setBaseElemXY(x, y, baseElem);
                if (cell.ex) {
                    mapObj.getCell(x, y).setExplored(true);
                }
                if (cell.elements) {
                    cell.elements.forEach(elem => {
                        mapObj.setElemXY(x, y, this.createBaseElem(elem));
                    });
                }
            });
        });
        return mapObj;
    };

    this.createBaseElem = function(cell) {
        switch (cell.type) {
            case '#': // wall
            case 'wall': return RG.ELEM.WALL;
            case '.': // floor
            case 'floor': return RG.ELEM.FLOOR;
            case 'tree': return RG.ELEM.TREE;
            case 'grass': return RG.ELEM.GRASS;
            case 'stone': return RG.ELEM.STONE;
            case 'water': return RG.ELEM.WATER;
            case 'chasm': return RG.ELEM.CHASM;
            case 'road': return RG.ELEM.ROAD;
            case 'highrock': return RG.ELEM.HIGH_ROCK;
            case 'bridge': return RG.ELEM.BRIDGE;
            default: {
                if (RG.elemTypeToObj[cell.type]) {
                    return RG.elemTypeToObj[cell.type];
                }
                else {
                    RG.err('Game.fromJSON', 'createBaseElem',
                        `Unknown type ${cell.type}`);
                }
            }
        }
        return null;
    };

    this.createGame = function(json) {
        const game = new RG.Game.Main();

        // Levels must be created before the actual world, because the World
        // object contains only level IDs
        json.levels.forEach(levelJson => {
            const level = this.restoreLevel(levelJson);
            if (!levelJson.parent) {
                game.addLevel(level); // remove once world is properly created
            }
        });

        Object.keys(json.places).forEach(name => {
            const place = json.places[name];
            const placeObj = this.restorePlace(place);
            game.addPlace(placeObj);
        });

        if (json.overworld) {
            const overworld = this.restoreOverWorld(json.overworld);
            game.setOverWorld(overworld);
        }

        // Connect levels using id2level + stairsInfo
        this.connectGameLevels(game);

        // Player created separately from other actors for now
        if (json.player) {
            const player = this.restorePlayer(json.player);
            const id = json.player.levelID;
            const level = game.getLevels().find(item => item.getID() === id);
            if (level) {
                const x = json.player.x;
                const y = json.player.y;
                level.addActor(player, x, y);
                game.addPlayer(player);
            }
            else {
                RG.err('Game.FromJSON', 'createGame',
                    `Cannot find player level object with level ID ${id}`);
            }
        }

        this.restoreEntityData();

        // Restore the ID counters for levels and entities, otherwise duplicate
        // IDs will appear when new levels/entities are created
        // RG.Map.Level.prototype.idCount = json.lastLevelID;
        // RG.Entity.prototype.idCount = json.lastEntityID;

        return game;
    };

    this.connectGameLevels = function(game) {
        const levels = game.getLevels();
        levels.forEach(level => {
            const stairsList = level.getStairs();

            stairsList.forEach(s => {
                const connObj = stairsInfo[s.getID()];
                const targetLevel = id2level[connObj.targetLevel];
                const targetStairsXY = connObj.targetStairs;
                const x = targetStairsXY.x;
                const y = targetStairsXY.y;
                if (targetLevel) {
                    s.setTargetLevel(targetLevel);
                    const targetStairs = targetLevel
                        .getMap().getCell(x, y).getStairs();
                    if (targetStairs) {
                        s.connect(targetStairs);
                    }
                    else {
                        RG.err('Game.FromJSON', 'connectGameLevels',
                            'Target stairs was null. Cannot connect.');
                    }
                }
                else {
                    RG.err('Game.FromJSON', 'connectGameLevels',
                        'Target level null. Cannot connect.');
                }
            });
        });
    };

    /* Assume the place is World object for now. */
    this.restorePlace = function(place) {
        const fact = new RG.Factory.World();
        fact.setId2Level(id2level);
        const world = fact.createWorld(place);
        return world;
    };

    this.restoreOverWorld = function(json) {
        const ow = new OW.Map();
        ow.setMap(json.baseMap);
        ow._features = json.features;
        ow._featuresByXY = json.featuresByXY;
        ow._vWalls = json.vWalls;
        ow._hWalls = json.hWalls;
        ow._biomeMap = json.biomeMap;
        return ow;
    };

    this.restoreEntityData = function() {
        Object.keys(id2EntityJson).forEach(id => {
            const obj = id2EntityJson[id];
            const entity = id2entity[id];
            this.restoreEntity(obj, entity);
        });
    };

};

module.exports = RG.Game.FromJSON;
<commit_msg>Corrected id2xxx variables as let.<commit_after>
const RG = require('./rg');
RG.Game = require('./game');

const OW = require('./overworld.map');

/* Object for converting serialized JSON objects to game objects. Note that all
 * actor/level ID info is stored between uses. If you call restoreLevel() two
 * times, all data from 1st is preserved. Call reset() to clear data. */
RG.Game.FromJSON = function() {

    let _dungeonLevel = 1;

    // Lookup table for mapping level ID to Map.Level object
    let id2level = {};
    let id2entity = {};
    let id2EntityJson = {};

    // Stores connection information for stairs
    let stairsInfo = {};

    /* Resets internal data of this object. */
    this.reset = function() {
        id2level = {};
        id2entity = {};
        id2EntityJson = {};
        stairsInfo = {};
    };

    this.getDungeonLevel = function() {
        return _dungeonLevel;
    };

    /* Handles creation of restored player from JSON.*/
    this.restorePlayer = function(obj) {
        const player = new RG.Actor.Rogue(obj.name);
        player.setIsPlayer(true);
        player.setType('player');
        player.setID(obj.id);
        id2entity[obj.id] = player;
        // id2EntityJson[obj.id] = obj;
        _dungeonLevel = obj.dungeonLevel;

        this.addCompsToEntity(player, obj.components);
        this.createInventory(obj, player);
        this.createEquipment(obj, player);
        if (obj.fovRange) {
            player.setFOVRange(obj.fovRange);
        }
        return player;
    };

    /* Restores all data for already created entity. */
    this.restoreEntity = function(obj, entity) {
        this.addCompsToEntity(entity, obj.components);
        this.createInventory(obj, entity);
        this.createEquipment(obj, entity);
        this.createBrain(obj.brain, entity);
        if (obj.fovRange) {
            entity.setFOVRange(obj.fovRange);
        }
        if (obj.spellbook) {
            this.createSpells(obj, entity);
        }
        return entity;
    };

    this.addCompsToEntity = function(ent, comps) {
        for (const name in comps) {
            if (name) {
                const comp = comps[name];
                const newCompObj = new RG.Component[name]();
                for (const fname in comp) {
                    if (typeof newCompObj[fname] === 'function') {
                        newCompObj[fname](comp[fname]);
                    }
                    else {
                        const json = JSON.stringify(comp);
                        RG.err('FromJSON', 'addCompsToEntity',
                            `${fname} not function in ${name}. Comp: ${json}`);

                    }
                }
                ent.add(name, newCompObj);
            }
        }
    };

    this.createBrain = function(brainJSON, ent) {
        const type = brainJSON.type;
        if (RG.Brain[type]) {
            const brainObj = new RG.Brain[type](ent);
            const memObj = brainObj.getMemory();
            const memJSON = brainJSON.memory;
            ent.setBrain(brainObj);

            if (memJSON) {
                memJSON.enemyTypes.forEach(type => {
                    brainObj.addEnemyType(type);
                });

                if (memJSON.lastAttackedID) {
                    const entity = id2entity[memJSON.lastAttackedID];
                    memObj.setLastAttacked(entity);
                }

                memJSON.enemies.forEach(enemyID => {
                    const enemy = id2entity[enemyID];
                    memObj.addEnemy(enemy);
                });
            }
            else if (type === 'Rogue') {
                brainObj.getMemory().addEnemyType('player');
            }
            // TODO reconstruct memory
        }
        else {
            RG.err('FromJSON', 'createBrain',
                `Cannot find RG.Brain.${type}, JSON: ${brainJSON}`);
        }
    };

    this.createSpells = function(json, entity) {
        entity._spellbook = new RG.Spell.SpellBook();
        json.spellbook.spells.forEach(spell => {
            const spellObj = new RG.Spell[spell.new]();
            spellObj.setPower(spell.power);
            if (spell.range) {
                spellObj.setRange(spell.range);
            }
            // If spell has damage/duration etc dice, restore them
            if (spell.dice) {
                const dice = [];
                spell.dice.forEach(die => {
                    dice.push(RG.FACT.createDie(die));
                });
                spellObj.setDice(dice);
            }
            entity._spellbook.addSpell(spellObj);
        });
    };

    this.createItem = function(obj) {
        const item = obj;
        const typeCapitalized = this.getItemObjectType(item);
        const newObj = new RG.Item[typeCapitalized]();
        for (const func in item) {
            if (func === 'setSpirit') {
                newObj[func](this.createSpirit(item[func]));
            }
            else if (func === 'components') {
                this.addCompsToEntity(newObj, obj.components);
            }
            else {
                newObj[func](item[func]); // Use setter
            }
        }
        return newObj;
    };

    this.createSpirit = function(obj) {
        const newObj = new RG.Actor.Spirit(obj.name);
        this.addCompsToEntity(newObj, obj.components);
        return newObj;
    };

    this.createInventory = function(obj, player) {
        if (obj.hasOwnProperty('inventory')) {
            const itemObjs = obj.inventory;
            for (let i = 0; i < itemObjs.length; i++) {
                const newObj = this.createItem(itemObjs[i]);
                player.getInvEq().addItem(newObj);
            }
        }
    };

    this.createEquipment = function(obj, player) {
        if (obj.hasOwnProperty('equipment')) {
            const equipObjs = obj.equipment;
            for (let i = 0; i < equipObjs.length; i++) {
                const newObj = this.createItem(equipObjs[i]);
                player.getInvEq().addItem(newObj);
                player.getInvEq().equipItem(newObj);
            }

        }
    };

    this.getItemObjectType = function(item) {
        if (item.setType === 'spiritgem') {return 'SpiritGem';}
        if (item.setType === 'goldcoin') {return 'GoldCoin';}
        if (item.setType === 'missileweapon') {return 'MissileWeapon';}
        if (!RG.isNullOrUndef([item])) {
            if (!RG.isNullOrUndef([item.setType])) {
                return item.setType.capitalize();
            }
            else {
                const itemJSON = JSON.stringify(item);
                RG.err('Game.Save', 'getItemObjectType',
                    'item.setType is undefined. item: ' + itemJSON);
            }
        }
        else {
            RG.err('Game.Save', 'getItemObjectType',
                'item is undefined');
        }
        return null;
    };

    /* Creates a Map.Level object from a json object. NOTE: This method cannot
    * connect stairs to other levels, but only create the stairs elements. */
    this.restoreLevel = function(json) {
        const level = new RG.Map.Level();
        level.setID(json.id);
        level.setLevelNumber(json.levelNumber);

        const mapObj = this.createCellList(json.map);
        level.setMap(mapObj);

        // Create actors
        json.actors.forEach(actor => {
            const actorObj = this.createActor(actor.obj);
            if (actorObj !== null) {
                level.addActor(actorObj, actor.x, actor.y);
            }
            else {
                RG.err('FromJSON', 'restoreLevel',
                    `Actor ${JSON.stringify(actor)} returned null`);
            }
        });

        // Create elements such as stairs
        json.elements.forEach(elem => {
            const elemObj = this.createElement(elem);
            if (elemObj !== null) {
                level.addElement(elemObj, elem.x, elem.y);
            }
            else {
                RG.err('FromJSON', 'restoreLevel',
                    `Elem ${JSON.stringify(elem)} returned null`);
            }
        });

        // Create items
        json.items.forEach(item => {
            const itemObj = this.createItem(item.obj);
            if (itemObj !== null) {
                level.addItem(itemObj, item.x, item.y);
            }
            else {
                RG.err('FromJSON', 'restoreLevel',
                    `Actor ${JSON.stringify(item)} returned null`);
            }
        });

        // Duplicate level IDs are very bad
        if (!id2level.hasOwnProperty(json.id)) {
            id2level[json.id] = level;
        }
        else {
            RG.err('FromJSON', 'restoreLevel',
                `Duplicate level ID detected ${json.id}`);
        }
        return level;
    };

    /* Creates elements such as stairs, doors and shop. */
    this.createElement = function(elem) {
        const elemObj = elem.obj;
        const type = elemObj.type;
        if (/stairs/.test(type)) {
            return this.createUnconnectedStairs(elem);
        }
        else if (type === 'passage') {
            return this.createUnconnectedStairs(elem);
        }
        else if (type === 'shop') {
            const shopElem = new RG.Element.Shop();
            let shopkeeper = null;
            if (!RG.isNullOrUndef([elemObj.shopkeeper])) {
                shopkeeper = id2entity[elemObj.shopkeeper];
                if (shopkeeper) {
                    shopElem.setShopkeeper(shopkeeper);
                }
                else {
                    RG.err('Game.FromJSON', 'createElement',
                        `Shopkeeper with ID ${elemObj.shopkeeper} not found`);
                }
            }
            shopElem.setCostFactor(elemObj.costFactorBuy,
                elemObj.costFactorSell);
            return shopElem;
        }
        else if (type === 'door') {
            return new RG.Element.Door(elemObj.closed);
        }
        return null;
    };

    /* Creates the actor and sets entity ID refs, but does not restore all
     * entity data. */
    this.createActor = function(obj) {
        if (obj.type === null) {
            RG.err('FromJSON', 'restoreEntity',
                `obj.type null, obj: ${JSON.stringify(obj)}`);
        }

        let entity = null;
        switch (obj.type) {
            case 'spirit': entity = new RG.Actor.Spirit(obj.name); break;
            default: entity = new RG.Actor.Rogue(obj.name);
        }
        entity.setType(obj.type);
        entity.setID(obj.id);
        id2entity[entity.getID()] = entity;
        id2EntityJson[obj.id] = obj;
        return entity;
    };

    /* Tricky one. The target level should exist before connections. The object
     * returned by this method is not complete stairs, but has placeholders for
     * targetLevel (level ID) and targetStairs (x, y coordinates).
     */
    this.createUnconnectedStairs = function(elem) {
        const x = elem.x;
        const y = elem.y;
        const id = elem.obj.srcLevel;
        const stairsId = `${id},${x},${y}`;
        const elemObj = elem.obj;
        const sObj = new RG.Element.Stairs(elemObj.isDown);
        stairsInfo[stairsId] = {targetLevel: elemObj.targetLevel,
            targetStairs: elemObj.targetStairs};
        return sObj;
    };

    this.createCellList = function(map) {
        const mapObj = new RG.Map.CellList(map.cols, map.rows);
        map.cells.forEach((col, x) => {
            col.forEach((cell, y) => {
                const baseElem = this.createBaseElem(cell);
                mapObj.setBaseElemXY(x, y, baseElem);
                if (cell.ex) {
                    mapObj.getCell(x, y).setExplored(true);
                }
                if (cell.elements) {
                    cell.elements.forEach(elem => {
                        mapObj.setElemXY(x, y, this.createBaseElem(elem));
                    });
                }
            });
        });
        return mapObj;
    };

    this.createBaseElem = function(cell) {
        switch (cell.type) {
            case '#': // wall
            case 'wall': return RG.ELEM.WALL;
            case '.': // floor
            case 'floor': return RG.ELEM.FLOOR;
            case 'tree': return RG.ELEM.TREE;
            case 'grass': return RG.ELEM.GRASS;
            case 'stone': return RG.ELEM.STONE;
            case 'water': return RG.ELEM.WATER;
            case 'chasm': return RG.ELEM.CHASM;
            case 'road': return RG.ELEM.ROAD;
            case 'highrock': return RG.ELEM.HIGH_ROCK;
            case 'bridge': return RG.ELEM.BRIDGE;
            default: {
                if (RG.elemTypeToObj[cell.type]) {
                    return RG.elemTypeToObj[cell.type];
                }
                else {
                    RG.err('Game.fromJSON', 'createBaseElem',
                        `Unknown type ${cell.type}`);
                }
            }
        }
        return null;
    };

    this.createGame = function(json) {
        const game = new RG.Game.Main();

        // Levels must be created before the actual world, because the World
        // object contains only level IDs
        json.levels.forEach(levelJson => {
            const level = this.restoreLevel(levelJson);
            if (!levelJson.parent) {
                game.addLevel(level); // remove once world is properly created
            }
        });

        Object.keys(json.places).forEach(name => {
            const place = json.places[name];
            const placeObj = this.restorePlace(place);
            game.addPlace(placeObj);
        });

        if (json.overworld) {
            const overworld = this.restoreOverWorld(json.overworld);
            game.setOverWorld(overworld);
        }

        // Connect levels using id2level + stairsInfo
        this.connectGameLevels(game);

        // Player created separately from other actors for now
        if (json.player) {
            const player = this.restorePlayer(json.player);
            const id = json.player.levelID;
            const level = game.getLevels().find(item => item.getID() === id);
            if (level) {
                const x = json.player.x;
                const y = json.player.y;
                level.addActor(player, x, y);
                game.addPlayer(player);
            }
            else {
                RG.err('Game.FromJSON', 'createGame',
                    `Cannot find player level object with level ID ${id}`);
            }
        }

        this.restoreEntityData();

        // Restore the ID counters for levels and entities, otherwise duplicate
        // IDs will appear when new levels/entities are created
        // RG.Map.Level.prototype.idCount = json.lastLevelID;
        // RG.Entity.prototype.idCount = json.lastEntityID;

        return game;
    };

    this.connectGameLevels = function(game) {
        const levels = game.getLevels();
        levels.forEach(level => {
            const stairsList = level.getStairs();

            stairsList.forEach(s => {
                const connObj = stairsInfo[s.getID()];
                const targetLevel = id2level[connObj.targetLevel];
                const targetStairsXY = connObj.targetStairs;
                const x = targetStairsXY.x;
                const y = targetStairsXY.y;
                if (targetLevel) {
                    s.setTargetLevel(targetLevel);
                    const targetStairs = targetLevel
                        .getMap().getCell(x, y).getStairs();
                    if (targetStairs) {
                        s.connect(targetStairs);
                    }
                    else {
                        RG.err('Game.FromJSON', 'connectGameLevels',
                            'Target stairs was null. Cannot connect.');
                    }
                }
                else {
                    RG.err('Game.FromJSON', 'connectGameLevels',
                        'Target level null. Cannot connect.');
                }
            });
        });
    };

    /* Assume the place is World object for now. */
    this.restorePlace = function(place) {
        const fact = new RG.Factory.World();
        fact.setId2Level(id2level);
        const world = fact.createWorld(place);
        return world;
    };

    this.restoreOverWorld = function(json) {
        const ow = new OW.Map();
        ow.setMap(json.baseMap);
        ow._features = json.features;
        ow._featuresByXY = json.featuresByXY;
        ow._vWalls = json.vWalls;
        ow._hWalls = json.hWalls;
        ow._biomeMap = json.biomeMap;
        return ow;
    };

    this.restoreEntityData = function() {
        Object.keys(id2EntityJson).forEach(id => {
            const obj = id2EntityJson[id];
            const entity = id2entity[id];
            this.restoreEntity(obj, entity);
        });
    };

};

module.exports = RG.Game.FromJSON;
<|endoftext|>


<commit_before>import { Settings } from "api"
import Model, { yupDate } from "components/Model"
import _isEmpty from "lodash/isEmpty"
import { Organization, Position } from "models"
import React from "react"
import AFG_ICON from "resources/afg_small.png"
import PEOPLE_ICON from "resources/people.png"
import RS_ICON from "resources/rs_small.png"
import utils from "utils"
import * as yup from "yup"

export const advisorPerson = Settings.fields.advisor.person
export const principalPerson = Settings.fields.principal.person

export default class Person extends Model {
  static resourceName = "Person"
  static listName = "personList"
  static getInstanceName = "person"
  static getModelNameLinkTo = "person"

  static STATUS = {
    NEW_USER: "NEW_USER",
    ACTIVE: "ACTIVE",
    INACTIVE: "INACTIVE"
  }

  static ROLE = {
    ADVISOR: "ADVISOR",
    PRINCIPAL: "PRINCIPAL"
  }

  static nameDelimiter = ","

  static yupSchema = yup
    .object()
    .shape({
      uuid: yup.string().default(""),
      name: yup
        .string()
        .nullable()
        .default(""),
      // not actually in the database, but used for validation
      firstName: yup
        .string()
        .nullable()
        .when("role", (role, schema) =>
          Person.isAdvisor({ role })
            ? schema.required(
              `You must provide the ${Settings.fields.person.firstName}`
            )
            : schema.nullable()
        )
        .default("")
        .label(Settings.fields.person.firstName),
      // not actually in the database, but used for validation
      lastName: yup
        .string()
        .nullable()
        .uppercase()
        .required(`You must provide the ${Settings.fields.person.lastName}`)
        .default("")
        .label(Settings.fields.person.lastName),
      domainUsername: yup
        .string()
        .nullable()
        .default("")
        .label(Settings.fields.person.domainUsername),
      emailAddress: yup
        .string()
        .nullable()
        .email()
        .when("role", (role, schema) =>
          schema.test(
            "emailAddress",
            "emailAddress error",
            // can't use arrow function here because of binding to 'this'
            function(emailAddress) {
              const r = utils.handleEmailValidation(
                emailAddress,
                role === Person.ROLE.ADVISOR
              )
              return r.isValid ? true : this.createError({ message: r.message })
            }
          )
        )
        .default("")
        .label(Settings.fields.person.emailAddress),
      country: yup
        .string()
        .nullable()
        .required(`You must provide the ${Settings.fields.person.country}`)
        .default("")
        .label(Settings.fields.person.country),
      rank: yup
        .string()
        .nullable()
        .required(
          `You must provide the ${
            Settings.fields.person.rank
          } (Military rank, CIV and CTR values are available)`
        )
        .default("")
        .label(Settings.fields.person.rank),
      gender: yup
        .string()
        .nullable()
        .when("role", (role, schema) =>
          Person.isAdvisor({ role })
            ? schema.required(
              `You must provide the ${Settings.fields.person.gender}`
            )
            : schema.nullable()
        )
        .default("")
        .label(Settings.fields.person.gender),
      phoneNumber: yup
        .string()
        .nullable()
        .default("")
        .label(Settings.fields.person.phoneNumber),
      endOfTourDate: yupDate
        .nullable()
        .when("role", (role, schema) =>
          Person.isAdvisor({ role })
            ? schema
              .nullable()
              .required(
                `You must provide the ${Settings.fields.person.endOfTourDate}`
              )
            : schema.nullable()
        )
        .when(["role", "status", "uuid"], (role, status, uuid, schema) =>
          Person.isAdvisor({ role }) && Person.isNewFormUser({ status, uuid })
            ? schema.test(
              "end-of-tour-date",
              `The ${
                Settings.fields.person.endOfTourDate
              } date must be in the future`,
              endOfTourDate => endOfTourDate > Date.now()
            )
            : schema.nullable()
        )
        .default(null)
        .label(Settings.fields.person.endOfTourDate),
      biography: yup
        .string()
        .nullable()
        .default(""),
      position: yup
        .object()
        .nullable()
        .default({}),
      role: yup
        .string()
        .nullable()
        .default(() => Person.ROLE.PRINCIPAL),
      status: yup
        .string()
        .nullable()
        .default(() => Person.STATUS.ACTIVE)
    })
    .concat(Model.yupSchema)

  static autocompleteQuery =
    "uuid, name, rank, role, status, endOfTourDate, position { uuid, name, type, code, status, organization { uuid, shortName }, location {uuid, name} }"

  static autocompleteTemplate(person) {
    return (

        {new Person(person).toString()}{" "}
        {person.position &&
          `- (${person.position.name}` +
            (person.position.code ? `, ${person.position.code}` : "") +
            (person.position.location
              ? `, ${person.position.location.name}`
              : "") +
            ")"}

    )
  }

  static humanNameOfRole(role) {
    if (role === Person.ROLE.ADVISOR) {
      return Settings.fields.advisor.person.name
    }
    if (role === Person.ROLE.PRINCIPAL) {
      return principalPerson.name
    }
    throw new Error(`Unrecognized role: ${role}`)
  }

  static humanNameOfStatus(status) {
    return utils.sentenceCase(status)
  }

  constructor(props) {
    super(Model.fillObject(props, Person.yupSchema))
  }

  humanNameOfRole() {
    return Person.humanNameOfRole(this.role)
  }

  humanNameOfStatus() {
    return Person.humanNameOfStatus(this.status)
  }

  static isNewUser(person) {
    return person.status === Person.STATUS.NEW_USER
  }

  static isNewFormUser(person) {
    return _isEmpty(person.uuid) || person.status === Person.STATUS.NEW_USER
  }

  isNewUser() {
    return Person.isNewUser(this)
  }

  static isAdvisor(person) {
    return person.role === Person.ROLE.ADVISOR
  }

  isAdvisor() {
    return Person.isAdvisor(this)
  }

  isPrincipal() {
    return this.role === Person.ROLE.PRINCIPAL
  }

  isAdmin() {
    return this.position && this.position.type === Position.TYPE.ADMINISTRATOR
  }

  isSuperUser() {
    return (
      this.position &&
      (this.position.type === Position.TYPE.SUPER_USER ||
        this.position.type === Position.TYPE.ADMINISTRATOR)
    )
  }

  hasAssignedPosition() {
    // has a non-empty position with a non-zero uuid
    return !_isEmpty(this.position) && !!this.position.uuid
  }

  hasActivePosition() {
    return (
      this.hasAssignedPosition() &&
      this.position.status === Position.STATUS.ACTIVE
    )
  }

  // Checks if this user is a valid super user for a particular organization
  // Must be either
  // - An Administrator
  // - A super user and this org is a PRINCIPAL_ORG
  // - A super user for this organization
  // - A super user for this orgs parents.
  isSuperUserForOrg(org) {
    if (!org) {
      return false
    }
    if (this.position && this.position.type === Position.TYPE.ADMINISTRATOR) {
      return true
    }
    if (this.position && this.position.type !== Position.TYPE.SUPER_USER) {
      return false
    }
    if (org.type === Organization.TYPE.PRINCIPAL_ORG) {
      return true
    }

    if (!this.position || !this.position.organization) {
      return false
    }
    let orgs = this.position.organization.allDescendantOrgs || []
    orgs.push(this.position.organization)
    let orgUuids = orgs.map(o => o.uuid)

    return orgUuids.includes(org.uuid)
  }

  iconUrl() {
    if (this.isAdvisor()) {
      return RS_ICON
    } else if (this.isPrincipal()) {
      return AFG_ICON
    } else {
      return PEOPLE_ICON
    }
  }

  toString() {
    if (this.rank) {
      return this.rank + " " + this.name
    } else {
      return this.name || this.uuid
    }
  }

  static fullName(person, doTrim) {
    if (person.lastName && person.firstName) {
      return `${Person.formattedLastName(person.lastName, doTrim)}${
        Person.nameDelimiter
      } ${Person.formattedFirstName(person.firstName, doTrim)}`
    } else if (person.lastName) {
      return Person.formattedLastName(person.lastName)
    } else {
      return ""
    }
  }

  static formattedLastName(lastName, doTrim) {
    let r = lastName.toUpperCase()
    if (doTrim) {
      r = r.trim()
    }
    return r
  }

  static formattedFirstName(firstName, doTrim) {
    let r = firstName
    if (doTrim) {
      r = r.trim()
    }
    return r
  }

  static parseFullName(name) {
    const delimiter = name.indexOf(Person.nameDelimiter)
    let lastName = name
    let firstName = ""

    if (delimiter > -1) {
      lastName = name.substring(0, delimiter)
      firstName = name.substring(delimiter + 1, name.length)
    }

    return {
      lastName: lastName.trim().toUpperCase(),
      firstName: firstName.trim()
    }
  }
}
<commit_msg>NCI-Agency/anet#1367: New person object should have null uuid<commit_after>import { Settings } from "api"
import Model, { yupDate } from "components/Model"
import _isEmpty from "lodash/isEmpty"
import { Organization, Position } from "models"
import React from "react"
import AFG_ICON from "resources/afg_small.png"
import PEOPLE_ICON from "resources/people.png"
import RS_ICON from "resources/rs_small.png"
import utils from "utils"
import * as yup from "yup"

export const advisorPerson = Settings.fields.advisor.person
export const principalPerson = Settings.fields.principal.person

export default class Person extends Model {
  static resourceName = "Person"
  static listName = "personList"
  static getInstanceName = "person"
  static getModelNameLinkTo = "person"

  static STATUS = {
    NEW_USER: "NEW_USER",
    ACTIVE: "ACTIVE",
    INACTIVE: "INACTIVE"
  }

  static ROLE = {
    ADVISOR: "ADVISOR",
    PRINCIPAL: "PRINCIPAL"
  }

  static nameDelimiter = ","

  static yupSchema = yup
    .object()
    .shape({
      uuid: yup.string().nullable().default(null),
      name: yup
        .string()
        .nullable()
        .default(""),
      // not actually in the database, but used for validation
      firstName: yup
        .string()
        .nullable()
        .when("role", (role, schema) =>
          Person.isAdvisor({ role })
            ? schema.required(
              `You must provide the ${Settings.fields.person.firstName}`
            )
            : schema.nullable()
        )
        .default("")
        .label(Settings.fields.person.firstName),
      // not actually in the database, but used for validation
      lastName: yup
        .string()
        .nullable()
        .uppercase()
        .required(`You must provide the ${Settings.fields.person.lastName}`)
        .default("")
        .label(Settings.fields.person.lastName),
      domainUsername: yup
        .string()
        .nullable()
        .default("")
        .label(Settings.fields.person.domainUsername),
      emailAddress: yup
        .string()
        .nullable()
        .email()
        .when("role", (role, schema) =>
          schema.test(
            "emailAddress",
            "emailAddress error",
            // can't use arrow function here because of binding to 'this'
            function(emailAddress) {
              const r = utils.handleEmailValidation(
                emailAddress,
                role === Person.ROLE.ADVISOR
              )
              return r.isValid ? true : this.createError({ message: r.message })
            }
          )
        )
        .default("")
        .label(Settings.fields.person.emailAddress),
      country: yup
        .string()
        .nullable()
        .required(`You must provide the ${Settings.fields.person.country}`)
        .default("")
        .label(Settings.fields.person.country),
      rank: yup
        .string()
        .nullable()
        .required(
          `You must provide the ${
            Settings.fields.person.rank
          } (Military rank, CIV and CTR values are available)`
        )
        .default("")
        .label(Settings.fields.person.rank),
      gender: yup
        .string()
        .nullable()
        .when("role", (role, schema) =>
          Person.isAdvisor({ role })
            ? schema.required(
              `You must provide the ${Settings.fields.person.gender}`
            )
            : schema.nullable()
        )
        .default("")
        .label(Settings.fields.person.gender),
      phoneNumber: yup
        .string()
        .nullable()
        .default("")
        .label(Settings.fields.person.phoneNumber),
      endOfTourDate: yupDate
        .nullable()
        .when("role", (role, schema) =>
          Person.isAdvisor({ role })
            ? schema
              .nullable()
              .required(
                `You must provide the ${Settings.fields.person.endOfTourDate}`
              )
            : schema.nullable()
        )
        .when(["role", "status", "uuid"], (role, status, uuid, schema) =>
          Person.isAdvisor({ role }) && Person.isNewFormUser({ status, uuid })
            ? schema.test(
              "end-of-tour-date",
              `The ${
                Settings.fields.person.endOfTourDate
              } date must be in the future`,
              endOfTourDate => endOfTourDate > Date.now()
            )
            : schema.nullable()
        )
        .default(null)
        .label(Settings.fields.person.endOfTourDate),
      biography: yup
        .string()
        .nullable()
        .default(""),
      position: yup
        .object()
        .nullable()
        .default({}),
      role: yup
        .string()
        .nullable()
        .default(() => Person.ROLE.PRINCIPAL),
      status: yup
        .string()
        .nullable()
        .default(() => Person.STATUS.ACTIVE)
    })
    .concat(Model.yupSchema)

  static autocompleteQuery =
    "uuid, name, rank, role, status, endOfTourDate, position { uuid, name, type, code, status, organization { uuid, shortName }, location {uuid, name} }"

  static autocompleteTemplate(person) {
    return (

        {new Person(person).toString()}{" "}
        {person.position &&
          `- (${person.position.name}` +
            (person.position.code ? `, ${person.position.code}` : "") +
            (person.position.location
              ? `, ${person.position.location.name}`
              : "") +
            ")"}

    )
  }

  static humanNameOfRole(role) {
    if (role === Person.ROLE.ADVISOR) {
      return Settings.fields.advisor.person.name
    }
    if (role === Person.ROLE.PRINCIPAL) {
      return principalPerson.name
    }
    throw new Error(`Unrecognized role: ${role}`)
  }

  static humanNameOfStatus(status) {
    return utils.sentenceCase(status)
  }

  constructor(props) {
    super(Model.fillObject(props, Person.yupSchema))
  }

  humanNameOfRole() {
    return Person.humanNameOfRole(this.role)
  }

  humanNameOfStatus() {
    return Person.humanNameOfStatus(this.status)
  }

  static isNewUser(person) {
    return person.status === Person.STATUS.NEW_USER
  }

  static isNewFormUser(person) {
    return _isEmpty(person.uuid) || person.status === Person.STATUS.NEW_USER
  }

  isNewUser() {
    return Person.isNewUser(this)
  }

  static isAdvisor(person) {
    return person.role === Person.ROLE.ADVISOR
  }

  isAdvisor() {
    return Person.isAdvisor(this)
  }

  isPrincipal() {
    return this.role === Person.ROLE.PRINCIPAL
  }

  isAdmin() {
    return this.position && this.position.type === Position.TYPE.ADMINISTRATOR
  }

  isSuperUser() {
    return (
      this.position &&
      (this.position.type === Position.TYPE.SUPER_USER ||
        this.position.type === Position.TYPE.ADMINISTRATOR)
    )
  }

  hasAssignedPosition() {
    // has a non-empty position with a non-zero uuid
    return !_isEmpty(this.position) && !!this.position.uuid
  }

  hasActivePosition() {
    return (
      this.hasAssignedPosition() &&
      this.position.status === Position.STATUS.ACTIVE
    )
  }

  // Checks if this user is a valid super user for a particular organization
  // Must be either
  // - An Administrator
  // - A super user and this org is a PRINCIPAL_ORG
  // - A super user for this organization
  // - A super user for this orgs parents.
  isSuperUserForOrg(org) {
    if (!org) {
      return false
    }
    if (this.position && this.position.type === Position.TYPE.ADMINISTRATOR) {
      return true
    }
    if (this.position && this.position.type !== Position.TYPE.SUPER_USER) {
      return false
    }
    if (org.type === Organization.TYPE.PRINCIPAL_ORG) {
      return true
    }

    if (!this.position || !this.position.organization) {
      return false
    }
    let orgs = this.position.organization.allDescendantOrgs || []
    orgs.push(this.position.organization)
    let orgUuids = orgs.map(o => o.uuid)

    return orgUuids.includes(org.uuid)
  }

  iconUrl() {
    if (this.isAdvisor()) {
      return RS_ICON
    } else if (this.isPrincipal()) {
      return AFG_ICON
    } else {
      return PEOPLE_ICON
    }
  }

  toString() {
    if (this.rank) {
      return this.rank + " " + this.name
    } else {
      return this.name || this.uuid
    }
  }

  static fullName(person, doTrim) {
    if (person.lastName && person.firstName) {
      return `${Person.formattedLastName(person.lastName, doTrim)}${
        Person.nameDelimiter
      } ${Person.formattedFirstName(person.firstName, doTrim)}`
    } else if (person.lastName) {
      return Person.formattedLastName(person.lastName)
    } else {
      return ""
    }
  }

  static formattedLastName(lastName, doTrim) {
    let r = lastName.toUpperCase()
    if (doTrim) {
      r = r.trim()
    }
    return r
  }

  static formattedFirstName(firstName, doTrim) {
    let r = firstName
    if (doTrim) {
      r = r.trim()
    }
    return r
  }

  static parseFullName(name) {
    const delimiter = name.indexOf(Person.nameDelimiter)
    let lastName = name
    let firstName = ""

    if (delimiter > -1) {
      lastName = name.substring(0, delimiter)
      firstName = name.substring(delimiter + 1, name.length)
    }

    return {
      lastName: lastName.trim().toUpperCase(),
      firstName: firstName.trim()
    }
  }
}
<|endoftext|>


<commit_before>
var url="http://127.0.0.1:1338/";

//Funciones que modifican el index

function inicio(){
	mostrarCabecera();
}

function borrarControl(){
	$('#control').remove();
}

function mostrarCabecera(){
	$('#cabecera').remove();
	$('#control').append('

Nombre Jugador
<input type="text" id="nombre" placeholder="introduce tu nombre">
');
	botonNombre();
}

function botonNombre(){
	var nombre="";
	$('#control').append('<button type="button" id="nombreBtn" class="btn btn-primary btn-md">Iniciar partida</button>');
	$('#nombreBtn').on('click',function(){
		nombre=$('#nombre').val();
		$('#nombre').remove();
		$('#nombreBtn').remove();
		crearUsuario(nombre);
	});
}

//Funciones de comunicación con el servidor

function crearUsuario(nombre){
	if (nombre==""){
		nombre="jugador";
	}
	$.getJSON(url+'crearUsuario/'+nombre,function(datos){
		mostrarInfoJugador(datos);
		crearNivel(datos);
	});
	//mostrar datos

}

function mostrarInfoJugador(datos){
	$('#datos').remove();
	//$('#control').append('
Nombre: '+datos.nombre+' Nivel: '+datos.nivel+' Id:'+datos.id+'
');
	$('#control').append('

'+datos.nombre+'

');
	$('#control').append('

Nivel: '+datos.nivel+'

');
	$('#control').append('

Utiliza las flechas para moverte. Debes alcanzar el cielo en el menor tiempo posible

');
	}

function crearNivel(datos){
	$('#juegoId').append('');

}<commit_msg>despliegue en heroku<commit_after>
//var url="http://127.0.0.1:1338/";

//Funciones que modifican el index

function inicio(){
	mostrarCabecera();
}

function borrarControl(){
	$('#control').remove();
}

function mostrarCabecera(){
	$('#cabecera').remove();
	$('#control').append('

Nombre Jugador
<input type="text" id="nombre" placeholder="introduce tu nombre">
');
	botonNombre();
}

function botonNombre(){
	var nombre="";
	$('#control').append('<button type="button" id="nombreBtn" class="btn btn-primary btn-md">Iniciar partida</button>');
	$('#nombreBtn').on('click',function(){
		nombre=$('#nombre').val();
		$('#nombre').remove();
		$('#nombreBtn').remove();
		crearUsuario(nombre);
	});
}

//Funciones de comunicación con el servidor

function crearUsuario(nombre){
	if (nombre==""){
		nombre="jugador";
	}
	$.getJSON(url+'crearUsuario/'+nombre,function(datos){
		mostrarInfoJugador(datos);
		crearNivel(datos);
	});
	//mostrar datos

}

function mostrarInfoJugador(datos){
	$('#datos').remove();
	//$('#control').append('
Nombre: '+datos.nombre+' Nivel: '+datos.nivel+' Id:'+datos.id+'
');
	$('#control').append('

'+datos.nombre+'

');
	$('#control').append('

Nivel: '+datos.nivel+'

');
	$('#control').append('

Utiliza las flechas para moverte. Debes alcanzar el cielo en el menor tiempo posible

');
	}

function crearNivel(datos){
	$('#juegoId').append('');

}<|endoftext|>


<commit_before>// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Provides a base class for custom Error objects such that the
 * stack is corectly maintained.
 *
 *
 */

goog.provide('goog.debug.Error');

/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
goog.debug.Error = function(opt_msg) {

  // Ensure there is a stack trace.
  this.stack = new Error().stack || '';

  if (opt_msg) {
    this.message = String(opt_msg);
  }
};
goog.inherits(goog.debug.Error, Error);

/** @inheritDoc */
goog.debug.Error.prototype.name = 'CustomError';
<commit_msg><commit_after>// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Provides a base class for custom Error objects such that the
 * stack is correctly maintained.
 *
 * You should never need to throw goog.debug.Error(msg) directly, Error(msg) is
 * sufficient.
 *
 *
 */

goog.provide('goog.debug.Error');

/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
goog.debug.Error = function(opt_msg) {

  // Ensure there is a stack trace.
  this.stack = new Error().stack || '';

  if (opt_msg) {
    this.message = String(opt_msg);
  }
};
goog.inherits(goog.debug.Error, Error);

/** @inheritDoc */
goog.debug.Error.prototype.name = 'CustomError';
<|endoftext|>


<commit_before>var socket = new WebSocket("wss://" + location.hostname);

socket.onopen = function () {
    //request output info
    socket.send('req-output-info');
};

socket.onerror = function (error) {
  //TODO show error on screen
  console.log('WebSocket Error ' + error);
};

socket.onclose = function () {
    alert("Connection with server closed; Maybe the server wasn't found, it shut down or you're behind a firewall/proxy.");
    //TODO should we attempt to reconnect?
};

socket.onmessage = function (e) {
    if(typeof e.data === "string"){
        //create canvas object based on output (json?) reply
        var canvas = document.createElement('canvas');

        //TODO map canvas geometry to reported server output
        canvas.id     = "output";
        canvas.width  = 1024;
        canvas.height = 768;
        canvas.style.position = "absolute";
        canvas.style.border   = "1px solid";

        //canvas left & right mouse click listeners
        //TODO add middle click? How to emulate other buttons?
        canvas.addEventListener('click',function(evt){
            //p(ointer):b(utton):0:t(ime):xxxxxxx
            socket.send("p:b:0:t:"+Date.now())
        },false);
        canvas.addEventListener('contextmenu', function(ev) {
            ev.preventDefault();
            //p(ointer):b(utton):1:t(ime):xxxxxxx
            socket.send("p:b:1:t:"+Date.now())
            return false;
        }, false);

        //TODO canvas key events

        document.body.appendChild(canvas);
    } else {
        //TODO read data as image & put it in the canvas
    }
};<commit_msg>more canvas/websocket client rendering code<commit_after>//use browser url to find socket to connect to
var socket = new WebSocket("wss://" + location.hostname);
//initialize socket using text for communication, used to initialize our output
socket.binaryType = "text";

function createOutputCanvas(outputInfo){
    //make canvas global
    canvas = document.createElement('canvas');

    //TODO map canvas geometry to reported server output
    canvas.id     = "output";
    canvas.width  = 1024;
    canvas.height = 768;
    canvas.style.position = "absolute";
    canvas.style.border   = "1px solid";

    //add canvas input listeners
    canvas.addEventListener('mousedown',function(ev){
        //p(ointer):d(own):b(utton):xxxx:t(ime):xxxxxxx
        socket.send("p:d:b:"+ev.button+":t:"+Date.now())
    },false);
    canvas.addEventListener('mouseup',function(ev){
        //p(ointer):u(p):b(utton):0:t(ime):xxxxxxx
        socket.send("p:u:b:"+ev.button+":t:"+Date.now())
    },false);
    canvas.addEventListener('contextmenu', function(ev) {
        ev.preventDefault();
        return false;
    }, false);
    canvas.addEventListener('mousemove', function(ev) {
        var rect = canvas.getBoundingClientRect();
        var x = Math.round((evt.clientX-rect.left)/(rect.right-rect.left)*canvas.width);
        var y = Math.round((evt.clientY-rect.top)/(rect.bottom-rect.top)*canvas.height);
        //p(ointer):m(otion):x:xxxx:y:xxxx:t(ime):xxxxxxx
        socket.send("p:m:x:"+x+":y:"+y+":t:"+Date.now())
    }, false);
    canvas.addEventListener('keydown', function(ev) {
        //k(ey):d(own):c(ode):xxxx:t(ime):xxxxxx
        socket.send("k:d:c"+ev.keyCode+":t:"+Date.now());
    }, false);
    canvas.addEventListener('keyup', function(ev) {
        //k(ey):u(p):c(ode):xxxx:t(ime):xxxxxx
        socket.send("k:d:c"+ev.keyCode+":t:"+Date.now());
    }, false);

    document.body.appendChild(canvas);
    //output canvas created, switch to binary mode so we can receive output frames
    socket.binaryType = "blob";
    //notify server we have created the output canvas and are now ready to receive binary frames
    socket.send("ack-output-info");
}

socket.onopen = function () {
    //request output info so we can initialize our html5 canvas with the correct size
    socket.send("req-output-info");
};

socket.onerror = function (error) {
  //TODO show error on screen
  console.log("WebSocket Error " + error);
};

socket.onclose = function () {
    alert("Connection with server closed; Maybe the server wasn't found, it shut down or you're behind a firewall/proxy.");
    //TODO should we attempt to reconnect?
};

socket.onmessage = function (e) {
    if(typeof e.data === "string"){
        //TODO validate reply
        //create canvas object based on output info reply
        createOutputCanvas(e.data);
    } else {
        //read binary data as image & put it in the canvas
        var ctx = canvas.getContext('2d');
        var img = new Image;
        img.onload = function() {
            ctx.drawImage(img, canvas.width, canvas.height);
        }
        img.src = URL.createObjectURL(e.data);
    }
};<|endoftext|>


<commit_before>
angular.module('os.biospecimen.participant.addedit', ['os.biospecimen.models', 'os.administrative.models'])
  .controller('ParticipantAddEditCtrl', function(
    $scope, $state, $stateParams, $translate, $modal, $q,
    cp, cpr, extensionCtxt, hasDict, cpDict, twoStepReg, layout,
    mrnAccessRestriction, addPatientOnLookupFail, lookupFieldsCfg,
    lockedFields, firstCpEvent,
    CpConfigSvc, CollectionProtocolRegistration, Participant,
    Visit, CollectSpecimensSvc, Site, PvManager, ExtensionsUtil, Alerts) {

    var availableSites = [];
    var inputParticipant = null;

    function init() {
      $scope.cpId = $stateParams.cpId;

      $scope.cp = cp;
      $scope.cpr = angular.copy(cpr);
      setBirthDate($scope.cpr);

      $scope.partCtx = {
        cpSites: cp.cpSites.map(function(cpSite) { return cpSite.siteName; }),
        firstCpEvent: firstCpEvent,
        fieldOpts: {lockedFields: lockedFields, layout: layout, mdInput: false},
        twoStep: lookupFieldsCfg.configured,
        edit: !!cpr.id,
        obj: {cpr: $scope.cpr, cp: cp},
        inObjs: ['cpr'],
        mrnAccessRestriction: mrnAccessRestriction,
        step: !cpr.id ? 'lookupParticipant' : 'registerParticipant',
        matchingTabOpts: {
          table: getMatchingTabFields(),
          multiple: true,
          static: true,
          allowBulkUpload: false,
          rowSelect: 'single',
          onRowSelect: onMatchSelect
        }
      }

      $scope.disableFieldOpts = {}
      if (!!cpr.id && cpr.participant.source != 'OpenSpecimen') {
        initDisableFieldOpts(lockedFields);
      }

      $scope.deFormCtrl = {};
      $scope.extnOpts = ExtensionsUtil.getExtnOpts(
        $scope.cpr.participant, extensionCtxt, $scope.disableFieldOpts.customFields);

      if (!hasDict) {
        $scope.cpr.participant.addPmi($scope.cpr.participant.newPmi());
        loadPvs();
      }

    };

    function onMatchSelect(match) {
      $scope.partCtx.selectedMatch = match;
    }

    function initDisableFieldOpts(lockedFields) {
      $scope.disableFieldOpts = {
        fields: getStaticFields(lockedFields),
        customFields: getCustomFields(lockedFields)
      }
      $scope.partCtx.fieldOpts = {lockedFields: lockedFields};
    }

    function getStaticFields(fields) {
      var result = {};
      angular.forEach(fields,
        function(f) {
          if (f.indexOf('cpr.participant.extensionDetail.attrsMap') != 0) {
            result[f] = true;
          }
        }
      );

      return result;
    }

    function getCustomFields(fields) {
      return fields.filter(
        function(f) {
          return f.indexOf('cpr.participant.extensionDetail.attrsMap') == 0
        }
      ).map(
        function(f) {
          return f.substring('cpr.participant.extensionDetail.attrsMap.'.length);
        }
      );
    }

    function getMatchingTabFields() {
      if (!hasDict) {
        return [];
      }

      var fields = cpDict.filter(
        function(field) {
          return field.name.indexOf('cpr.participant.') == 0 &&
            field.name.indexOf('cpr.participant.extensionDetail.') == -1;
        }
      ).map(
        function(field) {
          var result = angular.copy(field);
          result.formatType = result.type;
          result.type = 'span'
          return result;
        }
      );

      fields.push({
        name: 'cps',
        caption: 'Registered Protocols',
        type: 'span',
        multiple: true
      });

      return fields;
    }

    function loadPvs() {
      $scope.op            = !!$scope.cpr.id ? 'Update' : 'Create';
      $scope.genders       = PvManager.getPvs('gender');
      $scope.vitalStatuses = PvManager.getPvs('vital-status');
    };

    function registerParticipant(proceedToSpmnColl) {
      var formCtrl = $scope.deFormCtrl.ctrl;
      if (formCtrl && !formCtrl.validate()) {
        return;
      }

      var cprToSave = angular.copy($scope.cpr);
      cprToSave.cpId = $scope.cpId;
      cprToSave.site = cprToSave.site || '';

      if (formCtrl) {
        cprToSave.participant.extensionDetail = formCtrl.getFormData();
      }

      cprToSave.$saveOrUpdate().then(
        function(savedCpr) {
          if (savedCpr.activityStatus == 'Active') {
            var registeredCps = cpr.participant.registeredCps;
            angular.extend(cpr, savedCpr);
            cpr.site = savedCpr.site;

            cpr.participant.registeredCps = registeredCps;

            if (proceedToSpmnColl) {
              gotoSpmnCollection(savedCpr);
            } else {
              $state.go('participant-detail.overview', {cprId: savedCpr.id});
            }
          } else {
            $state.go('participant-list', {cpId: $scope.cp.id});
          }
        }
      );
    };

    function gotoSpmnCollection(cpr) {
      var state = $state.get('participant-detail.overview');
      var visit = new Visit({cpId: cp.id, cprId: cpr.id, eventId: firstCpEvent.id});
      CollectSpecimensSvc.collectVisit({state: state, params: {cprId: cpr.id}}, cp, cpr.id, visit);
    }

    function updateUsingSelectedParticipant(match) {
      var selectedPart = match.participant;

      var promise;
      if (inputParticipant.participant.source != selectedPart.source) {
        promise = CpConfigSvc.getLockedParticipantFields(selectedPart.source);
      } else {
        var q = $q.defer();
        q.resolve(lockedFields);
        promise = q.promise;
      }

      promise.then(
        function(lockedFields) {
          clearFields($scope, lockedFields);
          angular.extend($scope.cpr.participant, selectedPart);
          registerParticipant();
        }
      );
    };

    function confirmMergeWithSelectedParticipant(match) {
      var modalInstance = $modal.open({
        templateUrl: "modules/biospecimen/participant/confirm-merge.html",
        controller: function($scope, $state, $modalInstance, cpr) {
          $scope.cpr = cpr;
          $scope.params = {
            cpr: cpr,
            url: $state.href('participant-detail.overview', {cpId: cpr.cpId, cprId: cpr.id})
          };

          $scope.ok = function() {
            $modalInstance.close(true);
          }

          $scope.cancel = function() {
            $modalInstance.dismiss('cancel');
          }
        },
        resolve: {
          cpr: function() {
            return cpr;
          }
        }
      });

      modalInstance.result.then(
        function() {
          updateUsingSelectedParticipant(match);
        }
      );
    }

    function clearFields(inputObj, fieldsToClear) {
      angular.forEach(fieldsToClear,
        function(field) {
          var attrs = field.split('\.');
          var obj = inputObj;
          for (var i = 0; i < attrs.length - 1; ++i) {
            obj = obj[attrs[i]];
            if (!obj) {
              return;
            }
          }

          delete obj[attrs[attrs.length - 1]];
        }
      );
    }

    function handleParticipantMatches(matches, proceedToSpmnColl) {
      $scope.partCtx.matches = matches;
      $scope.partCtx.matchAutoSelected = false;
      $scope.partCtx.allowIgnoreMatches = matches.every(
        function(match) {
          return match.matchedAttrs.length == 1 && match.matchedAttrs[0] == 'lnameAndDob';
        }
      );

      angular.forEach(matches,
        function(match) {
          if (!match.participant.id && match.participant.source != 'OpenSpecimen') {
            //
            // Ask API to not use existing participant ID
            //
            match.participant.id = -1;
          }

          match.cpr = {participant: match.participant};
          match.cps = match.participant.registeredCps.map(function(reg) { return reg.cpShortTitle; });
        }
      );

      if (!matches || matches.length == 0) {
        if ($scope.partCtx.edit) {
          registerParticipant();
        } else if (!addPatientOnLookupFail && lookupFieldsCfg.configured) {
          Alerts.error('participant.no_matching_participant');
        } else {
          if (!lookupFieldsCfg.configured) {
            registerParticipant(proceedToSpmnColl);
          } else {
            $scope.partCtx.step = 'registerParticipant';
          }
        }
      } else if (isAutoSelect(matches)) {
        $scope.partCtx.matchAutoSelected = true;
        useSelectedMatch(matches[0]);
      } else {
        $scope.partCtx.step = 'chooseMatch';
      }
    }

    function isAutoSelect(matches) {
      if ($scope.partCtx.edit || matches.length > 1) {
        return false;
      }

      if (matches.length == 1 && matches[0].matchedAttrs.length == 1 && matches[0].matchedAttrs[0] == 'lnameAndDob') {
        return false;
      }

      return true;
    }

    function useSelectedMatch(match) {
      var matchedCprId = undefined;
      var participant = match.participant;
      for (var i = 0; i < participant.registeredCps.length; ++i) {
        if (participant.registeredCps[i].cpId == cp.id) {
          matchedCprId = participant.registeredCps[i].cprId;
          break;
        }
      }

      if ($scope.partCtx.edit) {
        if (match.participant.id != -1) {
          confirmMergeWithSelectedParticipant(match);
        } else {
          updateUsingSelectedParticipant(match);
        }

        return;
      }

      if (!matchedCprId) {
        prepareRegParticipantStep(undefined, match.participant);
      } else {
        CollectionProtocolRegistration.getById(matchedCprId).then(prepareRegParticipantStep);
      }
    }

    function prepareRegParticipantStep(matchedCpr, matchedParticipant) {
      if (!matchedCpr && !matchedParticipant) {
        return;
      }

      if (matchedCpr) {
        matchedParticipant = matchedCpr.participant;
        ExtensionsUtil.createExtensionFieldMap(matchedParticipant);
      }

      return setLockingOpts(matchedParticipant.source).then(
        function(lockedFields) {
          clearFields($scope, lockedFields);
          angular.extend($scope.cpr.participant, matchedParticipant);
          if (!hasDict) {
            $scope.cpr.participant.addPmi($scope.cpr.participant.newPmi());
          }

          if (matchedCpr) {
            delete matchedCpr.participant;
            angular.extend($scope.cpr, matchedCpr);
            $scope.extnOpts = ExtensionsUtil.getExtnOpts(
              $scope.cpr.participant, extensionCtxt, $scope.disableFieldOpts.customFields);
          }

          $scope.partCtx.step = 'registerParticipant';
        }
      );
    }

    function setLockingOpts(source) {
      if (!source || source == 'OpenSpecimen') {
        initDisableFieldOpts([]);

        var q = $q.defer();
        q.resolve([]);
        return q.promise;
      } else {
        return CpConfigSvc.getLockedParticipantFields(source).then(
          function(lockedFields) {
            initDisableFieldOpts(lockedFields);
            return lockedFields;
          }
        );
      }
    }

    function setBirthDate(reg) {
      if (!reg.participant.birthDateStr) {
        return;
      }

      var parts = reg.participant.birthDateStr.split('-');
      reg.participant.birthDate = new Date(parts[0], parseInt(parts[1]) - 1, parts[2]);
    }

    function cacheInputParticipant() {
      var formCtrl = $scope.deFormCtrl.ctrl;
      if (formCtrl && !formCtrl.validate()) {
        return;
      }

      inputParticipant = angular.copy($scope.cpr);
      if (formCtrl) {
        inputParticipant.participant.extensionDetail = formCtrl.getFormData();
        // ExtensionsUtil.createExtensionFieldMap(inputParticipant.participant)
      }
    }

    function revertToInputParticipant() {
      var formCtrl = $scope.deFormCtrl.ctrl;
      $scope.partCtx.obj.cpr = $scope.cpr = angular.copy(inputParticipant);

      if (formCtrl) {
        $scope.extnOpts = ExtensionsUtil.getExtnOpts(
          $scope.cpr.participant, extensionCtxt, $scope.disableFieldOpts.customFields);
      }

      var birthDate = $scope.cpr.participant.birthDate;
      if (!!birthDate && typeof birthDate == 'string') {
        $scope.cpr.participant.birthDateStr = birthDate;
        setBirthDate($scope.cpr);
      }
    }

    $scope.pmiText = function(pmi) {
      return pmi.siteName + (pmi.mrn ? " (" + pmi.mrn + ")" : "");
    }

    $scope.addPmiIfLast = function(idx) {
      if (lockedFields.indexOf('cpr.participant.pmis') != -1) {
        return;
      }

      var participant = $scope.cpr.participant;
      if (idx == participant.pmis.length - 1) {
        participant.addPmi(participant.newPmi());
      }
    };

    $scope.removePmi = function(pmi) {
      var participant = $scope.cpr.participant;
      participant.removePmi(pmi);

      if (participant.pmis.length == 0) {
        participant.addPmi(participant.newPmi());
      }
    };

    $scope.register = function(proceedToSpmnColl) {
      if ($scope.partCtx.edit) {
        cacheInputParticipant();
        $scope.cpr.participant.getMatchingParticipants().then(handleParticipantMatches);
      } else {
        registerParticipant(proceedToSpmnColl);
      }
    };

    $scope.lookup = function(proceedToSpmnColl) {
      cacheInputParticipant();
      $scope.cpr.participant.getMatchingParticipants().then(
        function(matches) {
          handleParticipantMatches(matches, proceedToSpmnColl)
        }
      );
    }

    $scope.useSelectedMatch = function() {
      var matches = $scope.partCtx.matches;
      for (var i = 0; i < matches.length; ++i) {
        if (matches[i].$$selected == 'true' || matches[i].$$selected == true) {
          useSelectedMatch(matches[i]);
          return;
        }
      }
    }

    $scope.selectMatch = function(selectedMatch) {
      angular.forEach($scope.partCtx.matches,
        function(match) {
          if (match != selectedMatch) {
            match.$$selected = false;
          }
        }
      );

      onMatchSelect(selectedMatch);
    }

    $scope.ignoreMatches = function() {
      angular.forEach($scope.partCtx.matches,
        function(match) {
          match.$$selected = false;
        }
      );
      $scope.partCtx.obj.cpr = $scope.cpr = angular.copy(inputParticipant);
      $scope.partCtx.step = 'registerParticipant';
    }

    $scope.previous = function() {
      if ($scope.partCtx.step == 'registerParticipant') {
        if ($scope.partCtx.edit) {
          $scope.back();
        } else if (isAutoSelect($scope.partCtx.matches)) {
          revertToInputParticipant();
          $scope.partCtx.step = 'lookupParticipant';
        } else {
          $scope.partCtx.step = 'chooseMatch';
        }
      } else if ($scope.partCtx.step == 'chooseMatch') {
        revertToInputParticipant();
        $scope.partCtx.step = $scope.partCtx.edit ? 'registerParticipant' : 'lookupParticipant';
      } else {
        $scope.back();
      }
    }

    init();
  });
<commit_msg>The registered CPs collection will be null/undefined for participant records pulled from staging table.<commit_after>
angular.module('os.biospecimen.participant.addedit', ['os.biospecimen.models', 'os.administrative.models'])
  .controller('ParticipantAddEditCtrl', function(
    $scope, $state, $stateParams, $translate, $modal, $q,
    cp, cpr, extensionCtxt, hasDict, cpDict, twoStepReg, layout,
    mrnAccessRestriction, addPatientOnLookupFail, lookupFieldsCfg,
    lockedFields, firstCpEvent,
    CpConfigSvc, CollectionProtocolRegistration, Participant,
    Visit, CollectSpecimensSvc, Site, PvManager, ExtensionsUtil, Alerts) {

    var availableSites = [];
    var inputParticipant = null;

    function init() {
      $scope.cpId = $stateParams.cpId;

      $scope.cp = cp;
      $scope.cpr = angular.copy(cpr);
      setBirthDate($scope.cpr);

      $scope.partCtx = {
        cpSites: cp.cpSites.map(function(cpSite) { return cpSite.siteName; }),
        firstCpEvent: firstCpEvent,
        fieldOpts: {lockedFields: lockedFields, layout: layout, mdInput: false},
        twoStep: lookupFieldsCfg.configured,
        edit: !!cpr.id,
        obj: {cpr: $scope.cpr, cp: cp},
        inObjs: ['cpr'],
        mrnAccessRestriction: mrnAccessRestriction,
        step: !cpr.id ? 'lookupParticipant' : 'registerParticipant',
        matchingTabOpts: {
          table: getMatchingTabFields(),
          multiple: true,
          static: true,
          allowBulkUpload: false,
          rowSelect: 'single',
          onRowSelect: onMatchSelect
        }
      }

      $scope.disableFieldOpts = {}
      if (!!cpr.id && cpr.participant.source != 'OpenSpecimen') {
        initDisableFieldOpts(lockedFields);
      }

      $scope.deFormCtrl = {};
      $scope.extnOpts = ExtensionsUtil.getExtnOpts(
        $scope.cpr.participant, extensionCtxt, $scope.disableFieldOpts.customFields);

      if (!hasDict) {
        $scope.cpr.participant.addPmi($scope.cpr.participant.newPmi());
        loadPvs();
      }

    };

    function onMatchSelect(match) {
      $scope.partCtx.selectedMatch = match;
    }

    function initDisableFieldOpts(lockedFields) {
      $scope.disableFieldOpts = {
        fields: getStaticFields(lockedFields),
        customFields: getCustomFields(lockedFields)
      }
      $scope.partCtx.fieldOpts = {lockedFields: lockedFields};
    }

    function getStaticFields(fields) {
      var result = {};
      angular.forEach(fields,
        function(f) {
          if (f.indexOf('cpr.participant.extensionDetail.attrsMap') != 0) {
            result[f] = true;
          }
        }
      );

      return result;
    }

    function getCustomFields(fields) {
      return fields.filter(
        function(f) {
          return f.indexOf('cpr.participant.extensionDetail.attrsMap') == 0
        }
      ).map(
        function(f) {
          return f.substring('cpr.participant.extensionDetail.attrsMap.'.length);
        }
      );
    }

    function getMatchingTabFields() {
      if (!hasDict) {
        return [];
      }

      var fields = cpDict.filter(
        function(field) {
          return field.name.indexOf('cpr.participant.') == 0 &&
            field.name.indexOf('cpr.participant.extensionDetail.') == -1;
        }
      ).map(
        function(field) {
          var result = angular.copy(field);
          result.formatType = result.type;
          result.type = 'span'
          return result;
        }
      );

      fields.push({
        name: 'cps',
        caption: 'Registered Protocols',
        type: 'span',
        multiple: true
      });

      return fields;
    }

    function loadPvs() {
      $scope.op            = !!$scope.cpr.id ? 'Update' : 'Create';
      $scope.genders       = PvManager.getPvs('gender');
      $scope.vitalStatuses = PvManager.getPvs('vital-status');
    };

    function registerParticipant(proceedToSpmnColl) {
      var formCtrl = $scope.deFormCtrl.ctrl;
      if (formCtrl && !formCtrl.validate()) {
        return;
      }

      var cprToSave = angular.copy($scope.cpr);
      cprToSave.cpId = $scope.cpId;
      cprToSave.site = cprToSave.site || '';

      if (formCtrl) {
        cprToSave.participant.extensionDetail = formCtrl.getFormData();
      }

      cprToSave.$saveOrUpdate().then(
        function(savedCpr) {
          if (savedCpr.activityStatus == 'Active') {
            var registeredCps = cpr.participant.registeredCps;
            angular.extend(cpr, savedCpr);
            cpr.site = savedCpr.site;

            cpr.participant.registeredCps = registeredCps;

            if (proceedToSpmnColl) {
              gotoSpmnCollection(savedCpr);
            } else {
              $state.go('participant-detail.overview', {cprId: savedCpr.id});
            }
          } else {
            $state.go('participant-list', {cpId: $scope.cp.id});
          }
        }
      );
    };

    function gotoSpmnCollection(cpr) {
      var state = $state.get('participant-detail.overview');
      var visit = new Visit({cpId: cp.id, cprId: cpr.id, eventId: firstCpEvent.id});
      CollectSpecimensSvc.collectVisit({state: state, params: {cprId: cpr.id}}, cp, cpr.id, visit);
    }

    function updateUsingSelectedParticipant(match) {
      var selectedPart = match.participant;

      var promise;
      if (inputParticipant.participant.source != selectedPart.source) {
        promise = CpConfigSvc.getLockedParticipantFields(selectedPart.source);
      } else {
        var q = $q.defer();
        q.resolve(lockedFields);
        promise = q.promise;
      }

      promise.then(
        function(lockedFields) {
          clearFields($scope, lockedFields);
          angular.extend($scope.cpr.participant, selectedPart);
          registerParticipant();
        }
      );
    };

    function confirmMergeWithSelectedParticipant(match) {
      var modalInstance = $modal.open({
        templateUrl: "modules/biospecimen/participant/confirm-merge.html",
        controller: function($scope, $state, $modalInstance, cpr) {
          $scope.cpr = cpr;
          $scope.params = {
            cpr: cpr,
            url: $state.href('participant-detail.overview', {cpId: cpr.cpId, cprId: cpr.id})
          };

          $scope.ok = function() {
            $modalInstance.close(true);
          }

          $scope.cancel = function() {
            $modalInstance.dismiss('cancel');
          }
        },
        resolve: {
          cpr: function() {
            return cpr;
          }
        }
      });

      modalInstance.result.then(
        function() {
          updateUsingSelectedParticipant(match);
        }
      );
    }

    function clearFields(inputObj, fieldsToClear) {
      angular.forEach(fieldsToClear,
        function(field) {
          var attrs = field.split('\.');
          var obj = inputObj;
          for (var i = 0; i < attrs.length - 1; ++i) {
            obj = obj[attrs[i]];
            if (!obj) {
              return;
            }
          }

          delete obj[attrs[attrs.length - 1]];
        }
      );
    }

    function handleParticipantMatches(matches, proceedToSpmnColl) {
      $scope.partCtx.matches = matches;
      $scope.partCtx.matchAutoSelected = false;
      $scope.partCtx.allowIgnoreMatches = matches.every(
        function(match) {
          return match.matchedAttrs.length == 1 && match.matchedAttrs[0] == 'lnameAndDob';
        }
      );

      angular.forEach(matches,
        function(match) {
          if (!match.participant.id && match.participant.source != 'OpenSpecimen') {
            //
            // Ask API to not use existing participant ID
            //
            match.participant.id = -1;
          }

          match.cpr = {participant: match.participant};
          match.cps = (match.participant.registeredCps || []).map(function(reg) { return reg.cpShortTitle; });
        }
      );

      if (!matches || matches.length == 0) {
        if ($scope.partCtx.edit) {
          registerParticipant();
        } else if (!addPatientOnLookupFail && lookupFieldsCfg.configured) {
          Alerts.error('participant.no_matching_participant');
        } else {
          if (!lookupFieldsCfg.configured) {
            registerParticipant(proceedToSpmnColl);
          } else {
            $scope.partCtx.step = 'registerParticipant';
          }
        }
      } else if (isAutoSelect(matches)) {
        $scope.partCtx.matchAutoSelected = true;
        useSelectedMatch(matches[0]);
      } else {
        $scope.partCtx.step = 'chooseMatch';
      }
    }

    function isAutoSelect(matches) {
      if ($scope.partCtx.edit || matches.length > 1) {
        return false;
      }

      if (matches.length == 1 && matches[0].matchedAttrs.length == 1 && matches[0].matchedAttrs[0] == 'lnameAndDob') {
        return false;
      }

      return true;
    }

    function useSelectedMatch(match) {
      var matchedCprId = undefined;
      var participant = match.participant;
      for (var i = 0; i < (participant.registeredCps || []).length; ++i) {
        if (participant.registeredCps[i].cpId == cp.id) {
          matchedCprId = participant.registeredCps[i].cprId;
          break;
        }
      }

      if ($scope.partCtx.edit) {
        if (match.participant.id != -1) {
          confirmMergeWithSelectedParticipant(match);
        } else {
          updateUsingSelectedParticipant(match);
        }

        return;
      }

      if (!matchedCprId) {
        prepareRegParticipantStep(undefined, match.participant);
      } else {
        CollectionProtocolRegistration.getById(matchedCprId).then(prepareRegParticipantStep);
      }
    }

    function prepareRegParticipantStep(matchedCpr, matchedParticipant) {
      if (!matchedCpr && !matchedParticipant) {
        return;
      }

      if (matchedCpr) {
        matchedParticipant = matchedCpr.participant;
        ExtensionsUtil.createExtensionFieldMap(matchedParticipant);
      }

      return setLockingOpts(matchedParticipant.source).then(
        function(lockedFields) {
          clearFields($scope, lockedFields);
          angular.extend($scope.cpr.participant, matchedParticipant);
          if (!hasDict) {
            $scope.cpr.participant.addPmi($scope.cpr.participant.newPmi());
          }

          if (matchedCpr) {
            delete matchedCpr.participant;
            angular.extend($scope.cpr, matchedCpr);
            $scope.extnOpts = ExtensionsUtil.getExtnOpts(
              $scope.cpr.participant, extensionCtxt, $scope.disableFieldOpts.customFields);
          }

          $scope.partCtx.step = 'registerParticipant';
        }
      );
    }

    function setLockingOpts(source) {
      if (!source || source == 'OpenSpecimen') {
        initDisableFieldOpts([]);

        var q = $q.defer();
        q.resolve([]);
        return q.promise;
      } else {
        return CpConfigSvc.getLockedParticipantFields(source).then(
          function(lockedFields) {
            initDisableFieldOpts(lockedFields);
            return lockedFields;
          }
        );
      }
    }

    function setBirthDate(reg) {
      if (!reg.participant.birthDateStr) {
        return;
      }

      var parts = reg.participant.birthDateStr.split('-');
      reg.participant.birthDate = new Date(parts[0], parseInt(parts[1]) - 1, parts[2]);
    }

    function cacheInputParticipant() {
      var formCtrl = $scope.deFormCtrl.ctrl;
      if (formCtrl && !formCtrl.validate()) {
        return;
      }

      inputParticipant = angular.copy($scope.cpr);
      if (formCtrl) {
        inputParticipant.participant.extensionDetail = formCtrl.getFormData();
        // ExtensionsUtil.createExtensionFieldMap(inputParticipant.participant)
      }
    }

    function revertToInputParticipant() {
      var formCtrl = $scope.deFormCtrl.ctrl;
      $scope.partCtx.obj.cpr = $scope.cpr = angular.copy(inputParticipant);

      if (formCtrl) {
        $scope.extnOpts = ExtensionsUtil.getExtnOpts(
          $scope.cpr.participant, extensionCtxt, $scope.disableFieldOpts.customFields);
      }

      var birthDate = $scope.cpr.participant.birthDate;
      if (!!birthDate && typeof birthDate == 'string') {
        $scope.cpr.participant.birthDateStr = birthDate;
        setBirthDate($scope.cpr);
      }
    }

    $scope.pmiText = function(pmi) {
      return pmi.siteName + (pmi.mrn ? " (" + pmi.mrn + ")" : "");
    }

    $scope.addPmiIfLast = function(idx) {
      if (lockedFields.indexOf('cpr.participant.pmis') != -1) {
        return;
      }

      var participant = $scope.cpr.participant;
      if (idx == participant.pmis.length - 1) {
        participant.addPmi(participant.newPmi());
      }
    };

    $scope.removePmi = function(pmi) {
      var participant = $scope.cpr.participant;
      participant.removePmi(pmi);

      if (participant.pmis.length == 0) {
        participant.addPmi(participant.newPmi());
      }
    };

    $scope.register = function(proceedToSpmnColl) {
      if ($scope.partCtx.edit) {
        cacheInputParticipant();
        $scope.cpr.participant.getMatchingParticipants().then(handleParticipantMatches);
      } else {
        registerParticipant(proceedToSpmnColl);
      }
    };

    $scope.lookup = function(proceedToSpmnColl) {
      cacheInputParticipant();
      $scope.cpr.participant.getMatchingParticipants().then(
        function(matches) {
          handleParticipantMatches(matches, proceedToSpmnColl)
        }
      );
    }

    $scope.useSelectedMatch = function() {
      var matches = $scope.partCtx.matches;
      for (var i = 0; i < matches.length; ++i) {
        if (matches[i].$$selected == 'true' || matches[i].$$selected == true) {
          useSelectedMatch(matches[i]);
          return;
        }
      }
    }

    $scope.selectMatch = function(selectedMatch) {
      angular.forEach($scope.partCtx.matches,
        function(match) {
          if (match != selectedMatch) {
            match.$$selected = false;
          }
        }
      );

      onMatchSelect(selectedMatch);
    }

    $scope.ignoreMatches = function() {
      angular.forEach($scope.partCtx.matches,
        function(match) {
          match.$$selected = false;
        }
      );
      $scope.partCtx.obj.cpr = $scope.cpr = angular.copy(inputParticipant);
      $scope.partCtx.step = 'registerParticipant';
    }

    $scope.previous = function() {
      if ($scope.partCtx.step == 'registerParticipant') {
        if ($scope.partCtx.edit) {
          $scope.back();
        } else if (isAutoSelect($scope.partCtx.matches)) {
          revertToInputParticipant();
          $scope.partCtx.step = 'lookupParticipant';
        } else {
          $scope.partCtx.step = 'chooseMatch';
        }
      } else if ($scope.partCtx.step == 'chooseMatch') {
        revertToInputParticipant();
        $scope.partCtx.step = $scope.partCtx.edit ? 'registerParticipant' : 'lookupParticipant';
      } else {
        $scope.back();
      }
    }

    init();
  });
<|endoftext|>