Source: app/models/experiment-schema.server.model.js

'use strict';

/*
    Module dependencies
 */
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var _ = require('lodash');
var BluebirdPromise = require('bluebird');

/**
 * The [Mongoose](http://mongoosejs.com/) `Schema` for an
 * `ExperimentSchema`. This schema is used in
 * creating the Mongoose `Model` for an `ExperimentSchema`.
 *
 * @module Node.ExperimentSchemaServerModel
 * @memberof Node
 * @type {mongoose.Schema}
 */
var ExperimentSchemaSchema = new Schema({
    sensors: [String],
    structure: {
        type: Array,
        required: true
    },
    trialCount: {
        type: Number,
        required: true
    },
    mediaPool: [{
        type: Schema.Types.ObjectId,
        ref: 'Media'
    }]
});

/**
 * A validator for Mongoose schemas that requires that its single argument
 * be an `Array` of length greater than 0.
 *
 * @private
 * @inner
 * @param {*} value The value to be validated
 * @returns {Boolean} `true` if the validation is successful; `false` otherwise
 */
function requiredArrayValidator(value) {
    return Array.isArray(value) && value.length > 0;
}

/**
 * Given an ExperimentSchema, an index into that schema's `media` array, and
 * the ID of a `Media` document in the database, this function retrieves the
 * document associated with that ID from the database, and updates the media
 * array of the schema with information from this document. A promise is
 * returned that is resolved when the schema is updated.
 *
 * @private
 * @inner
 * @param {ExperimentSchema} schema
 * @param {Number} index The index into `schema`'s `media` array
 * @param {mongoose.ObjectId} mediaId The ID corresponding to the document
 * that should be retrieved from the database
 * @returns {bluebird.BluebirdPromise}
 */
function updateSchemaForFixedMedia(schema, index, mediaId) {

    console.debug(
        'Updating schema for fixed media at index ' +
        index +
        ' using media with ID ' +
        mediaId,
        schema
    );

    return new BluebirdPromise(function(resolve, reject) {

        // Get the media entry for the ObjectID in slide.media
        var MediaModel = mongoose.model('Media');
        MediaModel.findOne({_id: mediaId}, {
            _id: 1,
            artist: 1,
            title: 1,
            label: 1
        }, function(err, media) {

            if (err) {
                return reject(err);
            }

            if (media) {
                schema.media[index] = media;
                return resolve();
            } else {
                var message = 'Media not found in database for media ID ' +
                    mediaId;
                console.error(message);
                return reject(new Error(message));
            }
        });
    });
}

// Use the above validator for properties that should contain a non-empty array
ExperimentSchemaSchema.path('mediaPool').validate(requiredArrayValidator);

/**
 * This method draws one available `ExperimentSchema` document at random
 * from the database. This `media` array of this document is populated. For
 * any fixed media specified in the document, their details are found and
 * inserted directly into the final document. The callback will be called
 * upon completion and provided with the 'built' experiment.
 *
 * @function buildExperiment
 * @instance
 * @param {Node.module:ExperimentSchemaServerModel~buildExperimentCallback} callback Will be called upon completion
 */
ExperimentSchemaSchema.methods.buildExperiment = function(callback) {

    console.info('Building random experiment.');

    // Populate mediaPool
    this.populate('mediaPool');

    var selectedMedia = _.sample(this.mediaPool, this.trialCount);

    var schemaSubset = {
        _id: this._id,
        structure: this.structure,
        sensors: this.sensors,
        media: selectedMedia
    };

    // Iterate over structure to look for fixed media-playback slides
    var indexInMedia = 0;
    var promises = [];
    for (var i = 0; i < schemaSubset.structure.length; i++) {
        var slide = schemaSubset.structure[i];

        if (slide.name === 'media-playback' && slide.mediaType === 'fixed') {
            promises.push(
                updateSchemaForFixedMedia(
                    schemaSubset,
                    indexInMedia,
                    slide.media
                )
            );
            indexInMedia++;
        } else if (slide.name === 'media-playback' &&
            slide.mediaType === 'random') {
            indexInMedia++;
        }
    }

    BluebirdPromise.all(promises)
        .then(function() {
            return callback(null, schemaSubset);
        })
        .catch(function(err) {
            return callback(err, schemaSubset);
        });
};

/**
 * @callback buildExperimentCallback
 * @param {*} err Any error that `buildExperiment()` encountered
 * @param {ExperimentSchema} schema The built `ExperimentSchema`
 */

// Register schema for ExperimentSchema model
mongoose.model('ExperimentSchema', ExperimentSchemaSchema);
console.debug(
    'Registered schema for ExperimentSchema model.',
    ExperimentSchemaSchema
);

module.exports.schema = ExperimentSchemaSchema;