var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

import { EventEmitter } from '@angular/core';
import { getQuerystring } from '../util/util';
import { ready, windowDimensions, flushDimensionCache } from '../util/dom';
/**
 * @name Platform
 * @description
 * The Platform service can be used to get information about your current device.
 * You can get all of the platforms associated with the device using the [platforms](#platforms)
 * method, including whether the app is being viewed from a tablet, if it's
 * on a mobile device or browser, and the exact platform (iOS, Android, etc).
 * You can also get the orientation of the device, if it uses right-to-left
 * language direction, and much much more. With this information you can completely
 * customize your app to fit any device.
 *
 * @usage
 * ```ts
 * import {Platform} from 'ionic-angular';
 *
 * @Component({...})
 * export MyPage {
 *   constructor(platform: Platform) {
 *     this.platform = platform;
 *   }
 * }
 * ```
 * @demo /docs/v2/demos/platform/
 */
export var Platform = function () {
    function Platform() {
        var _this = this;

        var platforms = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];

        _classCallCheck(this, Platform);

        this._versions = {};
        this._onResizes = [];
        this._bbActions = [];
        // Events meant to be triggered by the engine
        // **********************************************
        /**
         * @private
         */
        this.backButton = new EventEmitter();
        /**
         * The pause event emits when the native platform puts the application
         * into the background, typically when the user switches to a different
         * application. This event would emit when a Cordova app is put into
         * the background, however, it would not fire on a standard web browser.
         */
        this.pause = new EventEmitter();
        /**
         * The resume event emits when the native platform pulls the application
         * out from the background. This event would emit when a Cordova app comes
         * out from the background, however, it would not fire on a standard web browser.
         */
        this.resume = new EventEmitter();
        this._platforms = platforms;
        this._readyPromise = new Promise(function (res) {
            _this._readyResolve = res;
        });
        this.backButton.subscribe(function () {
            // the hardware back button event has been fired
            console.debug('hardware back button');
            // decide which backbutton action should run
            _this.runBackButtonAction();
        });
    }
    /**
     * @private
     */


    _createClass(Platform, [{
        key: 'setZone',
        value: function setZone(zone) {
            this.zone = zone;
        }
        // Methods
        // **********************************************
        /**
         * @returns {boolean} returns true/false based on platform.
         * @description
         * Depending on the platform the user is on, `is(platformName)` will
         * return `true` or `false`. Note that the same app can return `true`
         * for more than one platform name. For example, an app running from
         * an iPad would return `true` for the platform names: `mobile`,
         * `ios`, `ipad`, and `tablet`. Additionally, if the app was running
         * from Cordova then `cordova` would be true, and if it was running
         * from a web browser on the iPad then `mobileweb` would be `true`.
         *
         * ```
         * import {Platform} from 'ionic-angular';
         *
         * @Component({...})
         * export MyPage {
         *   constructor(platform: Platform) {
         *     this.platform = platform;
         *
         *     if (this.platform.is('ios')) {
         *       // This will only print when on iOS
         *       console.log("I'm an iOS device!");
         *     }
         *   }
         * }
         * ```
         *
         * | Platform Name   | Description                        |
         * |-----------------|------------------------------------|
         * | android         | on a device running Android.       |
         * | cordova         | on a device running Cordova.       |
         * | core            | on a desktop device.               |
         * | ios             | on a device running iOS.           |
         * | ipad            | on an iPad device.                 |
         * | iphone          | on an iPhone device.               |
         * | mobile          | on a mobile device.                |
         * | mobileweb       | in a browser on a mobile device.   |
         * | phablet         | on a phablet device.               |
         * | tablet          | on a tablet device.                |
         * | windows         | on a device running Windows.       |
         *
         * @param {string} platformName
         */

    }, {
        key: 'is',
        value: function is(platformName) {
            return this._platforms.indexOf(platformName) > -1;
        }
        /**
         * @returns {array} the array of platforms
         * @description
         * Depending on what device you are on, `platforms` can return multiple values.
         * Each possible value is a hierarchy of platforms. For example, on an iPhone,
         * it would return `mobile`, `ios`, and `iphone`.
         *
         * ```
         * import {Platform} from 'ionic-angular';
         *
         * @Component({...})
         * export MyPage {
         *   constructor(platform: Platform) {
         *     this.platform = platform;
         *
         *     // This will print an array of the current platforms
         *     console.log(this.platform.platforms());
         *   }
         * }
         * ```
         */

    }, {
        key: 'platforms',
        value: function platforms() {
            // get the array of active platforms, which also knows the hierarchy,
            // with the last one the most important
            return this._platforms;
        }
        /**
         * Returns an object containing version information about all of the platforms.
         *
         * ```
         * import {Platform} from 'ionic-angular';
         *
         * @Component({...})
         * export MyPage {
         *   constructor(platform: Platform) {
         *     this.platform = platform;
         *
         *     // This will print an object containing
         *     // all of the platforms and their versions
         *     console.log(platform.versions());
         *   }
         * }
         * ```
         *
         * @returns {object} An object containing all of the platforms and their versions.
         */

    }, {
        key: 'versions',
        value: function versions() {
            // get all the platforms that have a valid parsed version
            return this._versions;
        }
        /**
         * @private
         */

    }, {
        key: 'version',
        value: function version() {
            for (var platformName in this._versions) {
                if (this._versions[platformName]) {
                    return this._versions[platformName];
                }
            }
            return {};
        }
        /**
         * Returns a promise when the platform is ready and native functionality
         * can be called. If the app is running from within a web browser, then
         * the promise will resolve when the DOM is ready. When the app is running
         * from an application engine such as Cordova, then the promise will
         * resolve when Cordova triggers the `deviceready` event.
         *
         * The resolved value is the `readySource`, which states which platform
         * ready was used. For example, when Cordova is ready, the resolved ready
         * source is `cordova`. The default ready source value will be `dom`. The
         * `readySource` is useful if different logic should run depending on the
         * platform the app is running from. For example, only Cordova can execute
         * the status bar plugin, so the web should not run status bar plugin logic.
         *
         * ```
         * import {Component} from '@angular/core';
         * import {Platform} from 'ionic-angular';
         *
         * @Component({...})
         * export MyApp {
         *   constructor(platform: Platform) {
         *     platform.ready().then((readySource) => {
         *       console.log('Platform ready from', readySource);
         *       // Platform now ready, execute any required native code
         *     });
         *   }
         * }
         * ```
         * @returns {promise}
         */

    }, {
        key: 'ready',
        value: function ready() {
            return this._readyPromise;
        }
        /**
         * @private
         * This should be triggered by the engine when the platform is
         * ready. If there was no custom prepareReady method from the engine,
         * such as Cordova or Electron, then it uses the default DOM ready.
         */

    }, {
        key: 'triggerReady',
        value: function triggerReady(readySource) {
            var _this2 = this;

            this.zone.run(function () {
                _this2._readyResolve(readySource);
            });
        }
        /**
         * @private
         * This is the default prepareReady if it's not replaced by an engine,
         * such as Cordova or Electron. If there was no custom prepareReady
         * method from an engine then it uses the method below, which triggers
         * the platform ready on the DOM ready event, and the default resolved
         * value is `dom`.
         */

    }, {
        key: 'prepareReady',
        value: function prepareReady() {
            var _this3 = this;

            ready(function () {
                _this3.triggerReady('dom');
            });
        }
        /**
         * Set the app's language direction, which will update the `dir` attribute
         * on the app's root `<html>` element. We recommend the app's `index.html`
         * file already has the correct `dir` attribute value set, such as
         * `<html dir="ltr">` or `<html dir="rtl">`. This method is useful if the
         * direction needs to be dynamically changed per user/session.
         * [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)
         * @param {string} dir  Examples: `rtl`, `ltr`
         */

    }, {
        key: 'setDir',
        value: function setDir(dir, updateDocument) {
            this._dir = (dir || '').toLowerCase();
            if (updateDocument !== false) {
                document.documentElement.setAttribute('dir', dir);
            }
        }
        /**
         * Returns app's language direction.
         * We recommend the app's `index.html` file already has the correct `dir`
         * attribute value set, such as `<html dir="ltr">` or `<html dir="rtl">`.
         * [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)
         * @returns {string}
         */

    }, {
        key: 'dir',
        value: function dir() {
            return this._dir;
        }
        /**
         * Returns if this app is using right-to-left language direction or not.
         * We recommend the app's `index.html` file already has the correct `dir`
         * attribute value set, such as `<html dir="ltr">` or `<html dir="rtl">`.
         * [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)
         * @returns {boolean}
         */

    }, {
        key: 'isRTL',
        value: function isRTL() {
            return this._dir === 'rtl';
        }
        /**
         * Set the app's language and optionally the country code, which will update
         * the `lang` attribute on the app's root `<html>` element.
         * We recommend the app's `index.html` file already has the correct `lang`
         * attribute value set, such as `<html lang="en">`. This method is useful if
         * the language needs to be dynamically changed per user/session.
         * [W3C: Declaring language in HTML](http://www.w3.org/International/questions/qa-html-language-declarations)
         * @param {string} language  Examples: `en-US`, `en-GB`, `ar`, `de`, `zh`, `es-MX`
         */

    }, {
        key: 'setLang',
        value: function setLang(language, updateDocument) {
            this._lang = language;
            if (updateDocument !== false) {
                document.documentElement.setAttribute('lang', language);
            }
        }
        /**
         * Returns app's language and optional country code.
         * We recommend the app's `index.html` file already has the correct `lang`
         * attribute value set, such as `<html lang="en">`.
         * [W3C: Declaring language in HTML](http://www.w3.org/International/questions/qa-html-language-declarations)
         * @returns {string}
         */

    }, {
        key: 'lang',
        value: function lang() {
            return this._lang;
        }
        // Methods meant to be overridden by the engine
        // **********************************************
        // Provided NOOP methods so they do not error when
        // called by engines (the browser)that do not provide them
        /**
         * @private
         */

    }, {
        key: 'exitApp',
        value: function exitApp() {}
        /**
         * The back button event is triggered when the user presses the native
         * platform's back button, also referred to as the "hardware" back button.
         * This event is only used within Cordova apps running on Android and
         * Windows platforms. This event is not fired on iOS since iOS doesn't come
         * with a hardware back button in the same sense an Android or Windows device
         * does.
         *
         * Registering a hardware back button action and setting a priority allows
         * apps to control which action should be called when the hardware back
         * button is pressed. This method decides which of the registered back button
         * actions has the highest priority and should be called.
         *
         * @param {Function} callback Called when the back button is pressed,
         * if this registered action has the highest priority.
         * @param {number} priority Set the priority for this action. Only the highest priority will execute. Defaults to `0`.
         * @returns {Function} A function that, when called, will unregister
         * the its back button action.
         */

    }, {
        key: 'registerBackButtonAction',
        value: function registerBackButtonAction(fn) {
            var _this4 = this;

            var priority = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];

            var action = { fn: fn, priority: priority };
            this._bbActions.push(action);
            // return a function to unregister this back button action
            return function () {
                var index = _this4._bbActions.indexOf(action);
                if (index > -1) {
                    _this4._bbActions.splice(index, 1);
                }
            };
        }
        /**
         * @private
         */

    }, {
        key: 'runBackButtonAction',
        value: function runBackButtonAction() {
            // decide which one back button action should run
            var winner = null;
            this._bbActions.forEach(function (action) {
                if (!winner || action.priority >= winner.priority) {
                    winner = action;
                }
            });
            // run the winning action if there is one
            winner && winner.fn && winner.fn();
        }
        // Getter/Setter Methods
        // **********************************************
        /**
         * @private
         */

    }, {
        key: 'setUrl',
        value: function setUrl(url) {
            this._url = url;
            this._qs = getQuerystring(url);
        }
        /**
         * @private
         */

    }, {
        key: 'url',
        value: function url() {
            return this._url;
        }
        /**
         * @private
         */

    }, {
        key: 'query',
        value: function query(key) {
            return (this._qs || {})[key];
        }
        /**
         * @private
         */

    }, {
        key: 'setUserAgent',
        value: function setUserAgent(userAgent) {
            this._ua = userAgent;
        }
        /**
         * @private
         */

    }, {
        key: 'userAgent',
        value: function userAgent() {
            return this._ua || '';
        }
        /**
         * @private
         */

    }, {
        key: 'setNavigatorPlatform',
        value: function setNavigatorPlatform(navigatorPlatform) {
            this._bPlt = navigatorPlatform;
        }
        /**
         * @private
         */

    }, {
        key: 'navigatorPlatform',
        value: function navigatorPlatform() {
            return this._bPlt || '';
        }
        /**
         * Gets the width of the platform's viewport using `window.innerWidth`.
         * Using this method is preferred since the dimension is a cached value,
         * which reduces the chance of multiple and expensive DOM reads.
         */

    }, {
        key: 'width',
        value: function width() {
            return windowDimensions().width;
        }
        /**
         * Gets the height of the platform's viewport using `window.innerHeight`.
         * Using this method is preferred since the dimension is a cached value,
         * which reduces the chance of multiple and expensive DOM reads.
         */

    }, {
        key: 'height',
        value: function height() {
            return windowDimensions().height;
        }
        /**
         * Returns `true` if the app is in portait mode.
         */

    }, {
        key: 'isPortrait',
        value: function isPortrait() {
            return this.width() < this.height();
        }
        /**
         * Returns `true` if the app is in landscape mode.
         */

    }, {
        key: 'isLandscape',
        value: function isLandscape() {
            return !this.isPortrait();
        }
        /**
         * @private
         */

    }, {
        key: 'windowResize',
        value: function windowResize() {
            var self = this;
            clearTimeout(self._resizeTm);
            self._resizeTm = setTimeout(function () {
                flushDimensionCache();
                for (var i = 0; i < self._onResizes.length; i++) {
                    try {
                        self._onResizes[i]();
                    } catch (e) {
                        console.error(e);
                    }
                }
            }, 200);
        }
        /**
         * @private
         */

    }, {
        key: 'onResize',
        value: function onResize(cb) {
            var self = this;
            self._onResizes.push(cb);
            return function () {
                var index = self._onResizes.indexOf(cb);
                if (index > -1) {
                    self._onResizes.splice(index, 1);
                }
            };
        }
        // Platform Registry
        // **********************************************
        /**
         * @private
         */

    }, {
        key: 'testQuery',

        /**
         * @private
         */
        value: function testQuery(queryValue, queryTestValue) {
            var valueSplit = queryValue.toLowerCase().split(';');
            return valueSplit.indexOf(queryTestValue) > -1;
        }
        /**
         * @private
         */

    }, {
        key: 'testNavigatorPlatform',
        value: function testNavigatorPlatform(navigatorPlatformExpression) {
            var rgx = new RegExp(navigatorPlatformExpression, 'i');
            return rgx.test(this._bPlt);
        }
        /**
         * @private
         */

    }, {
        key: 'matchUserAgentVersion',
        value: function matchUserAgentVersion(userAgentExpression) {
            if (this._ua && userAgentExpression) {
                var val = this._ua.match(userAgentExpression);
                if (val) {
                    return {
                        major: val[1],
                        minor: val[2]
                    };
                }
            }
        }
        /**
         * @private
         */

    }, {
        key: 'isPlatformMatch',
        value: function isPlatformMatch(queryStringName, userAgentAtLeastHas) {
            var userAgentMustNotHave = arguments.length <= 2 || arguments[2] === undefined ? [] : arguments[2];

            var queryValue = this.query('ionicplatform');
            if (queryValue) {
                return this.testQuery(queryValue, queryStringName);
            }
            userAgentAtLeastHas = userAgentAtLeastHas || [queryStringName];
            var userAgent = this._ua.toLowerCase();
            for (var i = 0; i < userAgentAtLeastHas.length; i++) {
                if (userAgent.indexOf(userAgentAtLeastHas[i]) > -1) {
                    for (var j = 0; j < userAgentMustNotHave.length; j++) {
                        if (userAgent.indexOf(userAgentMustNotHave[j]) > -1) {
                            return false;
                        }
                    }
                    return true;
                }
            }
            return false;
        }
        /**
         * @private
         */

    }, {
        key: 'load',
        value: function load(config) {
            var rootPlatformNode = void 0;
            var enginePlatformNode = void 0;
            var self = this;
            // figure out the most specific platform and active engine
            var tmpPlatform = void 0;
            for (var platformName in platformRegistry) {
                tmpPlatform = this.matchPlatform(platformName);
                if (tmpPlatform) {
                    // we found a platform match!
                    // check if its more specific than the one we already have
                    if (tmpPlatform.isEngine) {
                        // because it matched then this should be the active engine
                        // you cannot have more than one active engine
                        enginePlatformNode = tmpPlatform;
                    } else if (!rootPlatformNode || tmpPlatform.depth > rootPlatformNode.depth) {
                        // only find the root node for platforms that are not engines
                        // set this node as the root since we either don't already
                        // have one, or this one is more specific that the current one
                        rootPlatformNode = tmpPlatform;
                    }
                }
            }
            if (!rootPlatformNode) {
                rootPlatformNode = new PlatformNode(platformDefault);
            }
            // build a Platform instance filled with the
            // hierarchy of active platforms and settings
            if (rootPlatformNode) {
                // check if we found an engine node (cordova/node-webkit/etc)
                if (enginePlatformNode) {
                    // add the engine to the first in the platform hierarchy
                    // the original rootPlatformNode now becomes a child
                    // of the engineNode, which is not the new root
                    enginePlatformNode.child = rootPlatformNode;
                    rootPlatformNode.parent = enginePlatformNode;
                    rootPlatformNode = enginePlatformNode;
                }
                var platformNode = rootPlatformNode;
                while (platformNode) {
                    insertSuperset(platformNode);
                    platformNode = platformNode.child;
                }
                // make sure the root noot is actually the root
                // incase a node was inserted before the root
                platformNode = rootPlatformNode.parent;
                while (platformNode) {
                    rootPlatformNode = platformNode;
                    platformNode = platformNode.parent;
                }
                platformNode = rootPlatformNode;
                while (platformNode) {
                    platformNode.initialize(this, config);
                    // set the array of active platforms with
                    // the last one in the array the most important
                    this._platforms.push(platformNode.name);
                    // get the platforms version if a version parser was provided
                    this._versions[platformNode.name] = platformNode.version(this);
                    // go to the next platform child
                    platformNode = platformNode.child;
                }
            }
            if (this._platforms.indexOf('mobile') > -1 && this._platforms.indexOf('cordova') === -1) {
                this._platforms.push('mobileweb');
            }
        }
        /**
         * @private
         */

    }, {
        key: 'matchPlatform',
        value: function matchPlatform(platformName) {
            // build a PlatformNode and assign config data to it
            // use it's getRoot method to build up its hierarchy
            // depending on which platforms match
            var platformNode = new PlatformNode(platformName);
            var rootNode = platformNode.getRoot(this);
            if (rootNode) {
                rootNode.depth = 0;
                var childPlatform = rootNode.child;
                while (childPlatform) {
                    rootNode.depth++;
                    childPlatform = childPlatform.child;
                }
            }
            return rootNode;
        }
    }], [{
        key: 'register',
        value: function register(platformConfig) {
            platformRegistry[platformConfig.name] = platformConfig;
        }
        /**
         * @private
         */

    }, {
        key: 'registry',
        value: function registry() {
            return platformRegistry;
        }
        /**
         * @private
         */

    }, {
        key: 'get',
        value: function get(platformName) {
            return platformRegistry[platformName] || {};
        }
        /**
         * @private
         */

    }, {
        key: 'setDefault',
        value: function setDefault(platformName) {
            platformDefault = platformName;
        }
    }]);

    return Platform;
}();
function insertSuperset(platformNode) {
    var supersetPlaformName = platformNode.superset();
    if (supersetPlaformName) {
        // add a platform in between two exist platforms
        // so we can build the correct hierarchy of active platforms
        var supersetPlatform = new PlatformNode(supersetPlaformName);
        supersetPlatform.parent = platformNode.parent;
        supersetPlatform.child = platformNode;
        if (supersetPlatform.parent) {
            supersetPlatform.parent.child = supersetPlatform;
        }
        platformNode.parent = supersetPlatform;
    }
}
/**
 * @private
 */

var PlatformNode = function () {
    function PlatformNode(platformName) {
        _classCallCheck(this, PlatformNode);

        this.c = Platform.get(platformName);
        this.name = platformName;
        this.isEngine = this.c.isEngine;
    }

    _createClass(PlatformNode, [{
        key: 'settings',
        value: function settings() {
            return this.c.settings || {};
        }
    }, {
        key: 'superset',
        value: function superset() {
            return this.c.superset;
        }
    }, {
        key: 'isMatch',
        value: function isMatch(p) {
            return this.c.isMatch && this.c.isMatch(p) || false;
        }
    }, {
        key: 'initialize',
        value: function initialize(platform, config) {
            this.c.initialize && this.c.initialize(platform, config);
        }
    }, {
        key: 'version',
        value: function version(p) {
            if (this.c.versionParser) {
                var v = this.c.versionParser(p);
                if (v) {
                    var str = v.major + '.' + v.minor;
                    return {
                        str: str,
                        num: parseFloat(str),
                        major: parseInt(v.major, 10),
                        minor: parseInt(v.minor, 10)
                    };
                }
            }
        }
    }, {
        key: 'getRoot',
        value: function getRoot(p) {
            if (this.isMatch(p)) {
                var parents = this.getSubsetParents(this.name);
                if (!parents.length) {
                    return this;
                }
                var platform = null;
                var rootPlatform = null;
                for (var i = 0; i < parents.length; i++) {
                    platform = new PlatformNode(parents[i]);
                    platform.child = this;
                    rootPlatform = platform.getRoot(p);
                    if (rootPlatform) {
                        this.parent = platform;
                        return rootPlatform;
                    }
                }
            }
            return null;
        }
    }, {
        key: 'getSubsetParents',
        value: function getSubsetParents(subsetPlatformName) {
            var platformRegistry = Platform.registry();
            var parentPlatformNames = [];
            var platform = null;
            for (var platformName in platformRegistry) {
                platform = platformRegistry[platformName];
                if (platform.subsets && platform.subsets.indexOf(subsetPlatformName) > -1) {
                    parentPlatformNames.push(platformName);
                }
            }
            return parentPlatformNames;
        }
    }]);

    return PlatformNode;
}();

var platformRegistry = {};
var platformDefault = null;