"use strict";
var core_1 = require('angular2/core');
var util_1 = require('../util/util');
var dom_1 = require('../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';
 *
 * @Page({...})
 * export MyPage {
 *   constructor(platform: Platform) {
 *     this.platform = platform;
 *   }
 * }
 * ```
 * @demo /docs/v2/demos/platform/
 */
var Platform = (function () {
    function Platform(platforms) {
        var _this = this;
        if (platforms === void 0) { platforms = []; }
        this._versions = {};
        this._onResizes = [];
        // Events meant to be triggered by the engine
        // **********************************************
        /**
        * @private
        */
        this.backButton = new core_1.EventEmitter();
        /**
        * @private
        */
        this.pause = new core_1.EventEmitter();
        /**
        * @private
        */
        this.resume = new core_1.EventEmitter();
        this._platforms = platforms;
        this._readyPromise = new Promise(function (res) { _this._readyResolve = res; });
    }
    /**
     * @private
     */
    Platform.prototype.setZone = function (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';
     *
     * @Page({...})
     * 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
     */
    Platform.prototype.is = function (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';
     *
     * @Page({...})
     * export MyPage {
     *   constructor(platform: Platform) {
     *     this.platform = platform;
     *
     *     // This will print an array of the current platforms
     *     console.log(this.platform.platforms());
     *   }
     * }
     * ```
     */
    Platform.prototype.platforms = function () {
        // 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';
     *
     * @Page({...})
     * 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());
     *   }
     * }
     * ```
     *
     * @param {string} [platformName] optional platformName
     * @returns {object} An object with various platform info
     *
     */
    Platform.prototype.versions = function () {
        // get all the platforms that have a valid parsed version
        return this._versions;
    };
    /**
     * @private
     */
    Platform.prototype.version = function () {
        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.
     *
     * ```
     * import {Platform} from 'ionic-angular';
     *
     * @Page({...})
     * export MyPage {
     *   constructor(platform: Platform) {
     *     platform.ready().then(() => {
     *       console.log('Platform ready');
     *       // The platform is now ready, execute any native code you want
     *     });
     *   }
     * }
     * ```
     * @returns {promise}
     */
    Platform.prototype.ready = function () {
        // this is the default if it's not replaced by the engine
        // if there was no custom ready method from the engine
        // then use the default DOM ready
        return this._readyPromise;
    };
    /**
     * @private
     */
    Platform.prototype.triggerReady = function () {
        var _this = this;
        this._zone.run(function () {
            _this._readyResolve();
        });
    };
    /**
     * @private
     */
    Platform.prototype.prepareReady = function () {
        // this is the default prepareReady if it's not replaced by the engine
        // if there was no custom ready method from the engine
        // then use the default DOM ready
        dom_1.ready(this.triggerReady.bind(this));
    };
    /**
    * 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`
    */
    Platform.prototype.setDir = function (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}
     */
    Platform.prototype.dir = function () {
        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}
     */
    Platform.prototype.isRTL = function () {
        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`
    */
    Platform.prototype.setLang = function (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}
     */
    Platform.prototype.lang = function () {
        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
    */
    Platform.prototype.exitApp = function () { };
    // Getter/Setter Methods
    // **********************************************
    /**
    * @private
    */
    Platform.prototype.setUrl = function (url) {
        this._url = url;
        this._qs = util_1.getQuerystring(url);
    };
    /**
    * @private
    */
    Platform.prototype.url = function () {
        return this._url;
    };
    /**
    * @private
    */
    Platform.prototype.query = function (key) {
        return (this._qs || {})[key];
    };
    /**
    * @private
    */
    Platform.prototype.setUserAgent = function (userAgent) {
        this._ua = userAgent;
    };
    /**
    * @private
    */
    Platform.prototype.userAgent = function () {
        return this._ua || '';
    };
    /**
    * @private
    */
    Platform.prototype.setNavigatorPlatform = function (navigatorPlatform) {
        this._bPlt = navigatorPlatform;
    };
    /**
    * @private
    */
    Platform.prototype.navigatorPlatform = function () {
        return this._bPlt || '';
    };
    /**
    * @private
    */
    Platform.prototype.width = function () {
        return dom_1.windowDimensions().width;
    };
    /**
    * @private
    */
    Platform.prototype.height = function () {
        return dom_1.windowDimensions().height;
    };
    /**
    * @private
    */
    Platform.prototype.isPortrait = function () {
        return this.width() < this.height();
    };
    /**
    * @private
    */
    Platform.prototype.isLandscape = function () {
        return !this.isPortrait();
    };
    /**
    * @private
    */
    Platform.prototype.windowResize = function () {
        var self = this;
        clearTimeout(self._resizeTm);
        self._resizeTm = setTimeout(function () {
            dom_1.flushDimensionCache();
            for (var i = 0; i < self._onResizes.length; i++) {
                try {
                    self._onResizes[i]();
                }
                catch (e) {
                    void 0;
                }
            }
        }, 200);
    };
    /**
     * @private
     * @returns Unregister function
     */
    Platform.prototype.onResize = function (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
     */
    Platform.register = function (platformConfig) {
        platformRegistry[platformConfig.name] = platformConfig;
    };
    /**
    * @private
    */
    Platform.registry = function () {
        return platformRegistry;
    };
    /**
     * @private
     */
    Platform.get = function (platformName) {
        return platformRegistry[platformName] || {};
    };
    /**
     * @private
     */
    Platform.setDefault = function (platformName) {
        platformDefault = platformName;
    };
    /**
     * @private
     */
    Platform.prototype.testQuery = function (queryValue, queryTestValue) {
        var valueSplit = queryValue.toLowerCase().split(';');
        return valueSplit.indexOf(queryTestValue) > -1;
    };
    /**
     * @private
     */
    Platform.prototype.testNavigatorPlatform = function (navigatorPlatformExpression) {
        var rgx = new RegExp(navigatorPlatformExpression, 'i');
        return rgx.test(this._bPlt);
    };
    /**
     * @private
     */
    Platform.prototype.matchUserAgentVersion = function (userAgentExpression) {
        if (this._ua && userAgentExpression) {
            var val = this._ua.match(userAgentExpression);
            if (val) {
                return {
                    major: val[1],
                    minor: val[2]
                };
            }
        }
    };
    /**
     * @private
     */
    Platform.prototype.isPlatformMatch = function (queryStringName, userAgentAtLeastHas, userAgentMustNotHave) {
        if (userAgentMustNotHave === void 0) { userAgentMustNotHave = []; }
        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
     */
    Platform.prototype.load = function (config) {
        var rootPlatformNode;
        var enginePlatformNode;
        var self = this;
        // figure out the most specific platform and active engine
        var tmpPlatform;
        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
     */
    Platform.prototype.matchPlatform = function (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;
    };
    return Platform;
}());
exports.Platform = 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) {
        this.c = Platform.get(platformName);
        this.name = platformName;
        this.isEngine = this.c.isEngine;
    }
    PlatformNode.prototype.settings = function () {
        return this.c.settings || {};
    };
    PlatformNode.prototype.superset = function () {
        return this.c.superset;
    };
    PlatformNode.prototype.isMatch = function (p) {
        return this.c.isMatch && this.c.isMatch(p) || false;
    };
    PlatformNode.prototype.initialize = function (platform, config) {
        this.c.initialize && this.c.initialize(platform, config);
    };
    PlatformNode.prototype.version = function (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)
                };
            }
        }
    };
    PlatformNode.prototype.getRoot = function (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;
    };
    PlatformNode.prototype.getSubsetParents = function (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;
