// put this here so we could turn on/off the logging, it's soon enough after the creation of ST.ready
if (!ST.isSandbox) {
    if (document.readyState !== 'complete') {
        ST.logger.debug('init.js, document.readyState='+document.readyState+', so wait for window load event to unblock ST.ready');
        ST.ready.block();
        var fn = function() {
            ST.logger.debug('init.js, document ready state change, readyState=' + document.readyState);
            if (document.readyState === 'complete') {
                ST.ready.unblock();
            }
        };
 
        if (document.attachEvent) {
            document.attachEvent('onreadystatechange', fn);
        } else {
            document.addEventListener('readystatechange', fn);
        }
    }
}
 
ST.typeOf = function (value) {
    var typeofTypes = {
        number: 1,
        string: 1,
        'boolean': 1,
        'undefined': 1
    },
    toStringTypes = {
        '[object Array]'  : 'array',
        '[object Date]'   : 'date',
        '[object Boolean]': 'boolean',
        '[object Number]' : 'number',
        '[object RegExp]' : 'regexp',
        '[object String]' : 'string'
    };
    
    if (value === null) {
        return 'null';
    }
 
    var type = typeof value,
        ret = type,
        typeToString,
        nonSpaceRe = /\S/;
 
    if (!typeofTypes[type]) {
        if (!(ret = toStringTypes[typeToString = ({}).toString.call(value)])) {
            if (type === 'function') {
                ret = type;
            } else if (type !== 'object') {
                ret = typeToString;
            } else if (value.nodeType === undefined) {
                ret = type;
            } else if (value.nodeType === 3) {
                ret = nonSpaceRe.test(value.nodeValue) ? 'textnode' : 'whitespace';
            } else {
                ret = 'element';
            }
        }
    }
 
    return ret;
};
 
/**
 * Similar `setTimeout` but instead returns a function that cancels the timer.
 *
 * The timeout value (`millisec`) defaults to `ST.options.timeout` unless that value
 * is set to `0` in which case timeouts are disabled. In that case, `fn` will never be
 * called.
 * @param {Function} fn The function to call after `millisec` milliseconds.
 * @param {Object} [scope] The `this` pointer to use for calling `fn`.
 * @param {Number} [millisec] The delay in milliseconds. Defaults to `ST.options.timeout`
 * and is disabled if that value is `0`.
 * @return {Function}
 * @private
 * @member ST
 */
ST.timeout = function (fn, scope, millisec) {
    var ms = ST.options.timeout;
 
    if (typeof scope === 'number') {
        millisec = scope;
        scope = null;
    }
 
    if (ms !== 0 && millisec != null) {
        // if ST.options.timeout is 0, ignore all timeouts even explicit ones
        ms = millisec;
    }
 
    return ms ? ST.doTimeout(fn, scope, ms) : ST.emptyFn;
};
 
 
ST.doTimeout = function (fn, scope, ms) {
    var cancelFn = function () {
        if (cancelFn.timerId > 0) {
            ST.deferCancel(cancelFn.timerId);
            cancelFn.timerId = 0;
        }
        return null;
    };
 
    cancelFn.timerId = ST.defer(fn, scope, ms);
    cancelFn.timeout = ms;
 
    return cancelFn;
};
 
/**
 * @member ST
 * Pretty print
 * @param value
 * @private
 */
ST.prettyPrint = function(value) {
    var formattedValue = value,
        className, superclass, id, type;
 
    if (value) {
        className = value.$className;
 
        if (className !== undefined) {
            // support for pretty printing instances of Ext classes
 
            if (!className) {
                // support for anonymous classes - Ext.define(null, ...)
                // loop up the inheritance chain to find nearest non-anonymous ancestor
                superclass = value.superclass;
                while (superclass && !superclass.$className) {
                    superclass = superclass.superclass;
                }
                if (superclass) {
                    className = superclass.$className;
                }
            }
 
            id = value.id || (value.getId && value.getId());
 
            formattedValue = className + (id ? ('#' + id) : '');
        } else if (value instanceof Array) {
            formattedValue = 'Array';
        } else {
            type = typeof value;
 
            if (type === 'string') {
                formattedValue = '"' + value + '"';
            } else if (type === 'boolean' || type === 'number') {
                formattedValue = value;
            } else if (value.tagName) {
                id = value.id;
                formattedValue = '<' + value.tagName.toLowerCase() + (id ? ('#' + id) : '') + '>';
            } else if (type === 'function') {
                formattedValue = 'Function';
            } else {
                formattedValue = 'Object';
            }
        }
    }
 
    return formattedValue;
};
 
ST.globRegex = function (glob, opts) {
    /*
     https://github.com/fitzgen/glob-to-regexp
     Copyright (c) 2013, Nick Fitzgerald
     All rights reserved.
 
     Redistribution and use in source and binary forms, with or without
     modification, are permitted provided that the following conditions
     are met:
 
     - Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
 
     - Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in
     the documentation and/or other materials provided with the
     distribution.
 
     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
     FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
     COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
     INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
     BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
     CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
     ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     POSSIBILITY OF SUCH DAMAGE.
     */
    if (glob == null) {
        return null;
    }
 
    var str = String(glob),
    // The regexp we are building, as a string.
        reStr = "",
    // Whether we are matching so called "extended" globs (like bash) and should
    // support single character matching, matching ranges of characters, group
    // matching, etc.
        extended = opts ? !!opts.extended : false,
    // If we are doing extended matching, this boolean is true when we are inside
    // a group (eg {*.html,*.js}), and false otherwise.
        inGroup = false,
    // RegExp flags (eg "i" ) to pass in to RegExp constructor.
        flags = opts && typeof( opts.flags ) === "string" ? opts.flags : "",
        c, i, len;
 
    for (i = 0, len = str.length; i < len; i++) {
        c = str[i];
 
        switch (c) {
            case "\\": case "/": case "$": case "^": case "+": case ".":
            case "(": case ")": case "=": case "!": case "|":
            reStr += "\\" + c;
            break;
 
            case "?":
                if (extended) {
                    reStr += ".";
                    break;
                }
            /* fallthrough */
            case "[": case "]":
            if (extended) {
                reStr += c;
                break;
            }
            /* fallthrough */
            case "{":
                if (extended) {
                    inGroup = true;
                    reStr += "(";
                    break;
                }
            /* fallthrough */
            case "}":
                if (extended) {
                    inGroup = false;
                    reStr += ")";
                    break;
                }
            /* fallthrough */
            case ",":
                if (inGroup) {
                    reStr += "|";
                    break;
                }
                reStr += "\\" + c;
                break;
 
            case "*":
                reStr += ".*";
                break;
 
            default:
                reStr += c;
        }
    }
 
    // When regexp 'g' flag is specified don't
    // constrain the regular expression with ^ & $
    if (!flags || !~flags.indexOf('g')) {
        reStr = "^" + reStr + "$";
    }
 
    return new RegExp(reStr, flags);
};
 
ST.initGlobals = function () {
    var globals = ST.options.globals,
        covRe = '/^__cov_/',
        prop;
 
    // Any properties already in the window object are ok
    for (prop in window) {
        globals[prop] = true;
    }
 
    // Old Firefox needs these
    globals.getInterface =
        globals.loadFirebugConsole =
            globals._createFirebugConsole =
                globals.netscape =
                    globals.XPCSafeJSObjectWrapper =
                        globals.XPCNativeWrapper =
                            globals.Components =
                                globals._firebug =
                                    // IE10+ F12 dev tools adds these properties when opened.
                                    globals.__IE_DEVTOOLBAR_CONSOLE_COMMAND_LINE =
                                        globals.__BROWSERTOOLS_CONSOLE_BREAKMODE_FUNC =
                                            globals.__BROWSERTOOLS_CONSOLE_SAFEFUNC =
                                                // in IE8 jasmine's overrides of setTimeout/setInterval make them iterable
                                                globals.setTimeout =
                                                    globals.setInterval =
                                                        globals.clearTimeout =
                                                            globals.clearInterval =
                                                                // In Ext JS 4 Ext.get(window) adds an id property
                                                                globals.id =
                                                                    // Temporary namespace used by serve/contex/WebDriver to store exec/ready/call functions
                                                                    // for deferred debugging.
                                                                    globals.$ST =
                                                                        // new standard globals, found in chrome
                                                                        globals.customElements =
                                                                            globals.chrome =
                                                                                globals.external = true;
 
    ST.options.globalPatterns[covRe] = ST.globRegex(covRe);
 
    ST.options.globalsInited = true;
};
 
(function () {
    var idRe = /^[a-z$_][a-z0-9$_.]*$/i;
 
    /**
     * Adds one or more allowable global variable names.
     * Variable names can be simple names or a regex.
     * @param {String/String[]} add
     * @member ST
     */
    ST.addGlobals = function () {
        var globals = ST.options.globals,
            args = arguments,
            i = args.length,
            s;
 
        while (i-- > 0) {
            if (!(s = args[i])) {
                continue;
            }
 
            if (typeof s === 'string') {
                ST.options.contextGlobals = ST.options.contextGlobals || [];
                ST.options.contextGlobals.push(s);
 
                if (idRe.test(s)) {
                    globals[s] = true;  // simple names can be in a map
                } else {
                    ST.options.globalPatterns[s] = ST.globRegex(s);
                }
            } else {
                ST.addGlobals.apply(ST,s);  // not a String so must be a String[]
            }
        }
    };
 
    ST.checkGlobalLeaks = function () {
        var allowedGlobals = ST.options.globals,
            globalPatterns = ST.options.globalPatterns,
            result = { results: [], addedGlobals: [] },
            i, ok, property, value;
 
 
        for (property in window) {
            if (allowedGlobals[property]) {
                // Reading some properties from window can trigger warnings (such as
                // webkitStorageInfo), so skip early.
                continue;
            }
 
            for (i in globalPatterns) {
                ok = globalPatterns[i].test(property);
                if (ok) break;
            }
            if (ok) {
                continue;
            }
 
            try {
                // IE throws error when trying to access window.localStorage
                value = window[property];
            } catch (e) {
                continue;
            }
 
            if (value !== undefined &&
                (!value || // make sure we don't try to do a property lookup on a null value
                // old browsers (IE6 and opera 11) add element IDs as enumerable properties
                // of the window object, so make sure the global var is not a HTMLElement
                value.nodeType !== 1 &&
                // make sure it isn't a reference to a window object.  This happens in
                // some browsers (e.g. IE6) when the document contains iframes.  The
                // frames' window objects are referenced by id in the parent window object.
                !(value.location && value.document))) {
                // add the bad global to allowed globals so that it only fails this one spec
                result.addedGlobals.push(property);
 
                result.results.push({
                    passed: false,
                    message: 'Bad global variable: ' + property + ' = ' + ST.prettyPrint(value)
                });
            }
        }
 
        return result;
    };
 
    ST.setupOptions = function (testOptions) {
        var options = ST.options,
            globals = options.globals,
            add;
 
        if (testOptions) {
            ST.apply(options, testOptions);
 
            add = testOptions.globals;
            if (add !== undefined) {
                options.globals = globals; // put back the original globals
                ST.addGlobals(add);
            }
        }
 
        ST.ready.on(function () {
            ST.initGlobals();
        });
    };
})();
 
ST.decodeRegex = function (value) {
    var regex = value,
        isRegex = regex && typeof regex === 'object' && regex.isRegex;
 
    if (isRegex) {
        regex = new RegExp(regex.source, regex.flags);
    }
 
    return regex;
};
 
ST.encodeRegex = function (value) {
    // a regex object won't survive serialization, so deconstruct it so
    // we can put it back together on the other side
    if (value instanceof RegExp) {
        var testFlags = {
                global: 'g',
                ignoreCase: 'i',
                multiline: 'm',
                sticky: 'y',
                unicode: 'u'
            },
            flags = '',
            key;
 
        // loop over detected flags and stringify them
        // we can't use regex.flags since it's not standardized...
        for (key in testFlags) {
            if (value[key]) {
                flags += testFlags[key];
            }
        }
 
        value = {
            isRegex: true,
            source: value.source,
            flags: flags
        };
    }
 
    return value;
};
 
ST.getScrollbarSize = function (force) {
    var scrollbarSize = ST._scrollbarSize;
 
    if (force || !scrollbarSize) {
        var db = document.body,
            div = document.createElement('div');
 
        div.style.width = div.style.height = '100px';
        div.style.overflow = 'scroll';
        div.style.position = 'absolute';
 
        db.appendChild(div); // now we can measure the div... 
 
        // at least in iE9 the div is not 100px - the scrollbar size is removed! 
        ST._scrollbarSize = scrollbarSize = {
            width: div.offsetWidth - div.clientWidth,
            height: div.offsetHeight - div.clientHeight
        };
 
        db.removeChild(div);
    }
 
    return scrollbarSize;
};