/**
 * @class ST.Browser
 * Provides information about the browser.
 *
 * Should not be manually instantiated unless for unit-testing.
 * Access the global instance stored in {@link ST.browser} instead.
 */
ST.Browser = ST.define({
    /**
     * @property {Boolean} isChrome
     * True if the detected browser is Chrome.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isGecko
     * True if the detected browser uses the Gecko layout engine (e.g. Mozilla, Firefox).
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isIE
     * True if the detected browser is Internet Explorer.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isIE8
     * True if the detected browser is Internet Explorer 8.x.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isIE8m
     * True if the detected browser is Internet Explorer 8.x or lower.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isIE8p
     * True if the detected browser is Internet Explorer 8.x or higher.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isIE9
     * True if the detected browser is Internet Explorer 9.x.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isIE9m
     * True if the detected browser is Internet Explorer 9.x or lower.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isIE9p
     * True if the detected browser is Internet Explorer 9.x or higher.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isIE10
     * True if the detected browser is Internet Explorer 10.x.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isIE10m
     * True if the detected browser is Internet Explorer 10.x or lower.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isIE10p
     * True if the detected browser is Internet Explorer 10.x or higher.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isIE11
     * True if the detected browser is Internet Explorer 11.x.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isIE11m
     * True if the detected browser is Internet Explorer 11.x or lower.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isIE11p
     * True if the detected browser is Internet Explorer 11.x or higher.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isLinux
     * True if the detected platform is Linux.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isMac
     * True if the detected platform is Mac OS.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isOpera
     * True if the detected browser is Opera.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isSafari
     * True if the detected browser is Safari.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isWebKit
     * True if the detected browser uses WebKit.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Boolean} isWindows
     * True if the detected platform is Windows.
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Number} chromeVersion
     * The current version of Chrome (0 if the browser is not Chrome).
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Number} firefoxVersion
     * The current version of Firefox (0 if the browser is not Firefox).
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Number} ieVersion
     * The current version of IE (0 if the browser is not IE). This does not account
     * for the documentMode of the current page, which is factored into {@link #isIE8},
     * and {@link #isIE9}. Thus this is not always true:
     *
     *     ST.isIE8 == (ST.ieVersion == 8)
     *
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Number} operaVersion
     * The current version of Opera (0 if the browser is not Opera).
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Number} safariVersion
     * The current version of Safari (0 if the browser is not Safari).
     * @readonly
     * @member ST
     */
 
    /**
     * @property {Number} webKitVersion
     * The current version of WebKit (0 if the browser does not use WebKit).
     * @readonly
     * @member ST
     */
 
    constructor: function (userAgent, publish) {
        var me = this,
            browserPrefixes = me.browserPrefixes,
            browserNames = me.browserNames,
            enginePrefixes = me.enginePrefixes,
            engineNames = me.engineNames,
            browserMatch = userAgent.match(new RegExp('((?:' +
                    ST.getValues(browserPrefixes).join(')|(?:') + '))([\\w\\._]+)')),
            engineMatch = userAgent.match(new RegExp('((?:' +
                    ST.getValues(enginePrefixes).join(')|(?:') + '))([\\w\\._]+)')),
            browserName = browserNames.other,
            engineName = engineNames.other,
            browserVersion = '',
            engineVersion = '',
            majorVer = '',
            isWebView = false,
            i, prefix, mode, name, maxIEVersion;
 
        /**
         * @property {String}
         * Browser User Agent string.
         */
        me.userAgent = userAgent;
 
        /**
         * A "hybrid" property, can be either accessed as a method call, for example:
         *
         *     if (ST.browser.is('IE')) {
         *         // ...
         *     }
         *
         * Or as an object with Boolean properties, for example:
         *
         *     if (ST.browser.is.IE) {
         *         // ...
         *     }
         *
         * Versions can be conveniently checked as well. For example:
         *
         *     if (ST.browser.is.IE10) {
         *         // Equivalent to (ST.browser.is.IE && ST.browser.version.equals(10))
         *     }
         *
         * __Note:__ Only {@link ST.Version#getMajor major component}  and {@link ST.Version#getShortVersion simplified}
         * value of the version are available via direct property checking.
         *
         * Supported values are:
         *
         * - IE
         * - Firefox
         * - Safari
         * - Chrome
         * - Opera
         * - WebKit
         * - Gecko
         * - Presto
         * - Trident
         * - WebView
         * - Other
         *
         * @param {String} name The OS name to check.
         * @return {Boolean}
         */
        this.is = function (name) {
            // Since this function reference also acts as a map, we do not want it to be
            // shared between instances, so it is defined here, not on the prototype.
            return !!this.is[name];
        };
 
        // Edge has a userAgent with All browsers so we manage it separately
        // "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"
        if (/Edge\//.test(userAgent)) {
            browserMatch = userAgent.match(/(Edge\/)([\w.]+)/);
        }
 
        if (browserMatch) {
            browserName = browserNames[ST.getKey(browserPrefixes, browserMatch[1])];
            //<feature legacyBrowser>
            if (browserName === 'Safari' && /^Opera/.test(userAgent)) {
                // Prevent Opera 12 and earlier from being incorrectly reported as Safari
                browserName = 'Opera';
            }
            //</feature>
            browserVersion = new ST.Version(browserMatch[2]);
        }
 
        if (engineMatch) {
            engineName = engineNames[ST.getKey(enginePrefixes, engineMatch[1])];
            engineVersion = new ST.Version(engineMatch[2]);
        }
 
        if (engineName === 'Trident' && browserName !== 'IE') {
            browserName = 'IE';
            var version = userAgent.match(/.*rv:(\d+.\d+)/);
            if (version && version.length) {
                version = version[1];
                browserVersion = new ST.Version(version);
            }
        }
 
        // Facebook changes the userAgent when you view a website within their iOS app. For some reason, the strip out information
        // about the browser, so we have to detect that and fake it...
        if (userAgent.match(/FB/) && browserName === "Other") {
            browserName = browserNames.safari;
            engineName = engineNames.webkit;
        }
 
        if (userAgent.match(/Android.*Chrome/g)) {
            browserName = 'ChromeMobile';
        }
 
        if (userAgent.match(/OPR/)) {
            browserName = 'Opera';
            browserMatch = userAgent.match(/OPR\/(\d+.\d+)/);
            browserVersion = new ST.Version(browserMatch[1]);
        }
 
        ST.apply(this, {
            engineName: engineName,
            engineVersion: engineVersion,
            name: browserName,
            version: browserVersion
        });
 
        this.setFlag(browserName, true, publish); // e.g., ST.isIE
 
        if (browserVersion) {
            majorVer = browserVersion.getMajor() || '';
            //<feature legacyBrowser>
            if (me.is.IE) {
                majorVer = parseInt(majorVer, 10);
                mode = document.documentMode;
 
                // IE's Developer Tools allows switching of Browser Mode (userAgent) and
                // Document Mode (actual behavior) independently. While this makes no real
                // sense, the bottom line is that document.documentMode holds the key to
                // getting the proper "version" determined. That value is always 5 when in
                // Quirks Mode.
 
                if (mode === 7 || (majorVer === 7 && mode !== 8 && mode !== 9 && mode !== 10)) {
                    majorVer = 7;
                } else if (mode === 8 || (majorVer === 8 && mode !== 8 && mode !== 9 && mode !== 10)) {
                    majorVer = 8;
                } else if (mode === 9 || (majorVer === 9 && mode !== 7 && mode !== 8 && mode !== 10)) {
                    majorVer = 9;
                } else if (mode === 10 || (majorVer === 10 && mode !== 7 && mode !== 8 && mode !== 9)) {
                    majorVer = 10;
                } else if (mode === 11 || (majorVer === 11 && mode !== 7 && mode !== 8 && mode !== 9 && mode !== 10)) {
                    majorVer = 11;
                }
 
                maxIEVersion = Math.max(majorVer, 12);
                for (i = 7; i <= maxIEVersion; ++i) {
                    prefix = 'isIE' + i;
                    if (majorVer <= i) {
                        ST[prefix + 'm'] = true;
                    }
 
                    if (majorVer === i) {
                        ST[prefix] = true;
                    }
 
                    if (majorVer >= i) {
                        ST[prefix + 'p'] = true;
                    }
                }
            }
 
            if (me.is.Opera && parseInt(majorVer, 10) <= 12) {
                ST.isOpera12m = true;
            }
            //</feature>
 
            ST.chromeVersion = ST.isChrome ? majorVer : 0;
            ST.firefoxVersion = ST.isFirefox ? majorVer : 0;
            ST.ieVersion = ST.isIE ? majorVer : 0;
            ST.operaVersion = ST.isOpera ? majorVer : 0;
            ST.safariVersion = ST.isSafari ? majorVer : 0;
            ST.webKitVersion = ST.isWebKit ? majorVer : 0;
 
            this.setFlag(browserName + majorVer, true, publish); // ST.isIE10
            this.setFlag(browserName + browserVersion.getShortVersion());
        }
 
        for (i in browserNames) {
            if (browserNames.hasOwnProperty(i)) {
                name = browserNames[i];
 
                this.setFlag(name, browserName === name);
            }
        }
 
        this.setFlag(name);
 
        if (engineVersion) {
            this.setFlag(engineName + (engineVersion.getMajor() || ''));
            this.setFlag(engineName + engineVersion.getShortVersion());
        }
 
        for (i in engineNames) {
            if (engineNames.hasOwnProperty(i)) {
                name = engineNames[i];
 
                this.setFlag(name, engineName === name, publish);
            }
        }
 
        this.setFlag('Standalone', !!navigator.standalone);
 
        this.setFlag('Ripple', !!document.getElementById("tinyhippos-injected") && !ST.isEmpty(window.top.ripple));
        this.setFlag('WebWorks', !!window.blackberry);
 
        if (window.PhoneGap !== undefined || window.Cordova !== undefined || window.cordova !== undefined) {
            isWebView = true;
            this.setFlag('PhoneGap');
            this.setFlag('Cordova');
        }
 
        // Check if running in UIWebView
        if (/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)(?!.*FBAN)/i.test(userAgent)) {
            isWebView = true;
        }
 
        // Flag to check if it we are in the WebView
        this.setFlag('WebView', isWebView);
 
        /**
         * @property {Boolean}
         * `true` if browser is using strict mode.
         */
        this.isStrict = ST.isStrict = document.compatMode === "CSS1Compat";
 
        /**
         * @property {Boolean}
         * `true` if page is running over SSL.
         */
        this.isSecure = ST.isSecure;
 
        // IE10Quirks, Chrome26Strict, etc.
        this.identity = browserName + majorVer + (this.isStrict ? 'Strict' : 'Quirks');
    },
 
    engineNames: {
        webkit: 'WebKit',
        gecko: 'Gecko',
        presto: 'Presto',
        trident: 'Trident',
        other: 'Other'
    },
 
    enginePrefixes: {
        webkit: 'AppleWebKit/',
        gecko: 'Gecko/',
        presto: 'Presto/',
        trident: 'Trident/'
    },
 
    styleDashPrefixes: {
        WebKit: '-webkit-',
        Gecko: '-moz-',
        Trident: '-ms-',
        Presto: '-o-',
        Other: ''
    },
 
    stylePrefixes: {
        WebKit: 'Webkit',
        Gecko: 'Moz',
        Trident: 'ms',
        Presto: 'O',
        Other: ''
    },
 
    propertyPrefixes: {
        WebKit: 'webkit',
        Gecko: 'moz',
        Trident: 'ms',
        Presto: 'o',
        Other: ''
    },
 
    browserPrefixes: {
        chrome: 'Chrome/',
        chromeMobile: 'CrMo/',
        chromeiOS: 'CriOS/',
        dolfin: 'Dolfin/',
        edge: 'Edge/',
        firefox: 'Firefox/',
        ie: 'MSIE ',
        opera: 'OPR/',
        safari: 'Version/',
        silk: 'Silk/',
        webosbrowser: 'wOSBrowser/'
    },
 
    browserNames: {
        chrome: 'Chrome',
        chromeMobile: 'ChromeMobile',
        chromeiOS: 'ChromeiOS',
        dolfin: 'Dolfin',
        edge: 'Edge',
        firefox: 'Firefox',
        ie: 'IE',
        opera: 'Opera',
        other: 'Other',
        safari: 'Safari',
        silk: 'Silk',
        webosbrowser: 'webOSBrowser'
    },
 
    // scope: ST.Browser.prototype
 
    /**
     * The full name of the current browser.
     * Possible values are:
     *
     * - IE
     * - Firefox
     * - Safari
     * - Chrome
     * - Opera
     * - Other
     * @type String
     * @readonly
     */
    name: null,
 
    /**
     * Refer to {@link ST.Version}.
     * @type ST.Version
     * @readonly
     */
    version: null,
 
    /**
     * The full name of the current browser's engine.
     * Possible values are:
     *
     * - WebKit
     * - Gecko
     * - Presto
     * - Trident
     * - Other
     * @type String
     * @readonly
     */
    engineName: null,
 
    /**
     * Refer to {@link ST.Version}.
     * @type ST.Version
     * @readonly
     */
    engineVersion: null,
 
    setFlag: function(name, value, publish) {
        if (value === undefined) {
            value = true;
        }
 
        this.is[name] = value;
        this.is[name.toLowerCase()] = value;
        if (publish) {
            ST['is' + name] = value;
        }
 
        return this;
    },
 
    getStyleDashPrefix: function() {
        return this.styleDashPrefixes[this.engineName];
    },
 
    getStylePrefix: function() {
        return this.stylePrefixes[this.engineName];
    },
 
    getVendorProperyName: function(name) {
        var prefix = this.propertyPrefixes[this.engineName];
 
        if (prefix.length > 0) {
            return prefix + ST.capitalize(name);
        }
 
        return name;
    },
 
    getPreferredTranslationMethod: function(config) {
        if (typeof config === 'object' && 'translationMethod' in config && config.translationMethod !== 'auto') {
            return config.translationMethod;
        } else {
            return 'csstransform';
        }
    }
});
 
ST._initBrowser = function (userAgent) {
    ST.userAgent = userAgent.toLowerCase();
    ST.browser = new ST.Browser(userAgent, true);
};
 
/**
 * @class ST.browser
 * @extends ST.Browser
 * @singleton
 * Provides useful information about the current browser.
 *
 * Example:
 *
 *     if (ST.browser.is.IE) {
 *         // IE specific code here
 *     }
 *
 *     if (ST.browser.is.WebKit) {
 *         // WebKit specific code here
 *     }
 *
 *     console.log("Version " + ST.browser.version);
 *
 * For a full list of supported values, refer to {@link #is} property/method.
 *
 */
if (!ST.isSandbox) {
    // if it's a sandobx test, browser will be set later by the context
    ST._initBrowser(navigator.userAgent);
}