/**
 * EJDB nodejs binding.
 * @module {EJDB} ejdb
 */

/**************************************************************************************************
 *  NodeJS API for EJDB database library http://ejdb.org
 *  Copyright (C) 2012-2015 Softmotions Ltd <info@softmotions.com>
 *
 *  This file is part of EJDB.
 *  EJDB is free software; you can redistribute it and/or modify it under the terms of
 *  the GNU Lesser General Public License as published by the Free Software Foundation; either
 *  version 2.1 of the License or any later version.  EJDB is distributed in the hope
 *  that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 *  License for more details.
 *  You should have received a copy of the GNU Lesser General Public License along with EJDB;
 *  if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 *  Boston, MA 02111-1307 USA.
 *************************************************************************************************/

var binary = require('node-pre-gyp');
var path = require('path');
var ejdblib_path = binary.find(path.resolve(path.join(__dirname, './package.json')));
var ejdblib = require(ejdblib_path);

var EJDBImpl = ejdblib.NodeEJDB;
const DEFAULT_OPEN_MODE = (ejdblib.JBOWRITER | ejdblib.JBOCREAT);

/**
 * The nodejs database wrapper.
 * @class
 * @memberOf ejdb
 */
var EJDB = function() {
    Object.defineProperty(this, "_impl", {
        value : new EJDBImpl(),
        configurable : false,
        enumerable : false,
        writable : false
    });
    return this;
};

for (var k in ejdblib) { //Export constants
    if (k.indexOf("JB") === 0) {
        EJDB[k] = ejdblib[k];
    }
}

/**
 * EJDB default open mode: `JBOWRITER | JBOCREAT`
 */
EJDB.DEFAULT_OPEN_MODE = DEFAULT_OPEN_MODE;

/**
 * Open database.
 * Return database instance handle object .
 *
 * Default open mode: JBOWRITER | JBOCREAT
 *
 * Depending on `cb` parameter is passed to this function will be
 * either async or blocking.
 *
 * @param {String} dbFile Database main file name
 * @param {Number} openMode [JBOWRITER | JBOCREAT | ..] Bitmask of open modes:
 *      - `JBOREADER` Open as a reader.
 *      - `JBOWRITER` Open as a writer.
 *      - `JBOCREAT` Create db if it not exists
 *      - `JBOTRUNC` Truncate db.
 * @param {Function} [cb] Callback called with error and EJDB object arguments.
 * @returns {EJDB} EJDB database instance.
 */
EJDB.open = function(dbFile, openMode, cb) {
    var db = new EJDB();
    var mode = (openMode > 0) ? openMode : DEFAULT_OPEN_MODE;

    if (cb) {
        db._impl.open(dbFile, mode, function(err) {
            cb(err, db);
        });
        return;
    }
    db._impl.open(dbFile, mode);
    return db;
};

/**
 * Returns true if argument is valid object id (OID) string.
 * @param {String} oid Object id.
 * @return {Boolean}
 */
EJDB.isValidOID = function(oid) {
    if (typeof oid !== "string" || oid.length !== 24) {
        return false;
    }
    var i = 0;
    //noinspection StatementWithEmptyBodyJS
    for (; ((oid[i] >= 0x30 && oid[i] <= 0x39) || /* 1 - 9 */
            (oid[i] >= 0x61 && oid[i] <= 0x66)); /* a - f */
           ++i);
    return (i === 24);
};

/**
 * Close a database.
 * If database was not opened it does nothing.
 *
 * Depending on if cb parameter is passed this function is either async or
 * blocking.
 *
 * @param {Function} [cb] Callback called with error argument.
 */
EJDB.prototype.close = function(cb) {
    return this._impl.close(cb);
};

/**
 * Check if database is in opened state.
 */
EJDB.prototype.isOpen = function() {
    return this._impl.isOpen();
};

/**
 * Return existing collection or create new collection if it was not created before.
 * Collection options `copts`
 * are applied only for newly created collection.
 * For existing collections `copts` takes no effect.
 *
 * Collection options (copts):
 *  {
 *      "cachedrecords" : Max number of cached records in shared memory segment. Default: 0
 *      "records" : Estimated number of records in this collection. Default: 65535.
 *      "large" : Specifies that the size of the database can be larger than 2GB. Default: false
 *      "compressed" : If true collection records will be compressed with DEFLATE compression. Default: false.
 *  }
 *
 * Depending on if cb parameter is passed this function is either async or
 * blocking.
 *
 * @param {String} cname Name of collection.
 * @param {Object} [copts] Collection options.
 * @param {Function} [cb] Callback called with an error argument.
 * @return {*}
 */
EJDB.prototype.ensureCollection = function(cname, copts, cb) {
    if (arguments.length === 2 && typeof copts === 'function') {
        cb = copts;
        copts = {};
    }
    return this._impl.ensureCollection(cname, copts || {}, cb);
};

/**
 * Please use #dropCollection
 * @deprecated Will be removed in v1.1 //todo
 */
EJDB.prototype.removeCollection = function(cname, prune, cb) {
    this.dropCollection.apply(this, arguments);
};

/**
 *  Drop collection.
 *
 *  Call variations:
 *      - dropCollection(cname)
 *      - dropCollection(cname, cb)
 *      - dropCollection(cname, prune, cb)
 *
 * @param {String} cname Name of collection.
 * @param {Boolean} [prune=false] If true the collection data will erased from disk.
 * @param {Function} [cb] Callback function with arguments: (error)
 */
EJDB.prototype.dropCollection = function(cname, prune, cb) {
    if (arguments.length == 2) {
        cb = prune;
        prune = false;
    }
    if (!cb) {
        cb = function() {
        };
    }
    //noinspection JSDeprecatedSymbols
    return this._impl.removeCollection(cname, !!prune, cb);
};

/**
 * Save/update specified in `jsarr` JSON objects in the collection identified by `cname`.
 * If collection with `cname` does not exists it will be created.
 *
 * Every persistent object has unique identifier (OID) placed in the `_id` property.
 * If a saved object does not have `_id` it will be autogenerated.
 * To identify and update object it should contains the `_id` property.
 *
 * If `cb` is not provided this function will be synchronous.
 *
 * Calling variations:
 *      - save(cname, <json object>|<Array of json objects>)
 *      - save(cname, <json object>|<Array of json objects>, options)
 *      - save(cname, <json object>|<Array of json objects>, cb)
 *      - save(cname, <json object>|<Array of json objects>, options, cb)
 *
 * NOTE: Field names of passed JSON objects may not contain `$` and `.` characters,
 *       error condition will be fired in this case.
 *
 * @param {String} cname Collection name.
 * @param {Array|Object} jsarr Signle JSON object or array of JSON objects to save
 * @param {Object?} opts Optional options obj.
 *                  If opts.merge == true saved object will be merged with who's
 *                  already persisted in db.
 * @param {Function} [cb] Callback function with arguments: (error, {Array} of OIDs of saved objects)
 * @return {Array} of OIDs of saved objects in synchronous mode otherwise returns {undefined}.
 */
EJDB.prototype.save = function(cname, jsarr, opts, cb) {
    if (!jsarr) {
        return [];
    }
    if (!Array.isArray(jsarr)) {
        jsarr = [jsarr];
    }
    if (typeof opts == "function") {
        cb = opts;
        opts = null;
    }
    var postprocess = function(oids) {
        //Assign _id property for newly created objects
        for (var i = jsarr.length - 1; i >= 0; --i) {
            var so = jsarr[i];
            if (so != null && so["_id"] !== oids[i]) {
                so["_id"] = oids[i];
            }
        }
    };
    if (cb == null) {
        postprocess(this._impl.save(cname, jsarr, (opts || {})));
        return jsarr;
    } else {
        return this._impl.save(cname, jsarr, (opts || {}), function(err, oids) {
            if (err) {
                cb(err);
                return;
            }
            postprocess(oids);
            cb(null, oids);
        });
    }
};

/**
 * Retrieve JSON object identified by OID.
 * If callback is not provided this function will be synchronous.
 * If callback is provided this function returns {undefined}.
 *
 * @param {String} cname Name of collection
 * @param {String} oid Object identifier (OID)
 * @param {Function} [cb]  Callback function with arguments: (error, obj)
 *        `obj`:  Retrieved JSON object or `null` if it is not found.
 * @return JSON object or {null} if it is not found in synchronous mode otherwise return {undefined}.
 */
EJDB.prototype.load = function(cname, oid, cb) {
    return this._impl.load(cname, oid, cb);
};

/**
 * Remove JSON object identified nby `oid` from the collection `cname`.
 * If callback is not provided this function will be synchronous.
 *
 * @param {String} cname Name of collection
 * @param {String} oid Object identifier (OID)
 * @param {Function} [cb]  Callback function with arguments: (error)
 * @return {undefined}
 */
EJDB.prototype.remove = function(cname, oid, cb) {
    return this._impl.remove(cname, oid, cb);
};

/*
 * - (cname, [cb])
 * - (cname, qobj, [cb])
 * - (cname, qobj, hints, [cb])
 * - (cname, qobj, qobjarr, [cb])
 * - (cname, qobj, qobjarr, hints, [cb])
 */
function parseQueryArgs(args) {
    var cname, qobj, orarr, hints, cb;
    var i = 0;
    cname = args[i++];
    if (typeof cname !== "string") {
        throw new Error("Collection name 'cname' argument must be specified");
    }
    var next = args[i++];
    if (typeof next === "function") {
        cb = next;
    } else {
        qobj = next;
    }
    next = args[i++];
    if (next !== undefined) {
        if (Array.isArray(next)) {
            orarr = next;
            next = args[i++];
        } else if (typeof next === "object") {
            hints = next;
            orarr = null;
            next = args[i++];
        }
        if (!hints && typeof next === "object") {
            hints = next;
            next = args[i++];
        }
        if (typeof next === "function") {
            cb = next;
        }
    }
    return [cname, (qobj || {}), (orarr || []), (hints || {}), (cb || null)];
}

/**
 * Execute query on collection.
 *
 * Execute query on collection of documents.
 * @see See the complete query language specification: http://ejdb.org/doc/ql/ql.html
 *
 * To traverse selected records cursor object is used:
 *      - Cursor#next() Move cursor to the next record and returns true if next record exists.
 *      - Cursor#hasNext() Returns true if cursor can be placed to the next record.
 *      - Cursor#field(name) Retrieve value of the specified field of the current JSON object record.
 *      - Cursor#object() Retrieve whole JSON object with all fields.
 *      - Cursor#reset() Reset cursor to its initial state.
 *      - Cursor#length Read-only property: Number of records placed into cursor.
 *      - Cursor#pos Read/Write property: You can set cursor position: 0 <= pos < length
 *      - Cursor#close() Closes cursor and free cursor resources. Cursor cant be used in closed state.
 *
 * Call variations of find():
 *       - find(cname, [cb])
 *       - find(cname, qobj, [cb])
 *       - find(cname, qobj, hints, [cb])
 *       - find(cname, qobj, qobjarr, [cb])
 *       - find(cname, qobj, qobjarr, hints, [cb])
 *
 * @param {String} cname Name of collection
 * @param {Object} qobj Main JSON query object
 * @param {Array} [qobjarr] Array of additional OR query objects (joined with OR predicate).
 * @param {Object} [hints] JSON object with query hints.
 * @param {Function} [cb] Callback function with arguments: (error, cursor, count) where:
 *          `cursor`: Cursor object to traverse records
 *          `count`:  Total number of selected records.
 * @return If callback is provided returns {undefined}.
 *         If no callback and $onlycount hint is set returns count {Number}.
 *         If no callback and no $onlycount hint returns cursor {Object}.
 *
 */
EJDB.prototype.find = function() {
    //[cname, qobj, orarr, hints, cb]
    var qa = parseQueryArgs(arguments);
    return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]),
            (qa[3]["$onlycount"] ? ejdblib.JBQRYCOUNT : 0),
            qa[4]);
};

/**
 * Retrieve a first found document matched to the specified query.
 * If callback is not provided this function will be synchronous.
 *
 * Call variations of findOne():
 *       - findOne(cname, [cb])
 *       - findOne(cname, qobj, [cb])
 *       - findOne(cname, qobj, hints, [cb])
 *       - findOne(cname, qobj, qobjarr, [cb])
 *       - findOne(cname, qobj, qobjarr, hints, [cb])
 *
 * @param {String} cname Name of collection
 * @param {Object} qobj Main JSON query object
 * @param {Array} [orarr] Array of additional OR query objects (joined with OR predicate).
 * @param {Object} [hints] JSON object with query hints.
 * @param {Function} [cb] Callback function with arguments: (error, obj) where:
 *          `obj`:  Retrieved JSON object or NULL if it is not found.
 * @return  If callback is provided returns {undefined}.
 *          If no callback is provided returns found {Object} or {null}.
 */

EJDB.prototype.findOne = function() {
    //[cname, qobj, orarr, hints, cb]
    var qa = parseQueryArgs(arguments);
    qa[3]["$max"] = 1;
    var cb = qa[4];
    if (cb) {
        return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), 0,
                function(err, cursor) {
                    if (err) {
                        cb(err);
                        return;
                    }
                    if (cursor.next()) {
                        try {
                            cb(null, cursor.object());
                        } finally {
                            cursor.close();
                        }
                    } else {
                        cb(null, null);
                    }
                });
    } else {
        var ret = null;
        var cursor = this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), 0, cb);
        if (cursor && typeof cursor === "object") {
            if (cursor.next()) {
                ret = cursor.object();
            }
            cursor.close();
        }
        return ret;
    }
};

/**
 * Execute ejdb database command.
 *
 * See http://ejdb.org/doc/iexp/index.html#cmd
 *
 * If callback is not provided this function will be synchronous.
 *
 * @param {Object} cmd   BSON command spec.
 * @param {Function} [cb] Callback function with arguments: (error, obj) where:
 *          `obj`:  Command response JSON object.
 * @return Command response JSON object if callback is not provided.
 */
EJDB.prototype.command = function(cmd, cb) {
    if (cb) {
        this._impl.command(cmd, function(err, cursor) {
            if (err) {
                cb(err);
                return;
            }
            if (cursor.next()) {
                try {
                    cb(null, cursor.object());
                } finally {
                    cursor.close();
                }
            } else {
                cb(null, null);
            }
        });
    } else {
        var ret = null;
        var cursor = this._impl.command(cmd);
        if (cursor && typeof cursor === "object") {
            if (cursor.next()) {
                ret = cursor.object();
            }
            cursor.close();
        }
        return ret;
    }
};

/**
 * Convenient method to execute update queries.
 *
 * Call variations of update():
 *    update(cname, qobj, [cb])
 *    update(cname, qobj, hints, [cb])
 *    update(cname, qobj, qobjarr, [cb])
 *    update(cname, qobj, qobjarr, hints, [cb])
 *
 * @param {String} cname Name of collection
 * @param {Object} qobj Main JSON query object
 * @param {Array} [orarr] Array of additional OR query objects (joined with OR predicate).
 * @param {Object} [hints] JSON object with query hints.
 * @param {Function} [cb] Callback function with arguments: (error, count) where:
 *          `count`:  The number of updated records.
 *
 * @return  If callback is provided returns {undefined}.
 *          If no callback is provided returns {Number} of updated objects.
 */
EJDB.prototype.update = function() {
    //[cname, qobj, orarr, hints, cb]
    var qa = parseQueryArgs(arguments);
    var cb = qa[4];
    if (cb) {
        return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), ejdblib.JBQRYCOUNT,
                function(err, cursor, count, log) {
                    if (err) {
                        cb(err, null, log);
                        return;
                    }
                    cb(null, count, log);
                });
    } else {
        return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), ejdblib.JBQRYCOUNT, cb);
    }
};

/**
 * Convenient count(*) operation.
 *
 * Call variations of count():
 *       - count(cname, [cb])
 *       - count(cname, qobj, [cb])
 *       - count(cname, qobj, hints, [cb])
 *       - count(cname, qobj, qobjarr, [cb])
 *       - count(cname, qobj, qobjarr, hints, [cb])
 *
 * @param {String} cname Name of collection
 * @param {Object} qobj Main JSON query object
 * @param {Array} [orarr] Array of additional OR query objects (joined with OR predicate).
 * @param {Object} [hints] JSON object with query hints.
 * @param {Function} [cb] Callback function with arguments: (error, count) where:
 *          `count`:  Number of matching records.
 *
 * @return  If callback is provided returns {undefined}.
 *          If no callback is provided returns {Number} of matched object.
 */
EJDB.prototype.count = function() {
    //[cname, qobj, orarr, hints, cb]
    var qa = parseQueryArgs(arguments);
    var cb = qa[4];
    if (cb) {
        return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), ejdblib.JBQRYCOUNT,
                function(err, cursor, count, log) {
                    if (err) {
                        cb(err, null, log);
                        return;
                    }
                    cb(null, count, log);
                });
    } else {
        return this._impl.query(qa[0], [qa[1]].concat(qa[2], qa[3]), ejdblib.JBQRYCOUNT, cb);
    }
};

/**
 * Synchronize entire database and all of its collections with disk.
 * If callback is not provided this function will be synchronous.
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.sync = function(cb) {
    return this._impl.sync(cb);
};

/**
 * DROP indexes of all types for JSON field path.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {String} path  JSON field path
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.dropIndexes = function(cname, path, cb) {
    return this._impl.setIndex(cname, path, ejdblib.JBIDXDROPALL, cb);
};

/**
 * OPTIMIZE indexes of all types for JSON field path.
 *  Performs B+ tree index file optimization.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {String} path  JSON field path
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.optimizeIndexes = function(cname, path, cb) {
    return this._impl.setIndex(cname, path, ejdblib.JBIDXOP, cb);
};

/**
 * Ensure index presence of String type for JSON field path.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {String} path  JSON field path
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.ensureStringIndex = function(cname, path, cb) {
    return this._impl.setIndex(cname, path, ejdblib.JBIDXSTR, cb);
};

/**
 * Rebuild index of String type for JSON field path.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {String} path  JSON field path
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.rebuildStringIndex = function(cname, path, cb) {
    return this._impl.setIndex(cname, path, ejdblib.JBIDXSTR | ejdblib.JBIDXREBLD, cb);
};

/**
 * Drop index of String type for JSON field path.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {String} path  JSON field path
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.dropStringIndex = function(cname, path, cb) {
    return this._impl.setIndex(cname, path, ejdblib.JBIDXSTR | ejdblib.JBIDXDROP, cb);
};

/**
 * Ensure case insensitive String index for JSON field path.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {String} path  JSON field path
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.ensureIStringIndex = function(cname, path, cb) {
    return this._impl.setIndex(cname, path, ejdblib.JBIDXISTR, cb);
};

/**
 * Rebuild case insensitive String index for JSON field path.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {String} path  JSON field path
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.rebuildIStringIndex = function(cname, path, cb) {
    return this._impl.setIndex(cname, path, ejdblib.JBIDXISTR | ejdblib.JBIDXREBLD, cb);
};

/**
 * Drop case insensitive String index for JSON field path.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {String} path  JSON field path
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.dropIStringIndex = function(cname, path, cb) {
    return this._impl.setIndex(cname, path, ejdblib.JBIDXISTR | ejdblib.JBIDXDROP, cb);
};

/**
 * Ensure index presence of Number type for JSON field path.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {String} path  JSON field path
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.ensureNumberIndex = function(cname, path, cb) {
    return this._impl.setIndex(cname, path, ejdblib.JBIDXNUM, cb);
};

/**
 * Rebuild index of Number type for JSON field path.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {String} path  JSON field path
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.rebuildNumberIndex = function(cname, path, cb) {
    return this._impl.setIndex(cname, path, ejdblib.JBIDXNUM | ejdblib.JBIDXREBLD, cb);
};

/**
 * Drop index of Number type for JSON field path.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {String} path  JSON field path
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.dropNumberIndex = function(cname, path, cb) {
    return this._impl.setIndex(cname, path, ejdblib.JBIDXNUM | ejdblib.JBIDXDROP, cb);
};

/**
 * Ensure index presence of Array type for JSON field path.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {String} path  JSON field path
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.ensureArrayIndex = function(cname, path, cb) {
    return this._impl.setIndex(cname, path, ejdblib.JBIDXARR, cb);
};

/**
 * Rebuild index of Array type for JSON field path.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {String} path  JSON field path
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.rebuildArrayIndex = function(cname, path, cb) {
    return this._impl.setIndex(cname, path, ejdblib.JBIDXARR | ejdblib.JBIDXREBLD, cb);
};

/**
 * Drop index of Array type for JSON field path.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {String} path  JSON field path
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.dropArrayIndex = function(cname, path, cb) {
    return this._impl.setIndex(cname, path, ejdblib.JBIDXARR | ejdblib.JBIDXDROP, cb);
};

/**
 * Get description of EJDB database and its collections.
 */
EJDB.prototype.getDBMeta = function() {
    return this._impl.dbMeta();
};

/**
 * Begin collection transaction.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.beginTransaction = function(cname, cb) {
    return this._impl._txctl(cname, 8/*cmdTxBegin*/, cb);
};

/**
 * Commit collection transaction.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.commitTransaction = function(cname, cb) {
    return this._impl._txctl(cname, 10/*cmdTxCommit*/, cb);
};

/**
 * Abort collection transaction.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {Function} [cb] Optional callback function. Callback args: (error)
 */
EJDB.prototype.rollbackTransaction = function(cname, cb) {
    return this._impl._txctl(cname, 9/*cmdTxAbort*/, cb);
};

/**
 * Get collection transaction status.
 * If callback is not provided this function will be synchronous.
 * @param {String} cname Name of collection
 * @param {Function} [cb] Optional callback function. Callback args: (error, isTxActive)
 * @return If no callback provided it will return {true} if transaction active otherwise {false}
 *         or {undefined} if callback presented.
 */
EJDB.prototype.getTransactionStatus = function(cname, cb) {
    return this._impl._txctl(cname, 11/*cmdTxStatus*/, cb);
};

module.exports = EJDB;


!function(a,b,c,d,e,f,g,h,i){function j(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=r&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a--;)b=h[f=r&f+1],c=c*d+h[r&(h[f]=h[g=r&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function k(a,b){var c,d=[],e=typeof a;if(b&&"object"==e)for(c in a)try{d.push(k(a[c],b-1))}catch(f){}return d.length?d:"string"==e?a:a+"\0"}function l(a,b){for(var c,d=a+"",e=0;e<d.length;)b[r&e]=r&(c^=19*b[r&e])+d.charCodeAt(e++);return n(b)}function m(c){try{return a.crypto.getRandomValues(c=new Uint8Array(d)),n(c)}catch(e){return[+new Date,a,(c=a.navigator)&&c.plugins,a.screen,n(b)]}}function n(a){return String.fromCharCode.apply(0,a)}var o=c.pow(d,e),p=c.pow(2,f),q=2*p,r=d-1,s=c["seed"+i]=function(a,f,g){var h=[];f=1==f?{entropy:!0}:f||{};var r=l(k(f.entropy?[a,n(b)]:null==a?m():a,3),h),s=new j(h);return l(n(s.S),b),(f.pass||g||function(a,b,d){return d?(c[i]=a,b):a})(function(){for(var a=s.g(e),b=o,c=0;p>a;)a=(a+c)*d,b*=d,c=s.g(1);for(;a>=q;)a/=2,b/=2,c>>>=1;return(a+c)/b},r,"global"in f?f.global:this==c)};l(c[i](),b),g&&g.exports?g.exports=s:h&&h.amd&&h(function(){return s})}(this,[],Math,256,6,52,"object"==typeof module&&module,"function"==typeof define&&define,"random");
angular.module('me.mnmlst.ng-dummy', [])

.directive('dummy', function() {

    var texts = {
        lorem: [
            'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
            'In sit amet lorem ipsum. Suspendisse nisl enim, iaculis a blandit et, lobortis ut turpis.',
            'Sed eu velit vel augue ultricies ullamcorper sed ut nulla.',
            'Proin semper tellus sit amet diam fringilla mattis.',
            'Suspendisse elementum congue dolor sit amet posuere.',
            'In sed nunc viverra, pretium erat mattis, semper odio.',
            'Maecenas rutrum nulla felis.',
            'Donec id lorem non eros blandit viverra.',
            'Maecenas adipiscing velit ac urna feugiat, ullamcorper tincidunt ipsum auctor.',
            'Nulla hendrerit tempus condimentum.',
            'Proin ac massa et purus vestibulum interdum vitae sed mauris.',
            'Cras vulputate iaculis velit sed tincidunt.',
            'Integer porta libero non tellus euismod fermentum.',
            'Aenean magna diam, fermentum eu libero sagittis, feugiat venenatis neque.',
            'Fusce facilisis vehicula enim id consectetur.',
            'In feugiat auctor quam eget scelerisque.',
            'Donec est lorem, dignissim sit amet velit sed, fringilla tincidunt orci.',
            'Ut a eleifend dolor.',
            'Nam non scelerisque odio.',
            'Integer sit amet justo vel massa luctus hendrerit.',
            'Vivamus elementum mollis nisl vitae euismod.',
            'Aliquam erat volutpat.',
            'Mauris suscipit tempus eros, in elementum nisl cursus sed.',
            'Sed tincidunt semper nunc, id pellentesque quam egestas at.',
            'Integer a placerat augue.',
            'Nam urna tellus, consectetur at aliquam et, gravida eu odio.',
            'Nam scelerisque, tortor a molestie blandit, risus mauris malesuada urna, eget tempor turpis tortor id lorem.',
            'Morbi eget ornare leo.',
            'Nulla luctus pellentesque magna, ac ultrices nunc semper ut.'
        ],
        lingo: [
            'Perhaps a re-engineering of your current world view will re-energize your online nomenclature to enable a new holistic interactive enterprise internet communication solution.',
            'Upscaling the resurgent networking exchange solutions, achieving a breakaway systemic electronic data interchange system synchronization, thereby exploiting technical environments for mission critical broad based capacity constrained systems.',
             'Fundamentally transforming well designed actionable information whose semantic content is virtually null.',
             'To more fully clarify the current exchange, a few aggregate issues will require addressing to facilitate this distributed communication venue.',
             'In integrating non-aligned structures into existing legacy systems, a holistic gateway blueprint is a backward compatible packaging tangible of immeasurable strategic value in right-sizing conceptual frameworks when thinking outside the box.',
             'This being said, the ownership issues inherent in dominant thematic implementations cannot be understated vis-a vis document distribution on a real operating system consisting primarily of elements regarded as outdated and therefore impelling as a integrated out sourcing avenue to facilitate multi-level name value pairing in static components.',
             'In order to properly merge and articulate these core assets, an acquisition statement outlining the information architecture, leading to a racheting up of convergence across the organic platform is an opportunity without precedent in current applicability transactional modeling.',
             'Implementing these goals requires a careful examination to encompass an increasing complex out sourcing disbursement to ensure the extant parameters are not exceeded while focusing on infrastructure cohesion.',
             'Dynamic demand forecasting indicates that a mainstream approach may establish a basis for leading-edge information processing to insure the diversity of granularity in encompassing expansion of content provided within the multimedia framework under examination.',
             'Empowerment in information design literacy demands the immediate and complete disregard of the entire contents of this cyberspace communication.'
        ],
        disclaimer: [
            'This product is meant for educational purposes only.',
            'Any resemblance to real persons, living or dead is purely coincidental.',
            'Void where prohibited.',
            'Some assembly required.',
            'List each check separately by bank number.',
            'Batteries not included.',
            'Contents may settle during shipment.',
            'Use only as directed.',
            'No other warranty expressed or implied.',
            'Do not use while operating a motor vehicle or heavy equipment.',
            'Postage will be paid by addressee.',
            'Subject to CARB approval.',
            'This is not an offer to sell securities.',
            'Apply only to affected area.',
            'May be too intense for some viewers.',
            'Do not stamp.',
            'Not rated by the Motion Picture Association of America.',
            'Call for nutritional information.',
            'Use other side for additional listings.',
            'Printed on recycled paper.',
            'For recreational use only.',
            'Do not disturb.',
            'All models over 18 years of age.',
            'Prize not redeemable for cash value.',
            'If condition persists, consult your physician.',
            'No user-serviceable parts inside.',
            'Freshest if eaten before date on carton.',
            'To be used as a supplementary restraint system only.',
            'Always fasten your safety belt.',
            'Subject to change without notice.',
            'Times approximate.',
            'Simulated picture.',
            'Do not staple or paper clip.',
            'Price higher east of Alaska.',
            'No postage necessary if mailed in the United States.',
            'Do not X-ray.',
            'Breaking seal constitutes acceptance of agreement.',
            'For off-road use only.',
            'As seen on TV.',
            'One size fits all.',
            'Many suitcases look alike.',
            'Contains a substantial amount of non-tobacco ingredients.',
            'Colors may, in time, fade.',
            'We have sent the forms which seem right for you.',
            'Magnetic media, non-returnable if seal is broken.',
            'Formatted to fit your screen.',
            'Slippery when wet.',
            'For office use only.',
            'Not affiliated with the American Red Cross.',
            'Drop in any mailbox.',
            'Edited for television.',
            'Keep cool, process promptly.',
            'Post office will not deliver without postage.',
            'Do not write under this line.'
        ],
        pomo: [
            'If one examines postcultural discourse, one is faced with a choice: either reject Sartreist existentialism or conclude that discourse must come from communication.',
            'The subject is interpolated into a rationalism that includes consciousness as a totality.',
            'Therefore, Batailleist `powerful communication’ suggests that the significance of the participant is deconstruction, but only if the premise of Sartreist existentialism is valid.',
            'In the works of Eco, a predominant concept is the concept of textual language.',
            'A number of deconstructions concerning not, in fact, discourse, but neodiscourse may be revealed.',
            'In a sense, Bailey states that we have to choose between the semantic paradigm of expression and the dialectic paradigm of expression.',
            'Several materialisms concerning rationalism exist.',
            'However, in Foucault’s Pendulum, Eco denies the semantic paradigm of expression; in The Aesthetics of Thomas Aquinas, although, he examines precapitalist semiotic theory.',
            'Sartre uses the term ‘the semantic paradigm of expression’ to denote the economy of subcultural sexual identity.',
            'It could be said that if rationalism holds, the works of Eco are an example of textual libertarianism.',
            'An abundance of discourses concerning the difference between class and sexual identity may be found.',
            'However, von Ludwig implies that we have to choose between postdialectic deappropriation and cultural Marxism.',
            'Many discourses concerning rationalism exist.',
            'Thus, if precapitalist desublimation holds, we have to choose between Sartreist existentialism and textual rationalism.',
            'If one examines Sartreist existentialism, one is faced with a choice: either accept rationalism or conclude that reality may be used to disempower the underprivileged.',
            'Postcultural modern theory states that narrative is a product of the masses, given that language is distinct from reality.',
            'Therefore, the characteristic theme of Buxton’s model of rationalism is the role of the reader as poet.',
            'The primary theme of the works of Smith is a self-fulfilling whole.',
            'Von Junz implies that we have to choose between Sartreist existentialism and dialectic narrative.',
            'In a sense, the premise of postsemantic textual theory states that consciousness is used to entrench hierarchy.',
            '“Art is meaningless,” says Derrida; however, according to Dietrich, it is not so much art that is meaningless, but rather the collapse, and thus the meaninglessness, of art.',
            'The characteristic theme of Tilton’s analysis of rationalism is the bridge between society and class.',
            'It could be said that if structuralist postcapitalist theory holds, we have to choose between Sartreist existentialism and textual materialism.',
            'Debord’s critique of preconceptualist objectivism holds that the task of the observer is significant form.',
            'However, Hubbard implies that we have to choose between rationalism and capitalist narrative.',
            'The primary theme of the works of Joyce is the role of the reader as observer.',
            'Thus, Baudrillard uses the term ‘subcultural structural theory’ to denote the genre, and eventually the failure, of neosemanticist society.',
            'Dialectic discourse states that consciousness may be used to marginalize minorities.',
            'Therefore, the main theme of Buxton’s model of subtextual deconstruction is not narrative as such, but postnarrative.',
            'Lyotard uses the term ‘rationalism’ to denote the difference between sexual identity and society.',
            'However, if Sartreist existentialism holds, we have to choose between capitalist nihilism and the neotextual paradigm of narrative.'
        ],
        dickens: [
            'Marley was dead: to begin with.',
            'There is no doubt whatever about that.',
            'The register of his burial was signed by the clergyman, the clerk, the undertaker, and the chief mourner.',
            'Scrooge signed it: and Scrooge’s name was good upon ’Change, for anything he chose to put his hand to.',
            'Old Marley was as dead as a door-nail.',
            'Mind! I don’t mean to say that I know, of my own knowledge, what there is particularly dead about a door-nail.',
            'I might have been inclined, myself, to regard a coffin-nail as the deadest piece of ironmongery in the trade.',
            'But the wisdom of our ancestors is in the simile; and my unhallowed hands shall not disturb it, or the Country’s done for.',
            'You will therefore permit me to repeat, emphatically, that Marley was as dead as a door-nail.',
            'Scrooge knew he was dead?',
            'Of course he did.',
            'How could it be otherwise?',
            'Scrooge and he were partners for I don’t know how many years.',
            'Scrooge was his sole executor, his sole administrator, his sole assign, his sole residuary legatee, his sole friend, and sole mourner.',
            'And even Scrooge was not so dreadfully cut up by the sad event, but that he was an excellent man of business on the very day of the funeral, and solemnised it with an undoubted bargain.',
            'The mention of Marley’s funeral brings me back to the point I started from.',
            'There is no doubt that Marley was dead.',
            'This must be distinctly understood, or nothing wonderful can come of the story I am going to relate.',
            'If we were not perfectly convinced that Hamlet’s Father died before the play began, there would be nothing more remarkable in his taking a stroll at night, in an easterly wind, upon his own ramparts, than there would be in any other middle-aged gentleman rashly turning out after dark in a breezy spot—say Saint Paul’s Churchyard for instance—literally to astonish his son’s weak mind.',
            'Scrooge never painted out Old Marley’s name.',
            'There it stood, years afterwards, above the warehouse door: Scrooge and Marley.',
            'The firm was known as Scrooge and Marley.',
            'Sometimes people new to the business called Scrooge Scrooge, and sometimes Marley, but he answered to both names.',
            'It was all the same to him.',
            'Oh! But he was a tight-fisted hand at the grindstone, Scrooge! a squeezing, wrenching, grasping, scraping, clutching, covetous, old sinner!',
            'Hard and sharp as flint, from which no steel had ever struck out generous fire; secret, and self-contained, and solitary as an oyster.',
            'The cold within him froze his old features, nipped his pointed nose, shrivelled his cheek, stiffened his gait; made his eyes red, his thin lips blue; and spoke out shrewdly in his grating voice.',
            'A frosty rime was on his head, and on his eyebrows, and his wiry chin.',
            'He carried his own low temperature always about with him; he iced his office in the dog-days; and didn’t thaw it one degree at Christmas.',
            'External heat and cold had little influence on Scrooge.',
            'No warmth could warm, no wintry weather chill him.',
            'No wind that blew was bitterer than he, no falling snow was more intent upon its purpose, no pelting rain less open to entreaty.',
            'Foul weather didn’t know where to have him.',
            'The heaviest rain, and snow, and hail, and sleet, could boast of the advantage over him in only one respect.',
            'They often “came down” handsomely, and Scrooge never did.'
        ]
    };

    var defaults = {
        P: { sentences: 3 },
        H1: { words: 3 },
        H2: { words: 3 },
        H3: { words: 4 },
        H4: { words: 3 },
        H5: { words: 5 },
        H6: { words: 5 },
        LI: { words: 8 },
        DT: { words: 1 },
        DD: { sentences: 2 },
        BLOCKQUOTE: { sentences: 1 },
        TD: { words: 3 },
        SPAN: { words: 5 },
        EM: { words: 3 },
        STRONG: { words: 3 },
        B: { words: 2 },
        I: { words: 2 },
        Q: { words: 5 }
    };

    Math.seedrandom('dummy');

    var origin; // used to start at 0 each time.

    function dummyText ( opts ) {
        var corpus = opts.corpus || 'lorem',
            i = opts.start,
            isRandom = typeof(i) === 'undefined',
            mustReset = typeof(origin) === 'undefined',
            skip = opts.skip || 1,
            sentences = opts.sentences || 1,
            words = opts.words,
            text = texts[corpus] || texts.lorem,
            len = text.length,
            output = [],
            s;

        if ( isRandom ) {
            i = Math.floor( Math.random() * len );
        }

        if ( mustReset ) {
            origin = i;
        }

        if ( isRandom ) {
            // possible modulo of a negative number, so take care here.
            i = ((i + len - origin) % len + len) % len;
        }

        while( sentences-- ) {
            s = text[i];
            if ( words ) {
                s = s.split(' ').slice(0,words).join(' ');
            }
            output.push( s );
            i = (i + skip) % len;
        }

        return output.join(' ');
    }

    function parseAttrs (str, prior) {
        var opts = prior || {},
            reWords = /^([0-9]+)w$/,
            reSentences = /^([0-9]+)s?$/,
            reStart = /^@([0-9]+)$/,
            reSkip = /^\+([0-9]+)$/,
            reCorpus = /^[a-z]+$/,
            matches;

        str.split(' ').forEach( function (s) {
            if ( matches = reWords.exec(s) ) {
                opts.words = parseInt(matches[1]);
                opts.sentences = 0;
            } else if ( matches = reSentences.exec(s) ) {
                opts.sentences = parseInt(matches[1]);
                opts.words = 0;
            } else if ( matches = reStart.exec(s) ) {
                opts.start = parseInt(matches[1]);
            } else if ( matches = reSkip.exec(s) ) {
                opts.skip = parseInt(matches[1]);
            } else if ( matches = reCorpus.exec(s) ) {
                opts.corpus = matches[0];
            }
        });
        return opts;
    }

    return {
        restrict: 'AE',
        link: function (scope, element, attrs) {
            var opts = defaults[element[0].tagName] || defaults.P,
                span = document.createElement('span');

            if ( attrs.dummy ) {
                opts = parseAttrs( attrs.dummy, opts );
            }

            span.textContent = dummyText( opts );
            span.className = 'ng-dummy-text';
            element[0].appendChild( span );
        }
    };
});


var Reconnect = require('./reconnect');
var extend = require('xtend/immutable');
var clearTimeout = require('timers').clearTimeout;

module.exports = Back;

//
// Takes a set of reconnect options defined in README
//
function Back(options) {
  if (!(this instanceof Back)) {
    return new Back(options);
  }

  this.settings = extend(options);
  this.reconnect = null;
}

Back.prototype.backoff = function backoff(cb) {
  this.reconnect = new Reconnect(cb, this.settings);
};

Back.prototype.close = function close() {
  if (this.reconnect && this.reconnect.timer) {
    return clearTimeout(this.reconnect.timer);
  }
}


"use strict";
/* jshint -W079 */
// # can/map/map.js (aka can.Map)
// `can.Map` provides the observable pattern for JavaScript objects. It
// provides an `attr` and `removeAttr` method that can be used to get/set and
// remove properties and nested properties by calling a "pipeline" of protected
// methods:
//
// - `_get`, `_set`, `_remove` - handle nested properties.
// - `__get`, `__set`, `__remove` - handle triggering events.
// - `___get`, `___set`, `___remove` - read / write / remove raw values.
//
// When `attr` gets or sets multiple properties it calls `_getAttrs` or `_setAttrs`.
//
// [bubble.js](bubble.html) - Handles bubbling of child events to parent events.
// [map_helpers.js](map_helpers.html) - Assorted helpers for handling cycles during serialization or
// instantition of objects.
var bubble = require('./bubble');
var mapHelpers = require('./map-helpers');
var canEvent = require('can-event-queue/map/map');
var addTypeEvents = require("can-event-queue/type/type");
var Construct = require('can-construct');
var ObservationRecorder = require('can-observation-recorder');
var ObserveReader = require('can-stache-key');
var canCompute = require('can-compute');
var singleReference = require('can-single-reference');

var namespace = require("can-namespace");
var dev = require("can-log/dev/dev");
var CID = require("can-cid");
var assign = require("can-assign");
var types = require("can-types");
var canReflect = require("can-reflect");
var canSymbol = require("can-symbol");
var CIDSet = require('can-cid/set/set');
var CIDMap = require("can-cid/map/map");
var canQueues = require("can-queues");

// properties that can't be observed on ... no matter what
var unobservable = {
	"constructor": true
};

var hasOwnProperty = ({}).hasOwnProperty;
var inSetupSymbol = canSymbol.for("can.initializing");

// Extend [can.Construct](../construct/construct.html) to make inheriting a `can.Map` easier.
var Map = Construct.extend(
	/**
	 * @static
	 */
	// ## Static Properties and Methods
	{
		// ### setup
		// Called when a Map constructor is defined/extended to
		// perform any initialization behavior for the new constructor
		// function.
		setup: function (baseMap) {

			Construct.setup.apply(this, arguments);

			// A cached list of computed properties on the prototype.
			this._computedPropertyNames = [];

			// Do not run if we are defining can.Map.
			if (Map) {
				addTypeEvents(this);
				this[canSymbol.for("can.defineInstanceKey")] = function(prop, definition){
					if(definition.value !== undefined) {
						this.defaults[prop] = definition.value;
					}
					if(definition.enumerable === false ) {
						this.enumerable[prop] = false;
					}
				};
				// Provide warnings if can.Map is used incorrectly.
				//!steal-remove-start
				if (process.env.NODE_ENV !== 'production') {
					if(this.prototype.define && !mapHelpers.define) {
						dev.warn("can/map/define is not included, yet there is a define property "+
						"used. You may want to add this plugin.");
					}
					if(this.define && !mapHelpers.define) {
						dev.warn("The define property should be on the map's prototype properties, "+
						"not the static properties. Also, can/map/define is not included.");
					}
				}
				//!steal-remove-end

				// Create a placeholder for default values.
				if (!this.defaults) {
					this.defaults = {};
				}
				if(!this.enumerable) {
					this.enumerable = {};
				}

				// Go through everything on the prototype.  If it's a primitive,
				// treat it as a default value.  If it's a compute, identify it so
				// it can be setup as a computed property.
				for (var prop in this.prototype) {
					if (
						prop !== "define" &&
						prop !== "constructor" &&
						(typeof this.prototype[prop] !== "function" ||
							this.prototype[prop].prototype instanceof Construct)
					) {
						this.defaults[prop] = this.prototype[prop];
					} else if (canReflect.isObservableLike(this.prototype[prop])) {
						this._computedPropertyNames.push(prop);
					}
				}

				// If define is a function, call it with this can.Map
				if(mapHelpers.define) {
					mapHelpers.define(this, baseMap.prototype.define);
				}
			}

			// If we inherit from can.Map, but not can.List, create a can.List that
			// creates instances of this Map type.
            // This is something List should weave in.
			/*if (can.List && !(this.prototype instanceof can.List)) {
				this.List = Map.List.extend({
					Map: this
				}, {});
			}*/

		},
		// ### shortName
		// Tells `can.Construct` to show instance as `Map` in the debugger.
		shortName: "Map",

		// ### _bubbleRule
		// Returns which events to setup bubbling on for a given bound event.
		// By default, only bubbles "change" events if someone listens to a
		// "change" event or a nested event like "foo.bar".
		_bubbleRule: function(eventName) {
			return (eventName === "change" || eventName.indexOf(".") >= 0 ) ?
				["change"] :
				[];
		},

		// ### bind,  unbind
		// Listen to events on the Map constructor.  These
		// are here mostly for can.Model.
		addEventListener: canEvent.addEventListener,
		removeEventListener: canEvent.removeEventListener,

		// ### keys
		// An observable way to get the keys from a map.
		keys: function (map) {
			return canReflect.getOwnEnumerableKeys(map);
		}
	},
	/**
	 * @prototype
	 */
	// ## Prototype Properties and Methods
	{
		// ### setup
		// Initializes the map instance's behavior.
		setup: function (obj) {

			if(canReflect.isObservableLike(obj) && typeof obj.serialize === "function"){
				obj = obj.serialize();
			}

			// Where we keep the values of the compute.
			this._data = Object.create(null);

			// The namespace this `object` uses to listen to events.
			CID(this, ".map");

			this._setupComputedProperties();
			var teardownMapping = obj && mapHelpers.addToMap(obj, this);

			var defaultValues = this._setupDefaults(obj);
			var data = assign(canReflect.assignDeep({}, defaultValues), obj);

			this.attr(data);

			if (teardownMapping) {
				teardownMapping();
			}
		},

		// ### _setupComputes
		// Sets up computed properties on a Map.
		// Stores information for each computed property on
		//  `this._computedAttrs` that looks like:
		//
		// ```
		// {
		//   // the number of bindings on this property
		//   count: 1,
		//   // a handler that forwards events on the compute
		//   // to the map instance
		//   handler: handler,
		//   compute: compute  // the compute
		// }
		// ```
		_setupComputedProperties: function () {
			this._computedAttrs = Object.create(null);

			var computes = this.constructor._computedPropertyNames;

			for (var i = 0, len = computes.length; i < len; i++) {
				var attrName = computes[i];
				mapHelpers.addComputedAttr(this, attrName, this[attrName]);
			}
		},

		// ### _setupDefaults
		// Returns the default values for the instance.
		_setupDefaults: function(){
			return this.constructor.defaults || {};
		},

		// ### attr
		// The primary get/set interface for can.Map.
		// Calls `_get`, `_set` or `_attrs` depending on
		// how it is called.
		attr: function (attr, val) {
			var type = typeof attr;
			if(attr === undefined) {
				return this._getAttrs();
			} else if (type !== "string" && type !== "number") {
				// Get or set multiple attributes.
				return this._setAttrs(attr, val);
			}
			else if (arguments.length === 1) {
				// Get a single attribute.
				return this._get(attr);
			} else {
				// Set an attribute.
				this._set(attr+"", val);
				return this;
			}
		},

		// ### _get
		// Handles reading nested properties like "foo.bar" by
		// getting the value of "foo" and recursively
		// calling `_get` for the value of "bar".
		// To read the actual values, `_get` calls
		// `___get`.
		_get: function (attr) {
			attr = attr + "";
			var dotIndex = attr.indexOf('.');

			if( dotIndex >= 0 ) {

				// Attempt to get the value anyway in case
				// somone wrote `new can.Map({"foo.bar": 1})`.
				var value = this.___get(attr);
				if (value !== undefined) {
					ObservationRecorder.add(this, attr);
					return value;
				}

				var first = attr.substr(0, dotIndex),
					second = attr.substr(dotIndex+1);

				var current = this.__get( first );

				return current && canReflect.getKeyValue(current, second);
			} else {
				return this.__get( attr );
			}
		},

		// ### __get
		// Signals `can.compute` that an observable
		// property is being read.
		__get: function(attr){
			if(!unobservable[attr] && !this._computedAttrs[attr]) {
				ObservationRecorder.add(this, attr);
			}
			return this.___get( attr );
		},

		// ### ___get
		// When called with an argument, returns the value of this property. If that
		// property is represented by a computed attribute, return the value of that compute.
		// If no argument is provided, return the raw data.
		___get: function (attr) {
			if (attr !== undefined) {
				var computedAttr = this._computedAttrs[attr];
				if (computedAttr) {
					// return computedAttr.compute();
					return canReflect.getValue(computedAttr.compute);
				} else {
					return hasOwnProperty.call(this._data, attr) ? this._data[attr] : undefined;
				}
			} else {
				return this._data;
			}
		},

		// ### _set
		// Handles setting nested properties by finding the
		// nested observable and recursively calling `_set` on it. Eventually,
		// it calls `__set` with the `__type` converted value to set
		// and the current value.  The current value is passed for two reasons:
		//  - so `__set` can trigger an event if the value has changed.
		//  - for advanced setting behavior that define.set can do.
		//
		// If the map is initializing, the current value does not need to be
		// read because no change events are dispatched anyway.
		_set: function (attr, value, keepKey) {
			attr = attr + "";

			var dotIndex = attr.indexOf('.'),
				current;

			if(dotIndex >= 0 && !keepKey){
				var first = attr.substr(0, dotIndex),
					second = attr.substr(dotIndex+1);

				current =  this[inSetupSymbol] ? undefined : this.___get( first );

				if( canReflect.isMapLike(current) ) {
					canReflect.setKeyValue(current, second, value);
				} else {
					current = this[inSetupSymbol] ? undefined : this.___get( attr );

					// //Convert if there is a converter.  Remove in 3.0.
					if (this.__convert) {
						value = this.__convert(attr, value);
					}

					this.__set(attr, this.__type(value, attr), current);
				}

			} else {
				current = this[inSetupSymbol] ? undefined : this.___get( attr );

				// //Convert if there is a converter.  Remove in 3.0.
				if (this.__convert) {
					value = this.__convert(attr, value);
				}

				this.__set(attr, this.__type(value, attr), current);
			}
		},

		// ## __type
		// Converts set values to another type.  By default,
		// this converts Objects to can.Maps and Arrays to
		// can.Lists.
		// This also makes it so if a plain JavaScript object
		// has already been converted to a list or map, that same
		// list or map instance is used.
		__type: function(value, prop){

			if (typeof value === "object" &&
				!canReflect.isObservableLike( value ) &&
				mapHelpers.canMakeObserve(value) &&
				!canReflect.isListLike(value)
			) {

				var cached = mapHelpers.getMapFromObject(value);
				if(cached) {
					return cached;
				}
				var MapConstructor = this.constructor.Map || Map;
				return new MapConstructor(value);

			}
			return value;
		},

		// ## __set
		// Handles firing events if the value has changed and
		// works with the `bubble` helpers to setup bubbling.
		// Calls `___set` to do the actual setting.
		__set: function (prop, value, current) {

			if ( value !== current || !Object.prototype.hasOwnProperty.call( this._data, prop ) ) {
				var computedAttr = this._computedAttrs[prop];

				// Dispatch an "add" event if adding a new property.
				var changeType = computedAttr || current !== undefined ||
					hasOwnProperty.call(this.___get(), prop) ? "set" : "add";

				// Set the value on `_data` and set up bubbling.
				this.___set(prop, typeof value === "object" ? bubble.set(this, prop, value, current) : value );

				// Computed properties change events are already forwarded except if
				// no one is listening to them.
				if(!computedAttr || !computedAttr.count) {
					this._triggerChange(prop, changeType, value, current);
				}

				// Stop bubbling old nested maps.
				if (typeof current === "object") {
					bubble.teardownFromParent(this, current);
				}
			}
		},

		// ### ___set
		// Directly saves the set value as a property on `_data`
		// or sets the computed attribute.
		___set: function (prop, val) {
			var computedAttr = this._computedAttrs[prop];
			if ( computedAttr ) {
				canReflect.setValue(computedAttr.compute, val);
			} else {
				this._data[prop] = val;
			}

			// Adds the property directly to the map instance. But first,
			// checks that it's not overwriting a method. This should be removed
			// in 3.0.
			if ( typeof this.constructor.prototype[prop] !== 'function' && !computedAttr ) {
				this[prop] = val;
			}
		},

		removeAttr: function (attr) {
			return this._remove(attr);
		},

		// ### _remove
		// Handles removing nested observes.
		_remove: function(attr){
			// If this is List.
			var parts = mapHelpers.attrParts(attr),
			// The actual property to remove.
				prop = parts.shift(),
			// The current value.
				current = this.___get(prop);

			// If we have more parts, call `removeAttr` on that part.
			if (parts.length && current) {
				return canReflect.deleteKeyValue(current, parts.join("."));
			} else {

				// If attr does not have a `.`
				if (typeof attr === 'string' && !!~attr.indexOf('.')) {
					prop = attr;
				}

				this.__remove(prop, current);
				return current;
			}
		},

		// ### __remove
		// Handles triggering an event if a property could be removed.
		__remove: function(prop, current){
			if (prop in this._data) {
				this.___remove(prop);
				// Let others now this property has been removed.
				this._triggerChange(prop, "remove", undefined, current);
			}
		},

		// ### ___remove
		// Deletes a property from `_data` and the map instance.
		___remove: function(prop){
			delete this._data[prop];
			if (!(prop in this.constructor.prototype)) {
				delete this[prop];
			}
		},

		// ### ___serialize
		// Serializes a property.  Uses map helpers to
		// recursively serialize nested observables.
		___serialize: function(name, val){
			if(this._legacyAttrBehavior) {
				return mapHelpers.getValue(this, name, val, "serialize");
			} else {
				return canReflect.serialize(val, CIDMap);
			}
		},

		// ### _getAttrs
		// Returns the values of all attributes as a plain JavaScript object.
		_getAttrs: function(){
			if(this._legacyAttrBehavior) {
				return mapHelpers.serialize(this, 'attr', {});
			} else {
				return canReflect.unwrap(this, CIDMap);
			}

		},
		// ### _setAttrs
		// Sets multiple properties on this object at once.
		// First, goes through all current properties and either merges
		// or removes old properties.
		// Then it goes through the remaining ones to be added and sets those properties.
		_setAttrs: function (props, remove) {
			if(this._legacyAttrBehavior) {
				return this.__setAttrs(props, remove);
			}
			if(remove === true || remove === "true") {
				this[canSymbol.for("can.updateDeep")](props);
			} else {
				this[canSymbol.for("can.assignDeep")](props);
			}
			return this;
		},
		__setAttrs: function (props, remove) {
			props = assign({}, props);
			var prop,
				self = this,
				newVal;

			// Batch all of the change events until we are done.
			canQueues.batch.start();
			// Merge current properties with the new ones.
			this._each(function (curVal, prop) {
				// You can not have a _cid property; abort.
				if (prop === "_cid") {
					return;
				}
				newVal = props[prop];

				// If we are merging, remove the property if it has no value.
				if (newVal === undefined) {
					if (remove) {
						self.removeAttr(prop);
					}
					return;
				}

				// Run converter if there is one. Remove in 3.0.
				if (self.__convert) {
					newVal = self.__convert( prop, newVal );
				}

				if ( canReflect.isObservableLike(curVal) && canReflect.isMapLike(curVal) && mapHelpers.canMakeObserve(newVal) ) {
					if(remove === true) {
						canReflect.updateDeep(curVal, newVal);
					} else {
						canReflect.assignDeep(curVal, newVal);
					}
					// Otherwise just set.
				} else if (curVal !== newVal) {
					self.__set(prop, self.__type(newVal, prop), curVal);
				}

				delete props[prop];
			});
			// Add remaining props.
			for (prop in props) {
				// Ignore _cid.
				if (prop !== "_cid") {
					newVal = props[prop];
					this._set(prop, newVal, true);
				}

			}
			canQueues.batch.stop();
			return this;
		},

		serialize: function () {
			return canReflect.serialize(this, CIDMap);
		},

		// ### _triggerChange
		// A helper function used to trigger events on this map.
		// If the map is bubbling, this will fire a change event.
		// Otherwise, it only fires a "named" event. Triggers a
		// "__keys" event if a property has been added or removed.
		_triggerChange: function (attr, how, newVal, oldVal, batchNum) {
			canQueues.batch.start();
			if(bubble.isBubbling(this, "change")) {
				canEvent.dispatch.call(this, {
					type: "change",
					target: this,
					batchNum: batchNum
				}, [attr, how, newVal, oldVal]);

			}

			canEvent.dispatch.call(this, {
				type: attr,
				target: this,
				batchNum: batchNum,
				patches: [{type: "set", key: attr, value: newVal}]
			}, [newVal, oldVal]);

			if(how === "remove" || how === "add") {
				canEvent.dispatch.call(this, {
					type: "__keys",
					target: this,
					batchNum: batchNum
				});
			}

			canQueues.batch.stop();
		},

		// ### compute
		// Creates a compute that represents a value on this map. If the property is a function
		// on the prototype, a "function" compute wil be created.
		// Otherwise, a compute will be created that reads the observable attributes
		compute: function (prop) {
			if (typeof this.constructor.prototype[prop] === "function") {
				return canCompute(this[prop], this);
			} else {
				var reads = ObserveReader.reads(prop);
				var last = reads.length - 1;

				return canCompute(function (newVal) {
					if (arguments.length) {
						ObserveReader.write(this, reads[last].key, newVal, {});
					} else {
						return ObserveReader.get(this, prop);
					}
				}, this);
			}
		},

		// ### each
		// loops through all the key-value pairs on this map.
		forEach: function (callback, context) {
				var key, item;
			var keys = canReflect.getOwnEnumerableKeys(this);
			for(var i =0, len = keys.length; i < len; i++) {
			    key = keys[i];
			    item = this.attr(key);
			    if (callback.call(context || item, item, key, this) === false) {
			        break;
			    }
			}
			return this;
		},

		// ### _each
		// Iterator that does not trigger live binding.
		_each: function (callback) {
			var data = this.___get();
			for (var prop in data) {
				if (hasOwnProperty.call(data, prop)) {
					callback(data[prop], prop);
				}
			}
		},

		dispatch: canEvent.dispatch
	});

// makes it so things can read this.
canEvent(Map.prototype);

// ### bind
// Listens to an event on a map.
// If the event is a  computed property,
// listen to the compute and forward its events
// to this map.
Map.prototype.addEventListener = function (eventName, handler) {

	var computedBinding = this._computedAttrs && this._computedAttrs[eventName];
	if (computedBinding && computedBinding.compute) {
		if (!computedBinding.count) {
			computedBinding.count = 1;
			canReflect.onValue(computedBinding.compute, computedBinding.handler, "notify");
		} else {
			computedBinding.count++;
		}

	}

	// Sets up bubbling if needed.
	bubble.bind(this, eventName);

	return canEvent.addEventListener.apply(this, arguments);
};

// ### unbind
// Stops listening to an event.
// If this is the last listener of a computed property,
// stop forwarding events of the computed property to this map.
Map.prototype.removeEventListener = function (eventName, handler) {
	var computedBinding = this._computedAttrs && this._computedAttrs[eventName];
	if (computedBinding) {
		if (computedBinding.count === 1) {
			computedBinding.count = 0;
			canReflect.offValue(computedBinding.compute, computedBinding.handler);
		} else {
			computedBinding.count--;
		}

	}

	// Teardown bubbling if needed.
	bubble.unbind(this, eventName);
	return canEvent.removeEventListener.apply(this, arguments);

};

// ### etc
// Setup on/off aliases
Map.prototype.on = Map.prototype.bind = Map.prototype.addEventListener;
Map.prototype.off = Map.prototype.unbind = Map.prototype.removeEventListener;
Map.on = Map.bind = Map.addEventListener;
Map.off = Map.unbind = Map.removeEventListener;

// - type -

canReflect.assignSymbols(Map.prototype,{
	// -type-
	"can.isMapLike": true,
	"can.isListLike":  false,
	"can.isValueLike": false,

	// -get/set-
	"can.getKeyValue": Map.prototype._get,
	"can.setKeyValue": Map.prototype._set,
	"can.deleteKeyValue": Map.prototype._remove,

	// -shape
	"can.getOwnEnumerableKeys": function(){
		if (!this[inSetupSymbol]) {
			ObservationRecorder.add(this, '__keys');
		}
		var enumerable = this.constructor.enumerable;
		if(enumerable) {
			return Object.keys(this._data).filter(function(key){
				return enumerable[key] !== false;
			},this);
		} else {
			return Object.keys(this._data);
		}
	},

	// -shape get/set-
	"can.assignDeep": function(source){
		canQueues.batch.start();
		// TODO: we should probably just throw an error instead of cleaning
		canReflect.assignDeepMap(this, mapHelpers.removeSpecialKeys(canReflect.assignMap({}, source)));
		canQueues.batch.stop();
	},
	"can.updateDeep": function(source){
		canQueues.batch.start();
		// TODO: we should probably just throw an error instead of cleaning
		canReflect.updateDeepMap(this, mapHelpers.removeSpecialKeys(canReflect.assignMap({}, source)));
		canQueues.batch.stop();
	},
	"can.unwrap": mapHelpers.reflectUnwrap,
	"can.serialize": mapHelpers.reflectSerialize,

	// observable
	"can.onKeyValue": function(key, handler, queue){
		var translationHandler = function(ev, newValue, oldValue){
			handler.call(this, newValue, oldValue);
		};
		singleReference.set(handler, this, translationHandler, key);

		this.addEventListener(key, translationHandler, queue);
	},
	"can.offKeyValue": function(key, handler, queue){
		this.removeEventListener(key, singleReference.getAndDelete(handler, this, key), queue );
	},
	"can.keyHasDependencies": function(key) {
		return !!(this._computedAttrs && this._computedAttrs[key] &&
			this._computedAttrs[key].compute);
	},
	"can.getKeyDependencies": function(key) {
		var ret;
		if(this._computedAttrs && this._computedAttrs[key] && this._computedAttrs[key].compute) {
			ret = {};
			ret.valueDependencies = new CIDSet();
			ret.valueDependencies.add(this._computedAttrs[key].compute);
		}
		return ret;
	}
});

if(!types.DefaultMap) {
	types.DefaultMap = Map;
}

module.exports = namespace.Map = Map;


//     uuid.js
//
//     Copyright (c) 2010-2012 Robert Kieffer
//     MIT License - http://opensource.org/licenses/mit-license.php

// Unique ID creation requires a high quality random # generator.  We feature
// detect to determine the best RNG source, normalizing to a function that
// returns 128-bits of randomness, since that's what's usually required
// var _rng = require('./rng');

import _rng from './browser-rng';

// Maps for number <-> hex string conversion
var _byteToHex = [];
var _hexToByte = {};
for (var i = 0; i < 256; i++) {
  _byteToHex[i] = (i + 0x100).toString(16).substr(1);
  _hexToByte[_byteToHex[i]] = i;
}

// **`parse()` - Parse a UUID into it's component bytes**
function parse(s, buf, offset) {
  var i = (buf && offset) || 0, ii = 0;

  buf = buf || [];
  s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {
    if (ii < 16) { // Don't overflow!
      buf[i + ii++] = _hexToByte[oct];
    }
  });

  // Zero out remaining bytes if string was short
  while (ii < 16) {
    buf[i + ii++] = 0;
  }

  return buf;
}

// **`unparse()` - Convert UUID byte array (ala parse()) into a string**
function unparse(buf, offset) {
  var i = offset || 0, bth = _byteToHex;
  return  bth[buf[i++]] + bth[buf[i++]] +
          bth[buf[i++]] + bth[buf[i++]] + '-' +
          bth[buf[i++]] + bth[buf[i++]] + '-' +
          bth[buf[i++]] + bth[buf[i++]] + '-' +
          bth[buf[i++]] + bth[buf[i++]] + '-' +
          bth[buf[i++]] + bth[buf[i++]] +
          bth[buf[i++]] + bth[buf[i++]] +
          bth[buf[i++]] + bth[buf[i++]];
}

// **`v1()` - Generate time-based UUID**
//
// Inspired by https://github.com/LiosK/UUID.js
// and http://docs.python.org/library/uuid.html

// random #'s we need to init node and clockseq
var _seedBytes = _rng();

// Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
var _nodeId = [
  _seedBytes[0] | 0x01,
  _seedBytes[1], _seedBytes[2], _seedBytes[3], _seedBytes[4], _seedBytes[5]
];

// Per 4.2.2, randomize (14 bit) clockseq
var _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff;

// Previous uuid creation time
var _lastMSecs = 0, _lastNSecs = 0;

// See https://github.com/broofa/node-uuid for API details
function v1(options, buf, offset) {
  var i = buf && offset || 0;
  var b = buf || [];

  options = options || {};

  var clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq;

  // UUID timestamps are 100 nano-second units since the Gregorian epoch,
  // (1582-10-15 00:00).  JSNumbers aren't precise enough for this, so
  // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs'
  // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.
  var msecs = options.msecs !== undefined ? options.msecs : new Date().getTime();

  // Per 4.2.1.2, use count of uuid's generated during the current clock
  // cycle to simulate higher resolution clock
  var nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1;

  // Time since last uuid creation (in msecs)
  var dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs)/10000;

  // Per 4.2.1.2, Bump clockseq on clock regression
  if (dt < 0 && options.clockseq === undefined) {
    clockseq = clockseq + 1 & 0x3fff;
  }

  // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
  // time interval
  if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) {
    nsecs = 0;
  }

  // Per 4.2.1.2 Throw error if too many uuids are requested
  if (nsecs >= 10000) {
    throw new Error('uuid.v1(): Can\'t create more than 10M uuids/sec');
  }

  _lastMSecs = msecs;
  _lastNSecs = nsecs;
  _clockseq = clockseq;

  // Per 4.1.4 - Convert from unix epoch to Gregorian epoch
  msecs += 12219292800000;

  // `time_low`
  var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
  b[i++] = tl >>> 24 & 0xff;
  b[i++] = tl >>> 16 & 0xff;
  b[i++] = tl >>> 8 & 0xff;
  b[i++] = tl & 0xff;

  // `time_mid`
  var tmh = (msecs / 0x100000000 * 10000) & 0xfffffff;
  b[i++] = tmh >>> 8 & 0xff;
  b[i++] = tmh & 0xff;

  // `time_high_and_version`
  b[i++] = tmh >>> 24 & 0xf | 0x10; // include version
  b[i++] = tmh >>> 16 & 0xff;

  // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
  b[i++] = clockseq >>> 8 | 0x80;

  // `clock_seq_low`
  b[i++] = clockseq & 0xff;

  // `node`
  var node = options.node || _nodeId;
  for (var n = 0; n < 6; n++) {
    b[i + n] = node[n];
  }

  return buf ? buf : unparse(b);
}

// **`v4()` - Generate random UUID**

// See https://github.com/broofa/node-uuid for API details
function v4(options, buf, offset) {
  // Deprecated - 'format' argument, as supported in v1.2
  var i = buf && offset || 0;

  if (typeof(options) === 'string') {
    buf = options === 'binary' ? new Array(16) : null;
    options = null;
  }
  options = options || {};

  var rnds = options.random || (options.rng || _rng)();

  // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
  rnds[6] = (rnds[6] & 0x0f) | 0x40;
  rnds[8] = (rnds[8] & 0x3f) | 0x80;

  // Copy bytes to buffer, if provided
  if (buf) {
    for (var ii = 0; ii < 16; ii++) {
      buf[i + ii] = rnds[ii];
    }
  }

  return buf || unparse(rnds);
}

export default {
  v1: v1,
  v4: v4,
  parse: parse,
  unparse: unparse,
};

export {v4, v1, parse, unparse};


'use strict';

const path = require('path');
const gulp = require('gulp');
const runSequence = require('run-sequence');
const GithubApi = require('github');
const inquirer = require('inquirer');
const $ = require('gulp-load-plugins')();

const pkg = require('./package.json');
const paths = {
  dist: './build/',
  css: 'src/css/*.css',
  zip: `${pkg.name}-v${pkg.version}.zip`
};
const banner = '/*!\n' +
  ' * <%= pkg.name %> - <%= pkg.description %>\n' +
  ' * v<%= pkg.version %> - <%= pkg.homepage %> - @license: <%= pkg.license %>\n' +
  ' */\n';

/**
  Build distribution version of library
*/
const dist_options = {minify: true, banner: true};
gulp.task('css', css(paths.css, paths.dist, dist_options));

/**
  Bump the bower version to match package.json
*/
gulp.task('version:bower', () => {
  return gulp.src('./bower.json')
    .pipe($.bump({version: pkg.version}))
    .pipe(gulp.dest('./'));
});

/**
  Prepare files for docs
*/
gulp.task('docs', () => {
  return gulp.src([`${paths.dist}*`, 'package.json', 'CHANGELOG.md'])
    .pipe($.copy('_docs/additional/'));
});

/**
  Create zip for github
*/
gulp.task('zip:github', () => {
  return gulp.src(`${paths.dist}*`, {base: 'dist'})
    .pipe($.zip(paths.zip))
    .pipe(gulp.dest(paths.dist));
});

/**
  Publish release to github
*/
gulp.task('publish:github', series('zip:github', (cb) => {
  const github = new GithubApi({
    version: '3.0.0',
    protocol: 'https'
  });
  const tag_name = `v${pkg.version}`;
  const owner = 'CSNW';
  const repo = 'd3.compose';
  const name = `${pkg.name} ${tag_name}`;

  inquirer.prompt([{
    type: 'input',
    name: 'token',
    message: 'Enter your GitHub token'
  }], (answers) => {
    const token = answers.token;
    if (!token)
      return cb(new Error('A GitHub token is required to publish'));

    github.authenticate({
      type: 'oauth',
      token
    });

    console.log(`Creating release: "${name}"`)

    github.releases.createRelease({
      owner,
      repo,
      tag_name,
      name
    }, (err, response) => {
      if (err) return cb(err);

      console.log(`Uploading zip: "${paths.zip}"`);

      github.releases.uploadAsset({
        owner,
        repo,
        id: response.id,
        name: paths.zip,
        filePath: path.join(__dirname, paths.dist, paths.zip)
      }, cb);
    })
  });
}));

/**
  Create css build function

  @method css
  @param {String|Array} files glob
  @param {String} output folder
  @param {Object} [options]
  @param {Boolean} [options.banner = false] Include banner with build
  @return {Function}
*/
function css(files, output, options) {
  options = Object.assign({
    banner: false
  }, options);

  return () => {
    var build = gulp.src(files)

    if (options.banner)
      build = build.pipe($.header(banner, {pkg}));

    build = build
      .pipe($.rename('d3.compose.css'))
      .pipe(gulp.dest(output));

    return build;
  };
}

/**
  Approximate gulp 4.0 series

  @param {...String} ...tasks
  @param {Function} [fn] Function to call at end of series
  @return {Function}
*/
function series() {
  const tasks = Array.prototype.slice.call(arguments);
  var fn = cb => cb();

  if (typeof tasks[tasks.length - 1] === 'function')
    fn = tasks.pop();

  return (cb) => {
    const tasks_with_cb = tasks.concat([(err) => {
      if (err) return cb(err);
      fn(cb);
    }]);

    runSequence.apply(this, tasks_with_cb);
  }
}


import {
  assign,
  isObject,
  isUndefined,
  objectEach
} from './utils';
import layered from './layouts/layered';

export function prepareDescription(description) {
  if (!description) {
    return;
  }

  if (description._layered) {
    var allItems = description.items.reduce(function(memo, item, index) {
      if (Array.isArray(item)) {
        var asSurround = toSurround(item);
        var items = asSurround.ordered.map(function(_id) { return asSurround.byId[_id]; });
        return memo.concat(items);
      } else {
        var container = {_id: '_container'};
        var layout = {
          top: 0,
          right: constraint.eq(container, 'right'),
          bottom: constraint.eq(container, 'bottom'),
          left: 0,
          width: constraint.flex(),
          height: constraint.flex()
        };
        item = assign({_id: 'item-0-' + index}, item);
        item.props = assign(layout, item.props);

        return memo.concat([item]);
      }
    }, []);

    var byId = {};
    var ordered = [];
    allItems.forEach(function(item) {
      byId[item._id] = item;
      ordered.push(item._id);
    });

    return {byId: byId, ordered: ordered};
  } else {
    return toSurround(description);
  }
}

var layoutProps = ['top', 'right', 'bottom', 'left', 'width', 'height'];
export function calculateLayout(layout, dimensions) {
  // 1. Create references for each layout item
  var byId = {};
  var ordered = [];
  layout.forEach(function(item) {
    byId[item._id] = {
      margin: getDefaultMargin(item.margin),
      zIndex: 0
    };

    layoutProps.forEach(function(key) {
      byId[item._id][key] = layoutReference(item[key]);
    });

    ordered.push(item._id);
  });

  var container = {
    top: layoutReference(0),
    right: layoutReference(dimensions.width),
    bottom: layoutReference(dimensions.height),
    left: layoutReference(0),
    width: layoutReference(dimensions.width),
    height: layoutReference(dimensions.height)
  };

  // 2. Replace constraints with references
  objectEach(byId, function(item) {
    layoutProps.forEach(function(key) {
      var constraint = item[key];
      if (constraint._id) {
        var reference = constraint._id == '_container' ? container : byId[constraint._id];
        item[key] = reference[constraint.key];
      }
    });
  });

  // 3. Calculate values
  var iteration = 0;
  var solved = false;
  var maxIterations = 100;
  while (!solved && iteration < maxIterations) {
    solved = true;

    objectEach(byId, function(item) {
      var has = hasResults();

      // Top
      if (!has.top && has.bottom && has.height) {
        item.top.result = item.bottom.result - (item.margin.top + item.height.result + item.margin.bottom);
      }

      // Right
      if (!has.right && has.left && has.width) {
        item.right.result = item.left.result + (item.margin.left + item.width.result + item.margin.right);
      }

      // Bottom
      if (!has.bottom && has.top && has.height) {
        item.bottom.result = item.top.result + (item.margin.top + item.height.result + item.margin.bottom);
      }

      // Left
      if (!has.left && has.right && has.width) {
        item.left.result = item.right.result - (item.margin.left + item.width.result + item.margin.right);
      }

      // Width
      if (!has.width && has.right && has.left) {
        item.width.result = item.right.result - item.left.result - item.margin.left - item.margin.right;
      }

      // Height
      if (!has.height && has.bottom && has.top) {
        item.height.result = item.bottom.result - item.top.result - item.margin.top - item.margin.bottom;
      }

      has = hasResults();
      if (!has.top || !has.right || !has.bottom || !has.left || !has.width || !has.height) {
        solved = false;
      }

      function hasResults() {
        return {
          top: !isUndefined(item.top.result),
          right: !isUndefined(item.right.result),
          bottom: !isUndefined(item.bottom.result),
          left: !isUndefined(item.left.result),
          width: !isUndefined(item.width.result),
          height: !isUndefined(item.height.result)
        };
      }
    });

    iteration++;
  }

  if (iteration === maxIterations) {
    throw new Error('Failed to calculate layout based on the given constraints');
  }

  // 4. Extract results
  var results = {};
  objectEach(byId, function(item, _id) {
    results[_id] = {
      x: item.left.result,
      y: item.top.result,
      width: item.width.result,
      height: item.height.result,
      margin: item.margin,
      zIndex: item.zIndex
    };
  });

  return {
    byId: results,
    ordered: ordered
  };
}

// Placeholder until formal constraints
export var constraint = {
  eq: function eq(item, key) {
    if (item) {
      return {_id: item._id, type: 'equal', key: key};
    }
  },
  flex: function flew(grow) {
    grow = grow || 1;
    return {type: 'flex', grow: grow};
  }
}

export function extractLayout(props) {
  return {
    width: props.width,
    height: props.height,
    top: props.top,
    right: props.right,
    bottom: props.bottom,
    left: props.left,
    zIndex: props.zIndex,
    margin: props.margin
  };
}

function layoutReference(value) {
  if (isObject(value)) {
    return value;
  } else if (!isUndefined(value)) {
    return {result: value};
  } else {
    return {};
  }
}

var defaultMargin = {top: 0, right: 0, bottom: 0, left: 0};
function getDefaultMargin(margin) {
  if (!isUndefined(margin) && !isObject(margin)) {
    margin = {top: margin, right: margin, bottom: margin, left: margin};
  }

  return assign({}, defaultMargin, margin);
}

// Placeholder until formal constraints/layouts are added
function toSurround(description) {
  if (!Array.isArray(description)) {
    description = [layered(description)];
  }

  var positions = extractSurroundPositions(description);
  var top = positions.top;
  var right = positions.right;
  var bottom = positions.bottom;
  var left = positions.left;
  var middle = positions.middle;

  var container = {_id: '_container'};
  var topEdge = top[top.length - 1] && constraint.eq(top[top.length - 1], 'bottom') || 0;
  var rightEdge = right[0] && constraint.eq(right[0], 'left') || constraint.eq(container, 'right');
  var bottomEdge = bottom[0] && constraint.eq(bottom[0], 'top') || constraint.eq(container, 'bottom');
  var leftEdge = left[left.length - 1] && constraint.eq(left[left.length - 1], 'right') || 0;

  top = top.map(function(item, i, items) {
    var layout = {
      _position: 'top',
      top: items[i - 1] && constraint.eq(items[i - 1], 'bottom') || 0,
      left: leftEdge,
      right: rightEdge,
      width: constraint.flex()
    };

    item = assign({}, item);
    item.props = assign(layout, item.props);
    return item;
  });
  right = right.map(function(item, i, items) {
    var layout = {
      _position: 'right',
      right: items[i + 1] && constraint.eq(items[i + 1], 'left') || constraint.eq(container, 'right'),
      top: topEdge,
      bottom: bottomEdge,
      height: constraint.flex()
    };

    item = assign({}, item);
    item.props = assign(layout, item.props);
    return item;
  });
  bottom = bottom.map(function(item, i, items) {
    var layout = {
      _position: 'bottom',
      bottom: items[i + 1] && constraint.eq(items[i + 1], 'top') || constraint.eq(container, 'bottom'),
      left: leftEdge,
      right: rightEdge,
      width: constraint.flex()
    };

    item = assign({}, item);
    item.props = assign(layout, item.props);
    return item;
  });
  left = left.map(function(item, i, items) {
    var layout = {
      _position: 'left',
      left: items[i - 1] && constraint.eq(items[i - 1], 'right') || 0,
      top: topEdge,
      bottom: bottomEdge,
      height: constraint.flex()
    };

    item = assign({}, item);
    item.props = assign(layout, item.props);
    return item;
  });
  middle = middle.map(function(item) {
    var layout = {
      _position: 'middle',
      top: topEdge,
      right: rightEdge,
      bottom: bottomEdge,
      left: leftEdge,
      width: constraint.flex(),
      height: constraint.flex()
    };

    item = assign({}, item);
    item.props = assign(layout, item.props);
    return item;
  });

  var allItems = top.concat(left).concat(middle).concat(right).concat(bottom);
  var byId = {};
  var ordered = [];
  allItems.forEach(function(item) {
    byId[item._id] = item;
    ordered.push(item._id);
  });

  return {byId: byId, ordered: ordered};
}

function extractSurroundPositions(description) {
  var top = [];
  var bottom = [];
  var right = [];
  var left = [];
  var middle = [];
  var foundRow = false;
  var foundMiddle = false;

  description.forEach(function(row, rowIndex) {
    if (!Array.isArray(row)) {
      row = [row];
    } else if (row.length > 1) {
      foundRow = true;
    }

    row.forEach(function(item, itemIndex) {
      if (!item) {
        return;
      }

      if (item._layered) {
        foundMiddle = foundRow = true;

        item.items.forEach(function(chart, chartIndex) {
          middle.push(assign({}, chart, {_id: getId(rowIndex, itemIndex, chartIndex)}));
        });

        return;
      }

      item = assign({}, item, {_id: getId(rowIndex, itemIndex)});

      if (row.length > 1 && !foundMiddle) {
        left.push(item);
      } else if (row.length > 1 && foundMiddle) {
        right.push(item);
      } else if (!foundRow) {
        top.push(item);
      } else {
        bottom.push(item);
      }
    });
  });

  return {
    top: top,
    right: right,
    bottom: bottom,
    left: left,
    middle: middle
  };
}

function getId(rowIndex, colIndex, layeredIndex) {
  var id = 'item-' + (rowIndex + 1) + '-' + (colIndex + 1);
  if (!isUndefined(layeredIndex)) {
    id += '-' + (layeredIndex + 1);
  }
  return id;
}


// This sync function for Couchbase Sync Gateway was generated by synctos: https://github.com/Kashoo/synctos
function synctos(doc, oldDoc) {
  // Whether the given value is either null or undefined
  function isValueNullOrUndefined(value) {
    return value === void 0 || value === null;
  }

  // Whether the given document is missing/nonexistant (i.e. null or undefined) or deleted (its "_deleted" property is true)
  function isDocumentMissingOrDeleted(candidate) {
    return isValueNullOrUndefined(candidate) || candidate._deleted;
  }

  // A property validator that is suitable for use on type identifier properties. Ensures the value is a string, is neither null nor
  // undefined, is not an empty string and cannot be modified.
  var typeIdValidator = {
    type: 'string',
    required: true,
    mustNotBeEmpty: true,
    immutable: true
  };

  // A type filter that matches on the document's type property
  function simpleTypeFilter(doc, oldDoc, candidateDocType) {
    if (oldDoc) {
      if (doc._deleted) {
        return oldDoc.type === candidateDocType;
      } else {
        return doc.type === oldDoc.type && oldDoc.type === candidateDocType;
      }
    } else {
      return doc.type === candidateDocType;
    }
  }

  // Retrieves the old doc's effective value. If it is null, undefined or its "_deleted" property is true, returns null. Otherwise, returns
  // the value of the "oldDoc" parameter.
  function resolveOldDoc() {
    return !isDocumentMissingOrDeleted(oldDoc) ? oldDoc : null;
  }

  // Add the specified padding to the right of the given string value until its length matches the desired length
  function padRight(value, desiredLength, padding) {
    while (value.length < desiredLength) {
      value += padding;
    }

    return value;
  }

  // Determine if a given value is an integer. Exists because Number.isInteger is not supported by Sync Gateway's JavaScript engine.
  function isValueAnInteger(value) {
    return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;
  }

  // Retrieves the effective value of a top-level document constraint (e.g. "channels", "documentIdRegexPattern", "accessAssignments")
  function resolveDocumentConstraint(constraintDefinition) {
    if (typeof constraintDefinition === 'function') {
      return constraintDefinition(doc, resolveOldDoc());
    } else {
      return constraintDefinition;
    }
  }

  // Converts a given value to a JSON string. Exists because JSON.stringify is not supported by all versions of Sync
  // Gateway's JavaScript engine.
  var jsonStringify =
    (typeof JSON !== 'undefined' && JSON.stringify) ? JSON.stringify : importSyncFunctionFragment('json-stringify-module.js');

  var utils = {
    isDocumentMissingOrDeleted: isDocumentMissingOrDeleted,
    isValueAnInteger: isValueAnInteger,
    isValueNullOrUndefined: isValueNullOrUndefined,
    jsonStringify: jsonStringify,
    padRight: padRight,
    resolveOldDoc: resolveOldDoc,
    resolveDocumentConstraint: resolveDocumentConstraint
  };

  // The document authorization module is responsible for verifying the user's permissions (e.g. roles, channels)
  var authorizationModule = importSyncFunctionFragment('./authorization-module.js')(utils);

  // The document validation module is responsible for verifying the document's contents
  var validationModule = importSyncFunctionFragment('./validation-module.js')(utils, simpleTypeFilter, typeIdValidator);

  // The access assignment module is responsible for dynamically assigning channels and roles to users
  var accessAssignmentModule = importSyncFunctionFragment('./access-assignment-module.js')(utils);

  // The expiry module is responsible for controlling when the document will expire and be purged from the DB
  var expiryModule = importSyncFunctionFragment('./expiry-module.js')(utils);

  var rawDocDefinitions = $DOCUMENT_DEFINITIONS_PLACEHOLDER$;

  var docDefinitions;
  if (typeof rawDocDefinitions === 'function') {
    docDefinitions = rawDocDefinitions();
  } else {
    docDefinitions = rawDocDefinitions;
  }

  function getDocumentType() {
    var effectiveOldDoc = resolveOldDoc();

    for (var docType in docDefinitions) {
      var docDefn = docDefinitions[docType];
      if (docDefn.typeFilter(doc, effectiveOldDoc, docType)) {
        return docType;
      }
    }

    // The document type does not exist
    return null;
  }

  // Now put the pieces together
  var theDocType = getDocumentType();

  if (isValueNullOrUndefined(theDocType)) {
    if (doc._deleted) {
      // Attempting to delete a document whose type is unknown. This may occur when bucket access/sharing
      // (https://developer.couchbase.com/documentation/mobile/current/guides/sync-gateway/shared-bucket-access.html)
      // is enabled and the document was deleted via the Couchbase SDK or if the document belongs to a type that existed
      // in a previous version of the document definitions but has since been removed. Verify that the user has
      // administrator access and then simply assign the public channel
      // (https://developer.couchbase.com/documentation/mobile/current/guides/sync-gateway/channels/index.html#special-channels)
      // to the document so that other users will get a 404 Not Found if they attempt to fetch (i.e. "view") the deleted
      // document rather than a 403 Forbidden.
      requireAccess([ ]); // This test can only be satisfied via the admin API
      channel('!');

      return;
    } else {
      var errorMessage = 'Unknown document type';
      var error = new Error(errorMessage);
      error.forbidden = errorMessage;

      throw error;
    }
  }

  var theDocDefinition = docDefinitions[theDocType];

  var customActionMetadata = {
    documentTypeId: theDocType,
    documentDefinition: theDocDefinition
  };

  if (theDocDefinition.customActions && typeof theDocDefinition.customActions.onTypeIdentificationSucceeded === 'function') {
    theDocDefinition.customActions.onTypeIdentificationSucceeded(doc, oldDoc, customActionMetadata);
  }

  customActionMetadata.authorization = authorizationModule.authorize(doc, oldDoc, theDocDefinition);

  if (theDocDefinition.customActions && typeof theDocDefinition.customActions.onAuthorizationSucceeded === 'function') {
    theDocDefinition.customActions.onAuthorizationSucceeded(doc, oldDoc, customActionMetadata);
  }

  validationModule.validateDoc(doc, oldDoc, theDocDefinition, theDocType);

  if (theDocDefinition.customActions && typeof theDocDefinition.customActions.onValidationSucceeded === 'function') {
    theDocDefinition.customActions.onValidationSucceeded(doc, oldDoc, customActionMetadata);
  }

  if (theDocDefinition.accessAssignments && !doc._deleted) {
    var accessAssignments = accessAssignmentModule.assignUserAccess(doc, oldDoc, theDocDefinition);
    if (accessAssignments.length > 0) {
      customActionMetadata.accessAssignments = accessAssignments;

      if (theDocDefinition.customActions &&
          typeof theDocDefinition.customActions.onAccessAssignmentsSucceeded === 'function') {
        theDocDefinition.customActions.onAccessAssignmentsSucceeded(doc, oldDoc, customActionMetadata);
      }
    }
  }

  if (!isValueNullOrUndefined(theDocDefinition.expiry) && !doc._deleted) {
    customActionMetadata.expiryDate = expiryModule.setDocExpiry(doc, oldDoc, theDocDefinition.expiry);

    if (theDocDefinition.customActions &&
        typeof theDocDefinition.customActions.onExpiryAssignmentSucceeded === 'function') {
      theDocDefinition.customActions.onExpiryAssignmentSucceeded(doc, oldDoc, customActionMetadata);
    }
  }

  // Getting here means the document revision is authorized and valid, and the appropriate channel(s) should now be assigned
  var allDocChannels = authorizationModule.getAllDocChannels(theDocDefinition);
  channel(allDocChannels);
  customActionMetadata.documentChannels = allDocChannels;

  if (theDocDefinition.customActions && typeof theDocDefinition.customActions.onDocumentChannelAssignmentSucceeded === 'function') {
    theDocDefinition.customActions.onDocumentChannelAssignmentSucceeded(doc, oldDoc, customActionMetadata);
  }
}