/**
 * @class Ext.ComponentQuery
 * @extends Object
 * @singleton
 *
 * Provides searching of Components within {@link Ext.ComponentManager} (globally) or a specific
 * {@link Ext.Container} on the document with a similar syntax to a CSS selector.
 *
 * Components can be retrieved by using their {@link Ext.Component xtype} with an optional '.' prefix
 *
 * - `component` or `.component`
 * - `gridpanel` or `.gridpanel`
 *
 * An itemId or id must be prefixed with a #
 *
 * - `#myContainer`
 *
 * Attributes must be wrapped in brackets
 *
 * - `component[autoScroll]`
 * - `panel[title="Test"]`
 *
 * Attributes can use the '=' or '~=' operators to do the pattern matching.
 *
 * The '=' operator will return the results that exactly match:
 *
 *     Ext.Component.query('panel[cls=my-cls]')
 *
 * Will match the following Component:
 *
 *     Ext.create('Ext.Panel', {
 *         cls : 'my-cls'
 *     });
 *
 * The '~=' operator will return results that exactly matches one of the whitespace-separated values:
 *
 *     Ext.Component.query('panel[cls~=my-cls]')
 *
 * Will match the follow Component:
 *
 *     Ext.create('My.Panel', {
 *         cls : 'foo-cls my-cls bar-cls'
 *     });
 *
 * This is because it exactly matched the 'my-cls' within the cls config.
 *
 * Member expressions from candidate Components may be tested. If the expression returns a *truthy* value,
 * the candidate Component will be included in the query:
 *
 *     var disabledFields = myFormPanel.query("{isDisabled()}");
 *
 * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
 *
 *     // Function receives array and returns a filtered array.
 *     Ext.ComponentQuery.pseudos.invalid = function(items) {
 *         var i = 0, l = items.length, c, result = [];
 *         for (; i < l; i++) {
 *             if (!(c = items[i]).isValid()) {
 *                 result.push(c);
 *             }
 *         }
 *         return result;
 *     };
 *
 *     var invalidFields = myFormPanel.query('field:invalid');
 *     if (invalidFields.length) {
 *         invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
 *         for (var i = 0, l = invalidFields.length; i < l; i++) {
 *             invalidFields[i].getEl().frame("red");
 *         }
 *     }
 *
 * Default pseudos include:
 *
 * - not
 *
 * Queries return an array of components.
 * Here are some example queries.
 *
 *     // retrieve all Ext.Panels in the document by xtype
 *     var panelsArray = Ext.ComponentQuery.query('panel');
 *
 *     // retrieve all Ext.Panels within the container with an id myCt
 *     var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
 *
 *     // retrieve all direct children which are Ext.Panels within myCt
 *     var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
 *
 *     // retrieve all grids and trees
 *     var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
 *
 * For easy access to queries based from a particular Container see the {@link Ext.Container#query},
 * {@link Ext.Container#down} and {@link Ext.Container#child} methods. Also see
 * {@link Ext.Component#up}.
 */
Ext.define('Ext.ComponentQuery', {
    singleton: true,
    uses: ['Ext.ComponentManager']
}, function() {

    var cq = this,

        // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
        // as a member on each item in the passed array.
        filterFnPattern = [
            'var r = [],',
                'i = 0,',
                'it = items,',
                'l = it.length,',
                'c;',
            'for (; i < l; i++) {',
                'c = it[i];',
                'if (c.{0}) {',
                   'r.push(c);',
                '}',
            '}',
            'return r;'
        ].join(''),

        filterItems = function(items, operation) {
            // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
            // The operation's method loops over each item in the candidate array and
            // returns an array of items which match its criteria
            return operation.method.apply(this, [ items ].concat(operation.args));
        },

        getItems = function(items, mode) {
            var result = [],
                i = 0,
                length = items.length,
                candidate,
                deep = mode !== '>';

            for (; i < length; i++) {
                candidate = items[i];
                if (candidate.getRefItems) {
                    result = result.concat(candidate.getRefItems(deep));
                }
            }
            return result;
        },

        getAncestors = function(items) {
            var result = [],
                i = 0,
                length = items.length,
                candidate;
            for (; i < length; i++) {
                candidate = items[i];
                while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
                    result.push(candidate);
                }
            }
            return result;
        },

        // Filters the passed candidate array and returns only items which match the passed xtype
        filterByXType = function(items, xtype, shallow) {
            if (xtype === '*') {
                return items.slice();
            }
            else {
                var result = [],
                    i = 0,
                    length = items.length,
                    candidate;
                for (; i < length; i++) {
                    candidate = items[i];
                    if (candidate.isXType(xtype, shallow)) {
                        result.push(candidate);
                    }
                }
                return result;
            }
        },

        // Filters the passed candidate array and returns only items which have the passed className
        filterByClassName = function(items, className) {
            var EA = Ext.Array,
                result = [],
                i = 0,
                length = items.length,
                candidate;
            for (; i < length; i++) {
                candidate = items[i];
                if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
                    result.push(candidate);
                }
            }
            return result;
        },

        // Filters the passed candidate array and returns only items which have the specified property match
        filterByAttribute = function(items, property, operator, value) {
            var result = [],
                i = 0,
                length = items.length,
                candidate, getter, getValue;
            for (; i < length; i++) {
                candidate = items[i];
                getter = Ext.Class.getConfigNameMap(property).get;
                if (operator === '~=') {
                    getValue = null;

                    if (candidate[getter]) {
                        getValue = candidate[getter]();
                    } else if (candidate.config && candidate.config[property]) {
                        getValue = String(candidate.config[property]);
                    } else if (candidate[property]) {
                        getValue = String(candidate[property]);
                    }

                    if (getValue) {
                        //normalize to an array
                        if (!Ext.isArray(getValue)) {
                            getValue = getValue.split(' ');
                        }

                        var v = 0,
                            vLen = getValue.length,
                            val;

                        for (; v < vLen; v++) {
                            /**
                             * getValue[v] could still be whitespaced-separated, this normalizes it. This is an example:
                             *
                             * {
                             *     html : 'Imprint',
                             *     cls  : 'overlay-footer-item overlay-footer-imprint'
                             * }
                             */
                            val = String(getValue[v]).split(' ');

                            if (Ext.Array.indexOf(val, value) !== -1) {
                                result.push(candidate);
                            }
                        }
                    }
                } else if (candidate[getter]) {
                    getValue = candidate[getter]();
                    if (!value ? !!getValue : (String(getValue) === value)) {
                        result.push(candidate);
                    }
                }
                else if (candidate.config && candidate.config[property]) {
                    if (!value ? !!candidate.config[property] : (String(candidate.config[property]) === value)) {
                        result.push(candidate);
                    }
                }
                else if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
                    result.push(candidate);
                }
            }
            return result;
        },

        // Filters the passed candidate array and returns only items which have the specified itemId or id
        filterById = function(items, id) {
            var result = [],
                i = 0,
                length = items.length,
                candidate;
            for (; i < length; i++) {
                candidate = items[i];
                if (candidate.getId() === id || candidate.getItemId() === id) {
                    result.push(candidate);
                }
            }
            return result;
        },

        // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
        filterByPseudo = function(items, name, value) {
            return cq.pseudos[name](items, value);
        },

        // Determines leading mode
        // > for direct child, and ^ to switch to ownerCt axis
        modeRe = /^(\s?([>\^])\s?|\s|$)/,

        // Matches a token with possibly (true|false) appended for the "shallow" parameter
        tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,

        matchers = [{
            // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
            re: /^\.([\w\-]+)(?:\((true|false)\))?/,
            method: filterByXType
        },{
            // checks for [attribute=value]
            re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
            method: filterByAttribute
        }, {
            // checks for #cmpItemId
            re: /^#([\w\-]+)/,
            method: filterById
        }, {
            // checks for :<pseudo_class>(<selector>)
            re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
            method: filterByPseudo
        }, {
            // checks for {<member_expression>}
            re: /^(?:\{([^\}]+)\})/,
            method: filterFnPattern
        }];

    cq.Query = Ext.extend(Object, {
        constructor: function(cfg) {
            cfg = cfg || {};
            Ext.apply(this, cfg);
        },

        /**
         * @private
         * Executes this Query upon the selected root.
         * The root provides the initial source of candidate Component matches which are progressively
         * filtered by iterating through this Query's operations cache.
         * If no root is provided, all registered Components are searched via the ComponentManager.
         * root may be a Container who's descendant Components are filtered
         * root may be a Component with an implementation of getRefItems which provides some nested Components such as the
         * docked items within a Panel.
         * root may be an array of candidate Components to filter using this Query.
         */
        execute : function(root) {
            var operations = this.operations,
                i = 0,
                length = operations.length,
                operation,
                workingItems;

            // no root, use all Components in the document
            if (!root) {
                workingItems = Ext.ComponentManager.all.getArray();
            }
            // Root is a candidate Array
            else if (Ext.isArray(root)) {
                workingItems = root;
            }

            // We are going to loop over our operations and take care of them
            // one by one.
            for (; i < length; i++) {
                operation = operations[i];

                // The mode operation requires some custom handling.
                // All other operations essentially filter down our current
                // working items, while mode replaces our current working
                // items by getting children from each one of our current
                // working items. The type of mode determines the type of
                // children we get. (e.g. > only gets direct children)
                if (operation.mode === '^') {
                    workingItems = getAncestors(workingItems || [root]);
                }
                else if (operation.mode) {
                    workingItems = getItems(workingItems || [root], operation.mode);
                }
                else {
                    workingItems = filterItems(workingItems || getItems([root]), operation);
                }

                // If this is the last operation, it means our current working
                // items are the final matched items. Thus return them!
                if (i === length -1) {
                    return workingItems;
                }
            }
            return [];
        },

        is: function(component) {
            var operations = this.operations,
                components = Ext.isArray(component) ? component : [component],
                originalLength = components.length,
                lastOperation = operations[operations.length-1],
                ln, i;

            components = filterItems(components, lastOperation);
            if (components.length === originalLength) {
                if (operations.length > 1) {
                    for (i = 0, ln = components.length; i < ln; i++) {
                        if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
                            return false;
                        }
                    }
                }
                return true;
            }
            return false;
        }
    });

    Ext.apply(this, {

        // private cache of selectors and matching ComponentQuery.Query objects
        cache: {},

        // private cache of pseudo class filter functions
        pseudos: {
            not: function(components, selector){
                var CQ = Ext.ComponentQuery,
                    i = 0,
                    length = components.length,
                    results = [],
                    index = -1,
                    component;

                for(; i < length; ++i) {
                    component = components[i];
                    if (!CQ.is(component, selector)) {
                        results[++index] = component;
                    }
                }
                return results;
            }
        },

        /**
         * Returns an array of matched Components from within the passed root object.
         *
         * This method filters returned Components in a similar way to how CSS selector based DOM
         * queries work using a textual selector string.
         *
         * See class summary for details.
         *
         * @param {String} selector The selector string to filter returned Components
         * @param {Ext.Container} root The Container within which to perform the query.
         * If omitted, all Components within the document are included in the search.
         *
         * This parameter may also be an array of Components to filter according to the selector.
         * @return {Ext.Component[]} The matched Components.
         *
         * @member Ext.ComponentQuery
         */
        query: function(selector, root) {
            var selectors = selector.split(','),
                length = selectors.length,
                i = 0,
                results = [],
                noDupResults = [],
                dupMatcher = {},
                query, resultsLn, cmp;

            for (; i < length; i++) {
                selector = Ext.String.trim(selectors[i]);
                query = this.parse(selector);
//                query = this.cache[selector];
//                if (!query) {
//                    this.cache[selector] = query = this.parse(selector);
//                }
                results = results.concat(query.execute(root));
            }

            // multiple selectors, potential to find duplicates
            // lets filter them out.
            if (length > 1) {
                resultsLn = results.length;
                for (i = 0; i < resultsLn; i++) {
                    cmp = results[i];
                    if (!dupMatcher[cmp.id]) {
                        noDupResults.push(cmp);
                        dupMatcher[cmp.id] = true;
                    }
                }
                results = noDupResults;
            }
            return results;
        },

        /**
         * Tests whether the passed Component matches the selector string.
         * @param {Ext.Component} component The Component to test.
         * @param {String} selector The selector string to test against.
         * @return {Boolean} `true` if the Component matches the selector.
         * @member Ext.ComponentQuery
         */
        is: function(component, selector) {
            if (!selector) {
                return true;
            }
            var query = this.cache[selector];
            if (!query) {
                this.cache[selector] = query = this.parse(selector);
            }
            return query.is(component);
        },

        parse: function(selector) {
            var operations = [],
                length = matchers.length,
                lastSelector,
                tokenMatch,
                matchedChar,
                modeMatch,
                selectorMatch,
                i, matcher, method;

            // We are going to parse the beginning of the selector over and
            // over again, slicing off the selector any portions we converted into an
            // operation, until it is an empty string.
            while (selector && lastSelector !== selector) {
                lastSelector = selector;

                // First we check if we are dealing with a token like #, * or an xtype
                tokenMatch = selector.match(tokenRe);

                if (tokenMatch) {
                    matchedChar = tokenMatch[1];

                    // If the token is prefixed with a # we push a filterById operation to our stack
                    if (matchedChar === '#') {
                        operations.push({
                            method: filterById,
                            args: [Ext.String.trim(tokenMatch[2])]
                        });
                    }
                    // If the token is prefixed with a . we push a filterByClassName operation to our stack
                    // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
                    else if (matchedChar === '.') {
                        operations.push({
                            method: filterByClassName,
                            args: [Ext.String.trim(tokenMatch[2])]
                        });
                    }
                    // If the token is a * or an xtype string, we push a filterByXType
                    // operation to the stack.
                    else {
                        operations.push({
                            method: filterByXType,
                            args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
                        });
                    }

                    // Now we slice of the part we just converted into an operation
                    selector = selector.replace(tokenMatch[0], '');
                }

                // If the next part of the query is not a space or > or ^, it means we
                // are going to check for more things that our current selection
                // has to comply to.
                while (!(modeMatch = selector.match(modeRe))) {
                    // Lets loop over each type of matcher and execute it
                    // on our current selector.
                    for (i = 0; selector && i < length; i++) {
                        matcher = matchers[i];
                        selectorMatch = selector.match(matcher.re);
                        method = matcher.method;

                        // If we have a match, add an operation with the method
                        // associated with this matcher, and pass the regular
                        // expression matches are arguments to the operation.
                        if (selectorMatch) {
                            operations.push({
                                method: Ext.isString(matcher.method)
                                    // Turn a string method into a function by formatting the string with our selector matche expression
                                    // A new method is created for different match expressions, eg {id=='textfield-1024'}
                                    // Every expression may be different in different selectors.
                                    ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
                                    : matcher.method,
                                args: selectorMatch.slice(1)
                            });
                            selector = selector.replace(selectorMatch[0], '');
                            break; // Break on match
                        }
                        //<debug>
                        // Exhausted all matches: It's an error
                        if (i === (length - 1)) {
                            Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
                        }
                        //</debug>
                    }
                }

                // Now we are going to check for a mode change. This means a space
                // or a > to determine if we are going to select all the children
                // of the currently matched items, or a ^ if we are going to use the
                // ownerCt axis as the candidate source.
                if (modeMatch[1]) { // Assignment, and test for truthiness!
                    operations.push({
                        mode: modeMatch[2]||modeMatch[1]
                    });
                    selector = selector.replace(modeMatch[0], '');
                }
            }

            //  Now that we have all our operations in an array, we are going
            // to create a new Query using these operations.
            return new cq.Query({
                operations: operations
            });
        }
    });
});


/**
 * @file 处理前端资源
 * @author musicode
 */

var fs = require('fs');
var path = require('path');
var crypto = require('crypto');

var es = require('event-stream');

var amdDeploy = require('amd-deploy');
var parseFile = require('amd-deploy/lib/parseFile');
var resolveResourceId = require('amd-deploy/lib/resolveResourceId');
var filePathToResourceId = require('amd-deploy/lib/filePathToResourceId');
var resourceIdToFilePath = require('amd-deploy/lib/resourceIdToFilePath');
var generateFileCode = require('amd-deploy/lib/generateFileCode');
var parseFactoryResources = require('amd-deploy/lib/parseFactoryResources');
var replaceResources = require('amd-deploy/lib/replaceResources');
var readRequireConfig = require('amd-deploy/lib/readRequireConfig');
var util = require('amd-deploy/lib/util');

var htmlRules = [

    {
        pattern: /href=['"](?:[^'"]+\.(?:ico|css|less|styl|sass)(?:\?.+)?)['"]/gi,
        match: function (result) {
            var terms = result.split(/['"]/);
            if (terms.length === 3) {
                return terms[1];
            }
        }
    },

    {
        pattern: /src=['"][^'"]+['"]/gi,
        match: function (result) {
            var terms = result.split(/['"]/);
            if (terms.length === 3) {
                return terms[1];
            }
        }
    }

];

var cssRules = [

    {
        pattern: /@import\s+['"](?:[^'")]+)['"]/gi,
        match: function (result, file) {
            var terms = result.split(/['"]/);
            if (terms.length === 3) {

                var result = terms[1];

                if (path.extname(result) === '') {
                    return {
                        extname: path.extname(file.path),
                        raw: result
                    };
                }
                else {
                    return result;
                }

            }
        }
    },

    {
        pattern: /url\(['"]?(?:[^'")]+)['"]?\)/gi,
        match: function (result, file) {

            var terms = result.split(/['"]/);
            var result;

            if (terms.length === 3) {
                result = terms[1];
            }
            else {
                result = result.split('(')[1].split(')')[0];

                // background: url( ../img/a.png )
                // 类似这种，两边可以有空白符，因此要 trim
                result = result.trim();
            }

            if (result) {

                if (path.extname(result) === '') {
                    return {
                        extname: path.extname(file.path),
                        raw: result
                    };
                }
                else {
                    return result;
                }

            }
        }

    }

];

/**
 * 计算 md5
 *
 * @inner
 * @param {Buffer} buffer
 * @return {string}
 */
function md5(buffer) {
    var hash = crypto.createHash('md5');
    return hash.update(buffer).digest('hex').slice(0, 10);
}

/**
 * 是否是绝对路径
 *
 * @inner
 * @param {string}  url
 * @return {boolean}
 */
function isAbsolute(url) {
    return /^(?:https?|data|javascript):/i.test(url);
}

/**
 * 清掉 url 的 query 后缀
 *
 * @inner
 * @param {string} url
 * @return {string}
 */
function cleanQuery(url) {

    var query = path.extname(url).split('?')[1];

    if (query && query.length > 0) {

        return url.substr(
            0,
            url.length - (query.length + 1)
        );

    }

    return url;

}

/**
 * 正则替换
 *
 * @inner
 * @param {string} content
 * @param {string} pattern
 * @param {string|Function} replacement
 * @return {string}
 */
function replaceByPattern(content, pattern, replacement) {
    return content.replace(
        createPattern(pattern, 'g'),
        replacement
    );
}

/**
 * 创建一个正则表达式
 *
 * @inner
 * @param {string} pattern
 * @param {string} flags
 * @return {RegExp}
 */
function createPattern(pattern, flags) {

    pattern = pattern.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");

    return flags
         ? new RegExp(pattern, flags)
         : new RegExp(pattern);

}

/**
 * 正则全局提取依赖
 *
 * @inner
 * @param {Object} file
 * @param {Array} rules
 * @return {Array}
 */
function walkDependencies(file, rules) {

    var directory = path.dirname(file.path);
    var content = file.contents.toString();

    var list = [ ];

    // 这里不用判断是否已添加
    // 比如同一个文件出现下面两句
    //
    // require(
    //     [ 'common/a' ]
    // );
    //
    // require(
    //     [ 'common/a', 'common/b' ]
    // );
    //
    // common/a 无需判断是否存在，因为它出现在不同的匹配块中
    //
    var addDependency = function (dependency) {
        list.push(dependency);
    };

    rules.forEach(function (parser, index) {

        var results = content.match(parser.pattern);

        if (results) {
            results.forEach(function (result) {

                var dependencies = parser.match(result, file);
                if (!dependencies) {
                    return;
                }

                if (!Array.isArray(dependencies)) {
                    dependencies = [ dependencies ];
                }

                dependencies.forEach(function (dependency) {

                    // 支持返回对象，必须包含 raw 属性
                    if (typeof dependency === 'string') {
                        dependency = {
                            raw: dependency
                        };
                    }

                    var raw = dependency.raw;
                    var absolute = dependency.absolute;

                    if (!absolute) {
                        absolute = /^(?:\w|\.(?:\.)?)/.test(raw)
                                 ? path.join(directory, raw)
                                 : raw;
                    }

                    var extname = dependency.extname;
                    if (extname && extname.length > 1) {

                        var terms = absolute.split('.');
                        terms.pop();
                        terms.push(
                            extname.substr(1)
                        );

                        absolute = terms.join('.');

                    }

                    dependency.raw = cleanQuery(raw);
                    dependency.absolute = cleanQuery(absolute);

                    // 便于替换回去
                    dependency.match = result;

                    addDependency(dependency);

                });

            });

        }

    });

    return list;

}

/**
 * 依赖去重
 *
 * @inner
 * @param {Array} dependencies
 * @return {Array}
 */
function uniqueDependencies(dependencies) {

    var result = [ ];
    var map = { };

    dependencies.forEach(function (dependency) {
        if (!map[ dependency.absolute ]) {
            result.push(dependency);
            map[ dependency.absolute ] = 1;
        }
    });

    return result;

}

/**
 * 纠正依赖的格式
 *
 * 开发时通常会约定一些不通用的路径，如 ${staticServer}/src/a.js，编译时需要处理
 *
 * @inner
 * @param {Object} file
 * @param {Array} dependencies
 * @param {Function} correct
 */
function correctDependencies(file, dependencies, correct) {

    if (!correct) {
        return;
    }

    for (var i = dependencies.length - 1; i >= 0; i--) {
        correct(file, dependencies[i]);
    }

}

/**
 * 过滤一些不需要的依赖，通常在 correctDependencies 之后处理
 *
 * @inner
 * @param {Object} file
 * @param {Array} dependencies
 * @param {Function} filter 返回 true 表示需要过滤
 */
function filterDependencies(file, dependencies, filter) {

    for (var i = dependencies.length - 1; i >= 0; i--) {

        var dependency = dependencies[i];

        // 绝对路径不用处理
        if (isAbsolute(dependency.raw)
            || (filter && filter(file, dependency))
        ) {
            dependencies.splice(i, 1);
        }

    }

}

/**
 * 替换依赖
 *
 * @inner
 * @param {Object} file
 * @param {Array} dependencies
 * @param {Function} rename
 */
function renameDependencies(file, dependencies, rename) {

    var srcContent = file.contents.toString();
    var destContent = srcContent;

    // 按 match 分组
    var group = { };

    dependencies.forEach(function (dependency) {

        var list = group[ dependency.match ];
        if (!list) {
            list = group[ dependency.match ] = [ ];
        }

        list.push(dependency);

    });

    util.each(group, function (dependencies, match) {

        destContent = replaceByPattern(
            destContent,
            match,
            function (result) {

                dependencies.forEach(function (dependency) {

                    var replacement = rename(dependency);
                    if (replacement) {
                        result = replaceByPattern(
                            result,
                            dependency.raw,
                            replacement
                        );
                    }

                });

                return result;

            }
        );

    });

    if (srcContent !== destContent) {
        file.contents = new Buffer(destContent);
    }

}

/**
 * 获取递归计算的 md5
 *
 * @inner
 * @param {string} dependency
 * @param {Object} hashMap
 * @param {Object} dependencyMap
 * @return {string}
 */
function getRecursiveHash(dependency, hashMap, dependencyMap) {

    // 递归分析出的完整的依赖列表
    var dependencies = [ ];
    var map = { };

    var addDependency = function (dependency) {
        // 要避免循环依赖
        if (!map[ dependency ]) {

            map[ dependency ] = 1;

            dependencies.push(dependency);

            var childDependencies = dependencyMap[ dependency ];
            if (Array.isArray(childDependencies)) {
                childDependencies.forEach(addDependency);
            }

        }
    };

    addDependency(dependency);

    // 按字母表顺序排序，确保每次顺序一致
    dependencies.sort(
        function (a, b) {
            if (a > b) {
                return 1;
            }
            else if (a < b) {
                return -1;
            }
            else {
                return 0;
            }
        }
    );

    var list = [ ];

    dependencies.forEach(
        function (dependency) {

            var hash = hashMap[ dependency ];
            if (hash) {
                list.push(hash);
            }

        }
    );

    var hash;

    switch (list.length) {
        case 0:
            hash = '';
            break;
        case 1:
            hash = list[0];
            break;
        default:
            hash = md5(new Buffer(list.join('')));
            break;
    }

    return hash;

}

/**
 * 分析 html 文件依赖
 *
 * @inner
 * @param {Object} file
 * @param {Object} instance
 * @param {Object} options
 * @return {Object}
 */
function htmlDependencies(file, instance, options) {

    var dependencies = walkDependencies(
        file,
        instance.htmlRules
    );

    correctDependencies(
        file,
        dependencies,
        instance.correctDependency
    );

    filterDependencies(
        file,
        dependencies,
        instance.filterDependency
    );

    if (options.process) {
        options.process(file, dependencies);
    }

    if (options.rename) {
        renameDependencies(
            file,
            dependencies,
            function (dependency) {
                return options.rename(
                    file,
                    dependency,
                    instance.hashMap,
                    instance.dependencyMap
                );
            }
        );
    }

}

/**
 * 分析 css 文件依赖
 *
 * @inner
 * @param {Object} file
 * @param {Object} instance
 * @param {Object} options
 * @return {Object}
 */
function cssDependencies(file, instance, options) {

    var dependencies = walkDependencies(
        file,
        instance.cssRules
    );

    correctDependencies(
        file,
        dependencies,
        instance.correctDependency
    );

    filterDependencies(
        file,
        dependencies,
        instance.filterDependency
    );

    if (options.process) {
        options.process(file, dependencies);
    }

    if (options.rename) {

        renameDependencies(
            file,
            dependencies,
            function (dependency) {
                return options.rename(
                    file,
                    dependency,
                    instance.hashMap,
                    instance.dependencyMap
                );
            }
        );

    }
}

/**
 * 分析 amd 文件依赖
 *
 * @inner
 * @param {Object} file
 * @param {Object} instance
 * @param {Object} options
 * @return {Object}
 */
function amdDependencies(file, instance, options) {

    var dependencies = [ ];

    var config = instance.getAmdConfig(file);

    var fileInfo = parseFile(
        file.path,
        file.contents.toString(),
        config
    );

    fileInfo.modules.forEach(
        function (module) {

            var resources = parseFactoryResources(module.factory);

            [
                // 同步
                module.dependencies,
                // 异步
                resources.async,
                // 其他资源，如 toUrl('../../a.png')
                resources.other
            ]
            .forEach(function (resources) {

                resources.forEach(function (resource) {

                    if (util.keywords[ resource.id ]) {
                        return;
                    }

                    var resourceId = resolveResourceId(resource.id, module.id);
                    var filePath = resourceIdToFilePath(resourceId, config);

                    if (filePath) {
                        dependencies.push({
                            raw: resource.id,
                            absolute: filePath
                        });
                    }

                });

            });
        }
    );

    correctDependencies(
        file,
        dependencies,
        instance.correctDependency
    );

    filterDependencies(
        file,
        dependencies,
        instance.filterDependency
    );

    if (options.process) {
        options.process(file, dependencies);
    }

    if (options.rename) {

        var replaceRequireResource = config.replaceRequireResource;

        config.replaceRequireResource = function (resource, absolute) {
            return options.rename(
                file,
                {
                    raw: resource.id,
                    absolute: absolute
                },
                instance.hashMap,
                instance.dependencyMap
            );
        };

        replaceResources(
            fileInfo,
            config
        );

        config.replaceRequireResource = replaceRequireResource;

        file.contents = new Buffer(
            generateFileCode(fileInfo, config.minify)
        );

    }
}

/**
 * 获取文件对应的遍历器
 *
 * @inner
 * @param {Object} file
 * @return {Function?}
 */
function getIterator(file) {

    var iterator;

    switch (path.extname(file.path).toLowerCase()) {

        // 其实用 .html 和 .tpl 就行了
        // 非要用太奇葩的扩展名，就自己蛋疼去吧
        case '.html':
        case '.tpl':
        case '.hbs':
        case '.ejs':
        case '.volt':
        case '.twig':
        case '.phtml':
            iterator = htmlDependencies;
            break;

        case '.css':
        case '.less':
        case '.styl':
        case '.sass':
            iterator = cssDependencies;
            break;

        case '.js':
            iterator = amdDependencies;
            break;

    }

    return iterator;

}

/**
 *
 * @param {Object} options
 * @property {Array} options.htmlRules
 * @property {Array} options.cssRules
 * @property {Function} options.getAmdConfig 获取 AMD 配置，会传入当前处理的文件对象
 * @property {Function} options.renameFile 重命名文件，比如加上 md5
 * @property {Function} options.renameDependency 重命名依赖，比如加上 md5
 * @property {Function=} options.filterDependency 过滤非法依赖，比如 src/img/${a}.jpg
 * @property {Function} options.correctDependency 纠正依赖的格式，比如开发约定 {{ $static_server }}/src 开头
 *                                                build 需要纠正为正确的路径
 */
function Resource(options) {

    util.extend(this, options);

    this.htmlRules = util.merge(htmlRules, options.htmlRules);
    this.cssRules = util.merge(cssRules, options.cssRules);

    this.hashMap = { };
    this.dependencyMap = { };

    // 缓存递归 MD5
    this.recursiveHashMap = { };

}

Resource.prototype = {

    constructor: Resource,

    /**
     * 自定义处理
     *
     * @param {Function} handler(file, callback)
     */
    custom: function (handler) {

        var me = this;

        return es.map(function (file, callback) {

            var done = function () {
                callback(null, file);
            };

            if (file.contents) {
                handler(file, done);
            }
            else {
                done();
            }

        });
    },

    /**
     * 获取 html 文件的依赖
     *
     * @param {Object} options
     * @property {Function} options.process
     * @property {Function} options.rename
     */
    htmlDependencies: function (options) {

        var me = this;

        return me.custom(function (file, callback) {

            htmlDependencies(file, me, options);

            callback();

        });

    },

    /**
     * 获取 css 文件的依赖
     *
     * @param {Object} options
     * @property {Function} options.process
     * @property {Function} options.rename
     */
    cssDependencies: function (options) {

        var me = this;

        return me.custom(function (file, callback) {

            cssDependencies(file, me, options);

            callback();

        });
    },

    /**
     * 获取 amd 文件的依赖
     *
     * @param {Object} options
     * @property {Function} options.process
     * @property {Function} options.rename
     */
    amdDependencies: function (options) {

        var me = this;

        return me.custom(function (file, callback) {

            amdDependencies(file, me, options);

            callback();

        });
    },

    /**
     * 分析文件的 hash
     *
     * @param {Object} options
     * @property {Function=} options.filter 文件过滤
     */
    analyzeHash: function (options) {

        var me = this;
        var filter = options && options.filter;

        return me.custom(function (file, callback) {

            if (!filter || !filter(file)) {

                var filePath = file.path;
                var hash = me.hashMap[ filePath ];

                hash = md5(file.contents);
                me.hashMap[ filePath ] = hash;

            }

            callback();

        });

    },

    /**
     * 分析文件的依赖
     *
     * 只能分析 html css amd 三种文件
     *
     * @param {Object} options
     * @property {Function=} options.filter 文件过滤
     */
    analyzeDependencies: function (options) {

        var me = this;
        var filter = options && options.filter;

        return me.custom(function (file, callback) {

            if (!filter || !filter(file)) {

                var iterator = getIterator(file);

                if (iterator) {
                    iterator(file, me, {
                        process: function (file, dependencies) {

                            if (dependencies.length > 0) {
                                me.dependencyMap[ file.path ] = uniqueDependencies(dependencies).map(
                                    function (dependency) {
                                        return dependency.absolute;
                                    }
                                );
                            }

                        }
                    });
                }
            }

            callback();

        });

    },

    /**
     * 替换依赖
     *
     * @param {Object} options
     * @property {Function=} options.filter
     * @property {Function=} options.replace
     */
    renameDependencies: function (options) {

        var me = this;

        var hashMap = me.hashMap;
        var dependencyMap = me.dependencyMap;

        var filter;
        var replace;

        if (options) {
            filter = options.filter;
            replace = options.replace;
        }

        return me.custom(function (file, callback) {

            if (!filter || !filter(file)) {

                var iterator = getIterator(file);

                if (iterator) {
                    iterator(file, me, {
                        process: function (file, dependencies) {

                            if (replace) {
                                var srcContent = file.contents.toString();
                                var destContent = replace(file, srcContent);
                                if (destContent && destContent !== srcContent) {
                                    file.contents = new Buffer(destContent);
                                }
                            }

                        },
                        rename: function (file, dependency) {

                            var prefix = './';

                            // "./a.js" 重命名为 "./a_123.js"
                            // 但是 path.join('.', 'a.js') 会变成 a.js

                            if (dependency.raw.indexOf(prefix) !== 0) {
                                prefix = '';
                            }

                            var dependencyPath = me.renameDependency(

                                file,
                                me.getFileHash(
                                    file.path,
                                    hashMap,
                                    dependencyMap,
                                    true
                                ),

                                dependency,
                                me.getFileHash(
                                    dependency.absolute,
                                    hashMap,
                                    dependencyMap,
                                    true
                                )

                            );

                            if (prefix && dependencyPath.indexOf(prefix) !== 0) {
                                dependencyPath = prefix + dependencyPath;
                            }

                            return dependencyPath;

                        }
                    });
                }
            }

            callback();

        });

    },

    /**
     * 生成文件名带有哈希值的文件
     *
     * @param {Object} options
     * @property {Function=} options.filter
     */
    renameFiles: function (options) {

        var me = this;
        var filter = options && options.filter;

        var hashMap = me.hashMap;
        var dependencyMap = me.dependencyMap;

        return me.custom(function (file, callback) {

            if (!filter || !filter(file)) {

                var hash = me.getFileHash(
                    file.path,
                    hashMap,
                    dependencyMap,
                    true
                );

                var filePath = me.renameFile(file, hash);
                if (filePath) {
                    file.path = filePath;
                }

            }

            callback();

        });

    },

    /**
     * 获得文件的哈希（递归哈希）
     *
     * @param {string} filePath
     * @param {Object=} hashMap
     * @param {Object=} dependencyMap
     * @param {boolean=} cache 是否缓存，默认不缓存
     * @return {string}
     */
    getFileHash: function (filePath, hashMap, dependencyMap, cache) {

        var me = this;
        var recursiveHashMap = me.recursiveHashMap;

        var hash = recursiveHashMap[ filePath ];

        if (!cache || !hash) {
            hash = getRecursiveHash(
                filePath,
                hashMap,
                dependencyMap
            );
        }

        if (cache) {
            recursiveHashMap[ filePath ] = hash;
        }

        return hash;

    },

    /**
     * 编译 amd 模块
     */
    buildAmdModules: function () {

        var me = this;

        return me.custom(function (file, callback) {

            amdDeploy({
                file: file.path,
                content: file.contents.toString(),
                config: me.getAmdConfig(file),
                callback: function (code) {

                    file.contents = new Buffer(code);
                    callback();

                }
            });

        });

    },

    /**
     * 解析 amd 依赖
     *
     * @param {Object} file 文件
     * @param {string} match 文件中匹配到的原始字符串
     * @param {string} literal 从 match 中抽离出的符合 id 规则的字面量
     * @return {Array.<string>}
     */
    parseAmdDependencies: function (file, match, literal) {

        // literal 可能是 'moduleId'、'[ "module1", "module2" ]'、xxx（非法 js 变量）

        literal = literal.trim();

        var resources;

        try {
            var factory = new Function('return ' + literal);
            resources = factory();
        }
        catch (e) {

            console.log('[INFO][amd id parse error]');
            console.log(match);
            console.log('');

            resources = literal;
        }

        if (!resources) {
            return;
        }

        if (!Array.isArray(resources)) {
            resources = [ resources ];
        }

        var me = this;
        var config = me.getAmdConfig(file);

        var result = [ ];

        resources.forEach(function (resourceId) {

            var filePath = resourceIdToFilePath(
                resourceId,
                config
            );

            if (filePath) {
                result.push({
                    amd: true,
                    raw: resourceId,
                    absolute: filePath
                });
            }

        });

        return result;

    },

    /**
     * 解析文本中的 require.config，比如写在 html
     *
     * @param {string} content
     * @return {Array}
     */
    parseAmdConfig: function (content) {
        return readRequireConfig(content);
    },

    /**
     * 资源 id 转为文件路径
     *
     * @param {string} resourceId
     * @param {Object} config
     * @return {string}
     */
    resourceIdToFilePath: function (resourceId, config) {
        return resourceIdToFilePath(resourceId, config);
    },

    /**
     * 文件路径转为资源 id
     *
     * @param {string} resourceId
     * @param {Object} config
     * @return {string}
     */
    filePathToResourceId: function (filePath, config) {
        return filePathToResourceId(filePath, config)[0];
    }

};

module.exports = Resource;


'use strict';

// MODULES //

var factory = require( './factory.js' );

// POST //

/**
* FUNCTION: post( [data,] opts, clbk )
*	Sends a POST request to a Travis CI API endpoint.
*
* @param {String|Object} [data] - request data
* @param {Object} opts - function options
* @param {String} [opts.protocol='https'] - request protocol
* @param {String} [opts.hostname='api.travis-ci.org'] - endpoint hostname
* @param {Number} [opts.port] - endpoint port
* @param {String} [opts.pathname='/'] - resource pathname
* @param {String} [opts.query=""] - params portion of a query string
* @param {String} [opts.token] - Travis CI access token
* @param {String} [opts.accept='application/vnd.travis-ci.2+json'] - media type
* @param {String} [opts.useragent] - user agent string
* @param {Function} clbk - callback to invoke upon query completion
* @returns {Void}
*/
function post( data, opts, clbk ) {
	if ( arguments.length === 2 ) {
		// Assume `data` is `opts` and `opts` is `clbk`...
		return factory( data, opts )();
	}
	factory( opts, clbk )( data );
} // end FUNCTION post()

// EXPORTS //

module.exports = post;


/**
 * @module kad-telemetry/router-decorator
 */

'use strict'

var inherits = require('util').inherits
var each = require('async-each')

/**
 * Returns a decorated router that wraps node selection
 * @constructor
 * @param {kad.Router} Router
 */
function RouterDecorator (Router) {

  function TelemetryRouter (options) {
    if (!(this instanceof TelemetryRouter)) {
      return new TelemetryRouter(options)
    }

    Router.call(this, options)
  }

  inherits(TelemetryRouter, Router)

  /**
   * Wraps getNearestContacts with telemetry
   * #getNearestContacts
   * @returns {Array} shortlist
   */
  TelemetryRouter.prototype.getNearestContacts = function (key, limit, id, cb) {
    var self = this
    var callback = function (err, shortlist) {
      if (!err) {
        self._log.debug('sorting shortlist based on telemetry score')
        var profiles = {}
        each(shortlist, function (contact, iteratorCallback) {
          var profileCallback = function (err, profile) {
            profiles[contact.nodeID] = profile
            iteratorCallback(err)
          }
          self._rpc.telemetry.getProfile(contact, profileCallback)
        }, function (err) {
          if (err) {
            cb(null, shortlist)
          } else {
            shortlist.sort(self._compare.bind(self, profiles))
            cb(null, shortlist)
          }
        })
      } else {
        cb(err, null)
      }
    }
    Router.prototype.getNearestContacts.call(this, key, limit, id, callback)
  }

  /**
   * Uses the transport telemetry to compare two nodes
   * #_compare
   * @param {kad.Contact} contactA
   * @param {kad.Contact} contactB
   * @returns {Number}
   */
  TelemetryRouter.prototype._compare = function (profiles, cA, cB) {
    var profileA = profiles[cA.nodeID]
    var profileB = profiles[cB.nodeID]
    var scoresA = {}
    var scoresB = {}

    this._rpc._telopts.metrics.forEach(function (Metric) {
      var m = new Metric()
      scoresA[m.key] = Metric.score(m.getMetric(profileA))
      scoresB[m.key] = Metric.score(m.getMetric(profileB))
    })

    var resultA = TelemetryRouter.getSuccessProbability(scoresA)
    var resultB = TelemetryRouter.getSuccessProbability(scoresB)

    this._log.debug(
      'success probability is %d% vs %d%',
      (resultA * 100).toFixed(3),
      (resultB * 100).toFixed(3)
    )

    // results are close to each other, break tie with throughput score
    if (Math.abs(resultB - resultA) <= 0.005) {
      this._log.debug(
        'score difference is within threshold, selecting based on throughput'
      )
      return scoresB.throughput - scoresA.throughput
    }

    return resultB - resultA
  }

  /**
   * Uses a profile scorecard to calculate the probability of success
   * #getSuccessProbability
   * @param {Object} score
   * @returns {Number}
   */
  TelemetryRouter.getSuccessProbability = function (score) {
    return (score.reliability + score.availability + score.latency) / 3
  }

  return TelemetryRouter
}

module.exports = RouterDecorator


'use strict';

// FUNCTIONS //

var sqrt = Math.sqrt;

// PARTIAL //

/**
* FUNCTION: partial( a, b, c )
*	Partially applies lower limit `a` and upper limit `b` and mode `c` and returns a function for evaluating the quantile function for a triangular distribution.
*
* @param {Number} a - lower limit
* @param {Number} b - upper limit
* @param {Number} c - mode
* @returns {Function} quantile function
*/
function partial( a, b, c ) {
	var pInflection = ( c - a ) / ( b - a ),
		fact1 = ( b - a ) * ( c - a),
		fact2 = ( b - a ) * ( b - c );
	/**
	* FUNCTION: quantile( p )
	*	Evaluates the quantile function for a triangular distribution.
	*
	* @private
	* @param {Number} p - input value
	* @returns {Number} evaluated quantile function
	*/
	return function quantile( p ) {
		if ( p !== p || p < 0 || p > 1 ) {
			return NaN;
		}
		if ( p  < pInflection ) {
			return a + sqrt( fact1 * p );
		}
		if ( p > pInflection ) {
			return b - sqrt( fact2 * ( 1 - p ) );
		}
		// Case: p = pInflection
		return c;
	};
} // end FUNCTION partial()

// EXPORTS //

module.exports = partial;


/**
 * @private
 */
Ext.define('Ext.device.sqlite.Sencha', {
    /**
     * Returns a {@link Ext.device.sqlite.Database} instance.
     * If the database with specified name does not exist, it will be created.
     * If the creationCallback is provided,
     * the database is created with the empty string as its version regardless of the specified version.
     *
     * @param {Object} config
     * The object which contains the following config options:
     *
     * @param {String} config.name This is required.
     * The name of the database to open.
     *
     * @param {String} config.version This is required.
     * The version of the database to open.
     *
     * @param {String} config.displayName This is required.
     * The display name of the database to open.
     *
     * @param {Number} config.estimatedSize This is required.
     * The estimated size of the database to open.
     *
     * @param {Function} config.creationCallback This is optional.
     * The callback to be called when the database has been created.
     *
     * @param {Ext.device.sqlite.Database} config.creationCallback.database
     * The created database with the empty string as its version regardless of the specified version.
     *
     * @param {Object} config.scope This is optional.
     * The scope object.
     *
     * @return {Ext.device.sqlite.Database}
     * The opened database, null if an error occured.
     */
    openDatabase: function(config) {
        if (config.name == null) {
            Ext.Logger.error('Ext.device.SQLite#openDatabase: You must specify a `name` of the database.');
            return null;
        }

        if (config.version == null) {
            Ext.Logger.error('Ext.device.SQLite#openDatabase: You must specify a `version` of the database.');
            return null;
        }

        if (config.displayName == null) {
            Ext.Logger.error('Ext.device.SQLite#openDatabase: You must specify a `displayName` of the database.');
            return null;
        }

        if (config.estimatedSize == null) {
            Ext.Logger.error('Ext.device.SQLite#openDatabase: You must specify a `estimatedSize` of the database.');
            return null;
        }

        var database = null;

        var result = Ext.device.Communicator.send({
            command: 'SQLite#openDatabase',
            sync: true,
            name: config.name,
            version: config.version,
            displayName: config.displayName,
            estimatedSize: config.estimatedSize,
            callbacks: {
                // `creationCallback != null` is checked for internal logic in native plugin code
                creationCallback: !config.creationCallback ? null : function() {
                    config.creationCallback.call(config.scope || this, database);
                }
            },
            scope: config.scope || this
        });

        if (result) {
            if (result.error) {
                Ext.Logger.error(result.error);
                return null;
            }

            database = Ext.create('Ext.device.sqlite.Database', result.id, result.version);

            return database;
        }

        return null;
    }
}, function() {
    /**
     * The Database class which is used to perform transactions.
     */
    Ext.define('Ext.device.sqlite.Database', {
        id: 0,
        version: null,

        constructor: function(id, version) {
            this.id = id;
            this.version = version;
        },

        /**
         * Returns the current version of the database.
         *
         * @return {String}
         * The database current version.
         */
        getVersion: function() {
            return Ext.device.Communicator.send({
                command: 'SQLite#getVersion',
                sync: true,
                databaseId: this.id
            });
        },

        /**
         * Performs a {@link Ext.device.sqlite.SQLTransaction} instance in a read/write mode.
         *
         * @param {Object} config
         * The object which contains the following config options:
         *
         * @param {Function} config.callback This is required.
         * The callback to be called when the transaction has been created.
         *
         * @param {Ext.device.sqlite.SQLTransaction} config.callback.transaction
         * The created transaction.
         *
         * @param {Function} config.success This is optional.
         * The callback to be called when the transaction has been successfully commited.
         *
         * @param {Function} config.failure This is optional.
         * The callback to be called when an error occurred and the transaction has been rolled back.
         *
         * @param {Object} config.failure.error
         * The occurred error.
         *
         * @param {Object} config.scope
         * The scope object
         */
        transaction: function(config) {
            if (!config.callback) {
                Ext.Logger.error('Ext.device.sqlite.Database#transaction: You must specify a `callback` callback.');
                return null;
            }

            var me = this;
            Ext.device.Communicator.send({
                command: 'SQLite#createTransaction',
                databaseId: this.id,
                readOnly: config.readOnly,
                callbacks: {
                    success: function(id) {
                        var exception = null;
                        var error = null;
                        var transaction = Ext.create('Ext.device.sqlite.SQLTransaction', id);

                        error = Ext.device.Communicator.send({
                            command: 'SQLite#beginTransaction',
                            sync: true,
                            transactionId: transaction.id
                        });

                        if (!error && config.preflight) {
                            error = config.preflight.call(config.scope || this);
                        }

                        if (!error) {
                            try {
                                transaction.active = true;
                                config.callback.call(config.scope || this, transaction); // may throw exception
                            } catch (e) {
                                exception = e;
                            } finally {
                                transaction.active = false;
                            }
                        }

                        var statements = transaction.statements;

                        while (!(exception || error) && statements.length > 0) {
                            var statement = statements.shift();
                            var result = Ext.device.Communicator.send({
                                command: 'SQLite#executeStatement',
                                sync: true,
                                transactionId: transaction.id,
                                databaseId: me.id,
                                version: me.version,
                                sqlStatement: statement.sqlStatement,
                                arguments: JSON.stringify(statement.arguments)
                            });

                            if (result) {
                                if (result.error) {
                                    error = result.error;
                                } else if (statement.callback) {
                                    var resultSet = Ext.create('Ext.device.sqlite.SQLResultSet', result);

                                    try {
                                        transaction.active = true;
                                        statement.callback.call(statement.scope || this, transaction, resultSet); // may throw exception
                                    } catch (e) {
                                        exception = e;
                                    } finally {
                                        transaction.active = false;
                                    }
                                }
                            }

                            if (error && statement.failure) {
                                try {
                                    transaction.active = true;
                                    if (!statement.failure.call(statement.scope || this, transaction, error)) { // may throw exception
                                        error = null;
                                    }
                                } catch (e) {
                                    exception = e;
                                } finally {
                                    transaction.active = false;
                                }
                            }
                        }

                        if (!(exception || error)) {
                            error = Ext.device.Communicator.send({
                                command: 'SQLite#commitTransaction',
                                sync: true,
                                transactionId: transaction.id
                            });

                            if (!error) {
                                if (config.postflight) {
                                    config.postflight.call(config.scope || this);
                                }

                                if (config.success) {
                                    config.success.call(config.scope || this);
                                }
                            }
                        }

                        if (exception || error) {
                            statements.splice(0, statements.length);

                            Ext.device.Communicator.send({
                                command: 'SQLite#rollbackTransaction',
                                sync: true,
                                transactionId: transaction.id
                            });

                            if (exception) {
                                throw exception;
                            } else if (config.failure) {
                                config.failure.call(config.scope || this, error);
                            }
                        }
                    },
                    failure: function(error) {
                        if (config.failure) {
                            config.failure.call(config.scope || this, error);
                        }
                    }
                },
                scope: config.scope || this
            });
        },

        /**
         * Works the same way as {@link Ext.device.sqlite.Database#transaction},
         * but performs a {@link Ext.device.sqlite.SQLTransaction} instance in a read-only mode.
         */
        readTransaction: function(config) {
            this.transaction(Ext.apply(config, {
                readOnly: true
            }));
        },

        /**
         * Verifies and changes the version of the database at the same time
         * as doing a schema update with a {@link Ext.device.sqlite.SQLTransaction} instance.
         *
         * @param {Object} config
         * The object which contains the following config options:
         *
         * @param {String} config.oldVersion This is required.
         * The current version of the database.
         *
         * @param {String} config.newVersion This is required.
         * The new version of the database.
         *
         * @param {Function} config.callback This is optional.
         * The callback to be called when the transaction has been created.
         *
         * @param {Ext.device.sqlite.SQLTransaction} config.callback.transaction
         * The created transaction.
         *
         * @param {Function} config.success This is optional.
         * The callback to be called when the transaction has been successfully commited.
         *
         * @param {Function} config.failure This is optional.
         * The callback to be called when an error occurred and the transaction has been rolled back.
         *
         * @param {Object} config.failure.error
         * The occurred error.
         *
         * @param {Object} config.scope
         * The scope object
         */
        changeVersion: function(config) {
            if (config.oldVersion == null) {
                Ext.Logger.error('Ext.device.SQLite#changeVersion: You must specify an `oldVersion` of the database.');
                return null;
            }

            if (config.newVersion == null) {
                Ext.Logger.error('Ext.device.SQLite#changeVersion: You must specify a `newVersion` of the database.');
                return null;
            }

            this.transaction(Ext.apply(config, {
                preflight: function() {
                    return config.oldVersion == this.getVersion() ? null : 'Unable to change version: version mismatch';
                },
                postflight: function() {
                    var result = Ext.device.Communicator.send({
                        command: 'SQLite#setVersion',
                        sync: true,
                        databaseId: this.id,
                        version: config.newVersion
                    });

                    if (result) {
                        this.version = config.newVersion;
                    }
                }
            }));
        }
    }, function() {
        /**
         * The SQLTransaction class which is used to execute SQL statements.
         */
        Ext.define('Ext.device.sqlite.SQLTransaction', {
            id: 0,
            active: false,
            statements: null,

            constructor: function(id) {
                this.id = id;
                this.statements = new Array();
            },

            /**
             * Executes an SQL statement.
             *
             * @param {Object} config
             * The object which contains the following config options:
             *
             * @param {String} config.sqlStatement This is required.
             * The SQL statement to execute.
             *
             * @param {Array} config.arguments This is optional.
             * The arguments array to bind each '?' placeholder in the SQL statement.
             *
             * @param {Function} config.callback This is optional.
             * The callback to be called when the SQL statement succeeded.
             *
             * @param {Ext.device.sqlite.SQLTransaction} config.callback.transaction
             * The transaction of the SQL statement.
             *
             * @param {Ext.device.sqlite.SQLTransaction} config.callback.resultSet
             * The result of the SQL statement.
             *
             * @param {Function} config.failure This is optional.
             * The callback to be called when an error occurred.
             * If the callback returns false, next SQL statement will be executed.
             *
             * @param {Ext.device.sqlite.SQLTransaction} config.failure.transaction
             * The transaction of the SQL statement.
             *
             * @param {Object} config.failure.error
             * The occurred error.
             *
             * @param {Object} config.scope
             * The scope object
             */
            executeSql: function(config) {
                if (!this.active) {
                    Ext.Logger.error('Ext.device.sqlite.SQLTransaction#executeSql: An attempt was made to use a SQLTransaction that is no longer usable.');
                    return null;
                }

                if (config.sqlStatement == null) {
                    Ext.Logger.error('Ext.device.sqlite.SQLTransaction#executeSql: You must specify a `sqlStatement` for the transaction.');
                    return null;
                }

                this.statements.push({
                    sqlStatement: config.sqlStatement,
                    arguments: config.arguments,
                    callback: config.callback,
                    failure: config.failure,
                    scope: config.scope
                });
            }
        }, function() {
            /**
             * The SQLResultSet class which is used to represent SQL statements results.
             */
            Ext.define('Ext.device.sqlite.SQLResultSet', {
                insertId: 0,
                rowsAffected: 0,
                rows: null,

                constructor: function(data) {
                    this.insertId = data.insertId;
                    this.rowsAffected = data.rowsAffected;
                    this.rows = Ext.create('Ext.device.sqlite.SQLResultSetRowList', data);
                },

                /**
                 * Returns the row ID of the last row that the SQL statement inserted into the database,
                 * if the statement inserted any rows.
                 * If the statement did not insert a row, throws an exception.
                 *
                 * @return {Number}
                 * The inserted row ID.
                 */
                getInsertId: function() {
                    if (this.insertId != 0) {
                        return this.insertId;
                    } else {
                        Ext.Logger.error('Ext.device.sqlite.SQLResultSet#getInsertId: An SQLTransaction did not insert a row.');
                        return null;
                    }
                },

                /**
                 * Returns the number of rows that were changed by the SQL statement.
                 * If the statement did not change any rows, returns zero.
                 *
                 * @return {Number}
                 * The number of rows affected.
                 */
                getRowsAffected: function() {
                    return this.rowsAffected;
                },

                /**
                 * Returns a {@link Ext.device.sqlite.SQLResultSetRowList} instance representing rows returned by the SQL statement.
                 *
                 * @return {Ext.device.sqlite.SQLResultSetRowList}
                 * The rows.
                 */
                getRows: function() {
                    return this.rows;
                }
            }, function() {
                /**
                 * The SQLResultSetRowList class which is used to represent rows returned by SQL statements.
                 */
                Ext.define('Ext.device.sqlite.SQLResultSetRowList', {
                    names: null,
                    rows: null,

                    constructor: function(data) {
                        this.names = data.names;
                        this.rows = data.rows;
                    },

                    /**
                     * Returns the number of rows returned by the SQL statement.
                     *
                     * @return {Number}
                     * The number of rows.
                     */
                    getLength: function() {
                        return this.rows.length;
                    },

                    /**
                     * Returns a row at specified index returned by the SQL statement.
                     * If there is no such row, returns null.
                     *
                     * @param {Number} index This is required.
                     * The index of a row.
                     *
                     * @return {Object}
                     * The row.
                     */
                    item: function(index) {
                        if (index < this.getLength()) {
                            var item = {};
                            var row = this.rows[index];
                            this.names.forEach(function(name, index) {
                                item[name] = row[index];
                            });

                            return item;
                        }

                        return null;
                    }
                });
            });
        });
    });
});


var consoleStream = require('./consoleStream');
var levels = require('./levels');

var _only;
var _loggers = {};

var _defaults = defaults({
  enabled: true
});

/**
 * @class
 * Logger instance with a name
 *
 * @param {string} name - Name of the logger
 */
function Logger(name, options) {
  this.name     = name;
  this._enabled = _defaults(options, 'enabled');
  this._stream  = _defaults(options, 'stream');
  this._level   = _defaults(options, 'level');

  var logger = this;
  _loggers[name] = this;

  /**
   * Create the logger method for each level. Set it up in the constructor
   * to properly lock in the context.
   */
  Object.keys(levels).forEach(function(level) {
    logger[level] = function() {
      return logger.write(levels[level], arguments);
    };
  });
}

/**
 * Expose levels to allow the customization of the values if need be.
 * Don't expect this to be a common use case.
 */
Logger.prototype.levels = levels;

/**
 * Helper factory method to create named loggers
 *
 * @returns {Logger} New logger instance
 */
Logger.prototype.create = function(name, options) {
  if (_loggers[name]) {
    return _loggers[name];
  }

  return new Logger(name, options);
};

/**
 * Method to find a logger instance by name.
 *
 * @param {string} name - Name of the logger to find
 *
 * @returns {Logger}
 */
Logger.prototype.find = function(name) {
  return _loggers[name];
};

/**
 * Method to replace the current stream with a new one.
 *
 * @param {Stream} stream - Stream to write data to
 *
 * @returns {Stream} stream passed in
 */
Logger.prototype.pipe = function(stream) {
  this._stream = stream;
  return stream;
};

/**
 * Log a message with a custom `level`
 */
Logger.prototype.write = function(level, data) {
  level = level || levels.info;
  if (this.isEnabled(level)) {
    (this._stream || _global._stream).write(createPayload(this.name, level, data));
  }

  return this;
};

/**
 * Checks if the logger can write messages.
 *
 * @returns {boolean}
 */
Logger.prototype.isEnabled = function(level) {
  if (!_global._enabled) {
    return false;
  }

  var enabled = this._enabled;
  var validLevel = this._level ? this._level <= level : _global._level <= level;
  var onlyTest = !_only || _only === this;

  return enabled && validLevel && onlyTest;
};

/**
 * Method to enable the logger intance. If loggers have been disabled
 * globally then this flag will not have an immediate effect, until
 * loggers are globally enabled.
 */
Logger.prototype.enable = function() {
  this._enabled = true;
  return this;
};

/**
 * Method to disable the logger instance. Like {@link Logger#enable},
 * this setting does not have an immediate effect if loggers are globally
 * disabled.
 */
Logger.prototype.disable = function() {
  this._enabled = false;
  return this;
};

/**
 * Method to make sure *only* this logger logs messages. If another logger
 * is set to only, then the request is silently ignored.
 */
Logger.prototype.only = function() {
  if (!_only) {
    _only = this;
  }
  return this;
};

/**
 * Method to remove the logger from the `only` state to allow other loggers
 * set themselves as only.
 */
Logger.prototype.all = function() {
  _only = null;
  return this;
};

/**
 * Enables loggers globally.
 */
Logger.prototype.enableAll = function() {
  return _global.enable();
};

/**
 * Disables loggers globally.
 */
Logger.prototype.disableAll = function() {
  return _global.disable();
};

/**
 * Sets the logging level
 */
Logger.prototype.level = function(level) {
  this._level = level;
  return this;
};

/**
 * Function that create a JSON structure to be logged
 *
 * @param {string} name - Name of the logger
 * @param {int} level - Logging level. E.g. log, warn, error
 * @param {object} data - application data to be logged
 *
 * @returns {{date: Date, level: int, name: string, data: object}}
 *  Meta data to be logged
 */
function createPayload(name, level, data) {
  return {
    date: getDate(),
    level: level,
    name: name,
    data: data
  };
}

/**
 * Helper method to get timestamps for logged message
 *
 * @private
 */
function getDate() {
  return (new Date()).getTime();
}

/**
 * Default logger instance available
 */
var _global = new Logger('global', {stream: consoleStream, level: levels.info, enabled: true});

module.exports = Logger.prototype.default = _global;

function defaults(_defaults) {
  return function read(options, name) {
      return options && options.hasOwnProperty(name) ? options[name] : _defaults[name];
  };
}


'use strict'

var constants = require('./constants')
var hat = require('hat')
var merge = require('merge')

/**
 * Represents a [JSON-RPC 2.0](http://www.jsonrpc.org/specification) request or
 * response; used by {@link RPC#send}.
 *
 * Note that the value of the contact property will be replaced by the
 * {@link Contact} your implementation uses.
 *
 * The decision to use JSON-RPC as the message format was made to allow for
 * other language implementations and services to easily communicate with the
 * network, using a widely recognized and used format.
 * @constructor
 * @param {Object} spec
 * @param {String} spec.id - Message ID
 * @param {String} spec.method - Method name to include in request message
 * @param {Object} spec.params - Named parameters for request message
 * @param {Object} spec.result - Result data for response message
 * @param {Error} spec.error - Error object to convert to message
 */
function Message (spec) {
  if (!(this instanceof Message)) {
    return new Message(spec)
  }

  this.jsonrpc = '2.0'

  if (Message.isRequest(spec)) {
    this.id = spec.id || Message.createID()
    this.method = spec.method
    this.params = spec.params
  } else if (Message.isResponse(spec)) {
    this.id = spec.id
    this.result = merge({}, spec.result)
    if (spec.error) {
      this.error = {
        code: -32603,
        message: spec.error.message
      }
    }
  } else {
    throw new Error('Invalid message specification')
  }
}

/**
 * Serialize message to a Buffer
 * @returns {Buffer}
 */
Message.prototype.serialize = function () {
  return new Buffer(JSON.stringify(this), 'utf8')
}

/**
 * Returns a boolean indicating if this message is a request
 * @param {Message} message - Message instance to inspect
 * @returns {Boolean}
 */
Message.isRequest = function (parsed) {
  return !!(parsed.method && parsed.params)
}

/**
 * Returns a boolean indicating if this message is a response
 * @param {Message} message - Message instance to inspect
 * @returns {Boolean}
 */
Message.isResponse = function (parsed) {
  return !!(parsed.id && (parsed.result || parsed.error))
}

/**
 * Create a Message instance from a buffer
 * @param {Buffer} buffer - Binary blob to convert to message object
 * @returns {Message}
 */
Message.fromBuffer = function (buffer) {
  function _convertByteArrays (key, value) {
    return value && value.type === 'Buffer' ? new Buffer(value.data) : value
  }

  var parsed = JSON.parse(buffer.toString('utf8'), _convertByteArrays)
  var message = new Message(parsed)

  return message
}

/**
 * Returns a message id
 * @returns {String}
 */
Message.createID = function () {
  return hat.rack(constants.B)()
}

module.exports = Message


module.exports = function  ifFunc(func, ...args){
    if (typeof func === "function") {
        return func(...args);
    }
}

module.exports = function  isUUID(uuid){
    const uuidRegEx = new RegExp(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, 'i');
    return uuidRegEx.test(uuid);
}

module.exports = function  parseCammelCase(string) {
    return string
        .replace(/([A-Z])/g, ' $1')
        .replace(/^./, function(str){ return str.toUpperCase(); })
}

module.exports = function  arrayChunks(arr, len){ __ENFORCETYPE(arguments, "array", "number");
    let chunks = [],
        i = 0,
        n = arr.length;

    while (i < n) {
        chunks.push(arr.slice(i, i += len));
    }

    return chunks;
}

module.exports = function  objectToProps(object, keys = false) {
    if (keys === false) {
        return Component => <Component {...object} />
    }
    let flattened = {};

    mapObj(keys, (v, k) => flattened[k] = accessDeep(v, object));

    return Component => <Component {...flattened} />
}

module.exports = function  clipText(text, characterLength) {
    if (text.length <= characterLength) {
        return text;
    }

    return text.substring(0, characterLength) + "...";
}

/**
 * Simple object check.
 * @param item
 * @returns {boolean}
 */
function isObject(item) {
    return (item && typeof item === 'object' && !Array.isArray(item));
}

/**
 *
 * @param target
 * @param sources
 * @returns {*}
 */
function mergeDeep(target, ...sources) {
    if (!sources.length) return target;
    const source = sources.shift();

    if (isObject(target) && isObject(source)) {
        for (const key in source) {
            if (isObject(source[key])) {
                if (!target[key]) Object.assign(target, { [key]: {} });
                mergeDeep(target[key], source[key]);
            } else {
                Object.assign(target, { [key]: source[key] });
            }
        }
    }

    return mergeDeep(target, ...sources);
}

/**
 * Returns afternoon or morning depending on the time of date provided.
 * @function
 * @param {object} m - The moment object of the date to evaluate.
 * @return {string} Time of day.
 */
 module.exports = function  getGreetingTime (m) {
    if (!m || !m.isValid()) { return ''; } //if we can't find a valid or filled moment, we return.
    return parseFloat(m.format("HH")) >= 12 /* 24hr time to split the afternoon */ ? 'afternoon' : 'morning';
 }

/**
 * Takes in a starting date, and a target date and returns an appropriate
 * interpretation of the time lapsed. 2 days since, 1 week since, 1 month since etc.
 * @function
 * @param {string|object} targetDate - The date to measure to
 * @param {string|object} startDate - The date to measure from, default is now.
 * @param {array} hierarchy -The date format in which the time should be refactored to. hierarchy
 * @return {string} Returns a string with the appropriate string representing
 * the time lapsed based on the hierarchy variable.
 */
module.exports = function  timeSince(targetDate, startDate = moment(), hierarchy = ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds']) {
    let date = hierarchy.find(item => (moment(startDate).diff(targetDate, item)));
    const timeSince = moment(startDate).diff(targetDate, date);

    if (timeSince === 1) { // Remove plural s
        date = date.slice(0, - 1);
    }

    if (!date) return false;

    return `${timeSince} ${date}`;
}

module.exports = function  toPercent(partial, whole) {
    return partial*100/whole;
}

module.exports = function  calculateDiscountAsAPercentage(x, y) { __ENFORCETYPE(arguments, 'number', 'number');
    return !y ? null : (((1 - (x / y))) * 100).toFixed(3);
}
/**
 * Sorts an array of objects into an array of arrays. Where path is the array
 * representation of the path to the value to access. Each value that that matches
 * will be sorted into an array.
 *
 * @function
 * @param {array} array_of_objects - The array of objects to sort.
 * @param {array} path             - The array representation of the path to the delimiter.
 * @return {object} Returns an object where each value is an array of the sorted items.
 */
module.exports = function  sortObjectsByProperty(array_of_objects, path) { __ENFORCETYPE(arguments, "array", "array");
    let sorted = {};

    array_of_objects.map( item => {
        let valueSet = accessDeep(path, item);

        if ( !valueSet ) {
            if (!sorted['unset']) {
                sorted['unset'] = [];
            }
            sorted['unset'].push(item);
        } else {
            if (!sorted[valueSet]) {
                sorted[valueSet] = [];
            }
            sorted[valueSet].push(item);
        }
    });

    return sorted;
}

// Adds leading zeros where size of the number is the exptected length of the number
// anything empty will be replaced with 0s
module.exports = function  leadingZeros(num, size) {
    var s = num+"";
    while (s.length < size) s = "0" + s;
    return s;
}

module.exports = function  decodeHtml(html) {
    var txt = document.createElement("textarea");
    txt.innerHTML = html;
    return txt.value;
}

/**
 * format currency
 *
 * will convert a number to string and add commas to it.
 *
 * @param n, number/string, number to format
 * @param c, number, decimal places (optional, default is 2)
 * @param d, string, character to use where decimal is supposed to go (optional, default is ".")
 * @param t, string, character to use where commas are supposed to go (optional, default is "," use "" to have no commas)
 */
module.exports = function  formatCurrency(n, c = 2, d = ".", t = ","){
    // Negative prefix
    let num = (Number(n) / 100).toFixed(c);
    let numArray = num.toString().split(".");

    let s = Number(numArray[0]) < 0 ? "-" : "",
        i = String(parseInt(n = Math.abs(Number(numArray[0]) || 0))),
        j = (j = i.length) > 3 ? j % 3 : 0;
    return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + "." + numArray[1];
}

/**
   * serialize
   *
   * Takes an object an object and creates a query out of it and appends it to the prefix
   *
   * @param obj, Object, the object representation of what you want the query to be
   * @param prefix, string, the URL
   * @return string, returns the full URL
   */
function serialize (obj, url="") {
        let str = [];
        for(var p in obj)
          if (obj.hasOwnProperty(p)) {
            str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
          }
        return url + "?" + str.join("&");
 }

 function segment (obj, ammount = 5) {
     let newArray = [];
     let individual = [];
     let current = 0;

     obj.forEach(function(value){
         if(current >= ammount){
             newArray.push(individual);
             individual = [];
             current = 0;
         }
         individual.push(value);
         current++;
     });

     if(individual.length > 0){
         newArray.push(individual);
     }

     return newArray;
 }

function findObj (obj, callback) { __ENFORCETYPE(arguments, 'object', 'function');
    return Object.keys(obj).find((element, index, array)=>callback(obj[element], element, index, obj));
}

 function mapObj (obj, callback) { __ENFORCETYPE(arguments, 'object', 'function');
     let objArray = Object.keys(obj).map((item, k) => {
         let returning = callback(obj[item], item);
         if(returning !== false){
             return returning;
         }
     })

     return objArray;
 }

module.exports = function  filterObj (obj, predicate) {
    let newObj = {};
    let objArray = Object.keys(obj);
    objArray.forEach((item, k) => {
        if(predicate(obj[item])){
            newObj[item] = obj[item];
        }
    })
    return newObj;
}

// removes items in array1 that match items in array2, returns new array
function ripArray (array1, array2) {
    return array1.filter( function( el ) {
        return !array2.includes( el );
    } );
}

 function makeid () {
     var text = "";
     var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

     for( var i=0; i < 5; i++ )
         text += possible.charAt(Math.floor(Math.random() * possible.length));

     return text;
 }

 module.exports = function  chainPromises (promises, thisUpdate = () => {})  {
    // If there's only one promise just do it and get it over with already.
    if(promises.length == 1) {
        return promises[0]();
    }
    let startPromise = promises[0];
    promises.shift();
    let promisesLength = promises.length;
    let currentIteration = 0;
    let chainedPromises = new Promise ((resolve, reject) => {
        if(promises.length > 0){
            promises.reduce((current, next) => {
                    return current.then( (response) =>{
                        currentIteration++;
                        thisUpdate(currentIteration);
                        if(promisesLength == currentIteration){
                            return next().then((response)=>{resolve(response)});
                        }
                        return next();

                    }).catch((error)=>{
                        reject(error);
                    })
            }, startPromise())
        } else {
            return startPromise().then((msg)=>{
                thisUpdate(0);
                resolve(msg);
            }).catch((err)=>{reject(err)});
        }
    })
    return chainedPromises;
 }

 module.exports = function  getHashParams (ignore = 0) {
     const locationHash =  window.location.hash
     let locationSplit = locationHash.split("/");
     locationSplit.splice(0, ignore);
     return locationSplit.map(item => decodeURI(item));
 }
// Compares two objects
 module.exports = function  isEquivalent(a, b) {
    // Create arrays of property names
    var aProps = Object.getOwnPropertyNames(a);
    var bProps = Object.getOwnPropertyNames(b);

    // If number of properties is different,
    // objects are not equivalent
    if (aProps.length != bProps.length) {
        return false;
    }

    for (var i = 0; i < aProps.length; i++) {
        var propName = aProps[i];

        // If values of same property are not equal,
        // objects are not equivalent
        if (a[propName] !== b[propName]) {
            return false;
        }
    }

    // If we made it this far, objects
    // are considered equivalent
    return true;
}

/**
 * Takes the arguments of a function ensures that the type provided is the type
 * being bassed.
 * @function
 * @param {object} a - The arguments object of the function.
 * @param {string} [types] - The types of variables you are expecting. (string, array, object, null, number)
 */
 function __ENFORCETYPE(a, ...types) {
     if (env.application_env !== "development") return;
     let hasError = false;
     let expecting;
     let got;
     let i = 0;

     types.map( (t, index) => {
         if (a[index] === null) {
             hasError = true;
             expecting = t;
             got = "null";
             i = index;
             return;
         }
         switch (t) {
             case "mixed":
             break;
             case "jsx":
             if (!React.isValidElement(a[index])) {
                 hasError = true;
                 expecting = "jsx";
                 got = typeof a[index];
                 i = index;
             }
             case "array":
             if (!Array.isArray(a[index])) {
                 hasError = true;
                 expecting = "array";
                 got = typeof a[index];
                 i = index;
             }
             break;
             case "object":
                 if (typeof a[index] !== 'object' || Array.isArray(a[index]) || a[index] === null) {
                     hasError = true;
                     expecting = "object";
                     i = index;
                     if (a[index] === null) {
                         got = 'null';
                     } else {
                         got = Array.isArray(a[index]) ? "array" : typeof a[index];
                     }

                 }

             default:
             if (typeof a[index] !== t) {
                 hasError = true;{
                 expecting = t;
                 got = typeof a[index];}
                 i = index;
             }
         }
     });

     if (hasError) {
         let err = new Error();
         console.error(`ENFORCETYPE: param ${i + 1} is expecting ${expecting} got ${got} instead.`, err.stack);
     }
 }

/**
 * Dynamically sets a deeply nested value in an object.
 * Optionally "bores" a path to it if its undefined.
 * @function
 * @param {!object} obj  - The object which contains the value you want to change/set.
 * @param {!array} path  - The array representation of path to the value you want to change/set.
 * @param {!mixed} value - The value you want to set it to.
 * @param {boolean} setrecursively - If true, will set value of non-existing path as well.
 */
module.exports = function  setDeep(obj, path, value, setrecursively = false) {
    let level = 0;

    if (typeof path === "string") {
        path = path.split(".");
    }

    level = 0;

    path.reduce((a, b)=>{
        level++;

        if (setrecursively && typeof a[b] === "undefined" && level !== path.length){
            a[b] = {};
            return a[b];
        }

        if (level === path.length){
            a[b] = value;
            return value;
        } else {
            return a[b];
        }
    }, obj);
    return obj;
}

 export {mergeDeep, isObject, segment, mapObj, makeid, ripArray, __ENFORCETYPE, findObj, serialize}